Redesign System/NumberFormatter to improve primitive numeric types ToString performance.
[mono.git] / mcs / class / corlib / System / Int32.cs
1 //
2 // System.Int32.cs
3 //
4 // Author:
5 //   Miguel de Icaza (miguel@ximian.com)
6 //
7 // (C) Ximian, Inc.  http://www.ximian.com
8 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 //
29
30 using System.Globalization;
31 using System.Threading;
32
33 namespace System {
34         
35         [Serializable]
36 #if NET_2_0
37         [System.Runtime.InteropServices.ComVisible (true)]
38 #endif
39         public struct Int32 : IFormattable, IConvertible, IComparable
40 #if NET_2_0
41                 , IComparable<Int32>, IEquatable <Int32>
42 #endif
43         {
44
45                 public const int MaxValue = 0x7fffffff;
46                 public const int MinValue = -2147483648;
47                 
48                 // This field is looked up by name in the runtime
49                 internal int m_value;
50
51                 public int CompareTo (object v)
52                 {
53                         if (v == null)
54                                 return 1;
55                         
56                         if (!(v is System.Int32))
57                                 throw new ArgumentException (Locale.GetText ("Value is not a System.Int32"));
58
59                         int xv = (int) v;
60                         if (m_value == xv)
61                                 return 0;
62                         if (m_value > xv)
63                                 return 1;
64                         else
65                                 return -1;
66                 }
67
68                 public override bool Equals (object o)
69                 {
70                         if (!(o is System.Int32))
71                                 return false;
72
73                         return ((int) o) == m_value;
74                 }
75
76                 public override int GetHashCode ()
77                 {
78                         return m_value;
79                 }
80
81 #if NET_2_0
82                 public int CompareTo (int value)
83                 {
84                         if (m_value == value)
85                                 return 0;
86                         if (m_value > value)
87                                 return 1;
88                         else
89                                 return -1;
90                 }
91
92                 public bool Equals (int value)
93                 {
94                         return value == m_value;
95                 }
96 #endif
97
98                 internal static bool ProcessTrailingWhitespace (bool tryParse, string s, int position, ref Exception exc)
99                 {
100                         int len = s.Length;
101                         
102                         for (int i = position; i < len; i++){
103                                 char c = s [i];
104                                 
105                                 if (!Char.IsWhiteSpace (c)){
106                                         if (!tryParse)
107                                                 exc = GetFormatException ();
108                                         return false;
109                                 }
110                         }
111                         return true;
112                 }
113
114                 internal static bool Parse (string s, bool tryParse, out int result, out Exception exc)
115                 {
116                         int val = 0;
117                         int len;
118                         int i, sign = 1;
119                         bool digits_seen = false;
120
121                         result = 0;
122                         exc = null;
123
124                         if (s == null) {
125                                 if (!tryParse)
126                                         exc = new ArgumentNullException ("s");
127                                 return false;
128                         }
129
130                         len = s.Length;
131
132                         char c;
133                         for (i = 0; i < len; i++){
134                                 c = s [i];
135                                 if (!Char.IsWhiteSpace (c))
136                                         break;
137                         }
138                         
139                         if (i == len) {
140                                 if (!tryParse)
141                                         exc = GetFormatException ();
142                                 return false;
143                         }
144
145                         c = s [i];
146                         if (c == '+')
147                                 i++;
148                         else if (c == '-'){
149                                 sign = -1;
150                                 i++;
151                         }
152                         
153                         for (; i < len; i++){
154                                 c = s [i];
155
156                                 if (c == '\0') {
157                                         i = len;
158                                         continue;
159                                 }
160                                 
161                                 if (c >= '0' && c <= '9'){
162                                         byte d = (byte) (c - '0');
163                                                 
164                                         if (val > (MaxValue/10))
165                                                 goto overflow;
166                                         
167                                         if (val == (MaxValue/10)){
168                                                 if ((d > (MaxValue % 10)) && (sign == 1 || (d > ((MaxValue % 10) + 1))))
169                                                         goto overflow;
170                                                 if (sign == -1)
171                                                         val = (val * sign * 10) - d;
172                                                 else
173                                                         val = (val * 10) + d;
174
175                                                 if (ProcessTrailingWhitespace (tryParse, s, i + 1, ref exc)){
176                                                         result = val;
177                                                         return true;
178                                                 }
179                                                 goto overflow;
180                                         } else 
181                                                 val = val * 10 + d;
182                                         
183                                         digits_seen = true;
184                                 } else if (!ProcessTrailingWhitespace (tryParse, s, i, ref exc))
185                                         return false;
186                         }
187                         if (!digits_seen) {
188                                 if (!tryParse)
189                                         exc = GetFormatException ();
190                                 return false;
191                         }
192
193                         if (sign == -1)
194                                 result = val * sign;
195                         else
196                                 result = val;
197
198                         return true;
199
200                 overflow:
201                         if (!tryParse)
202                                 exc = new OverflowException ("Value is too large");
203                         return false;
204                 }
205
206                 public static int Parse (string s, IFormatProvider fp)
207                 {
208                         return Parse (s, NumberStyles.Integer, fp);
209                 }
210
211                 public static int Parse (string s, NumberStyles style)
212                 {
213                         return Parse (s, style, null);
214                 }
215
216                 internal static bool CheckStyle (NumberStyles style, bool tryParse, ref Exception exc)
217                 {
218                         if ((style & NumberStyles.AllowHexSpecifier) != 0) {
219                                 NumberStyles ne = style ^ NumberStyles.AllowHexSpecifier;
220                                 if ((ne & NumberStyles.AllowLeadingWhite) != 0)
221                                         ne ^= NumberStyles.AllowLeadingWhite;
222                                 if ((ne & NumberStyles.AllowTrailingWhite) != 0)
223                                         ne ^= NumberStyles.AllowTrailingWhite;
224                                 if (ne != 0) {
225                                         if (!tryParse)
226                                                 exc = new ArgumentException (
227                                                         "With AllowHexSpecifier only " + 
228                                                         "AllowLeadingWhite and AllowTrailingWhite " + 
229                                                         "are permitted.");
230                                         return false;
231                                 }
232                         }
233
234                         return true;
235                 }
236                 
237                 internal static bool JumpOverWhite (ref int pos, string s, bool reportError, bool tryParse, ref Exception exc)
238                 {
239                         while (pos < s.Length && Char.IsWhiteSpace (s [pos]))
240                                 pos++;
241
242                         if (reportError && pos >= s.Length) {
243                                 if (!tryParse)
244                                         exc = GetFormatException ();
245                                 return false;
246                         }
247
248                         return true;
249                 }
250
251                 internal static void FindSign (ref int pos, string s, NumberFormatInfo nfi, 
252                                       ref bool foundSign, ref bool negative)
253                 {
254                         if ((pos + nfi.NegativeSign.Length) <= s.Length &&
255                                 s.IndexOf (nfi.NegativeSign, pos, nfi.NegativeSign.Length) == pos) {
256                                 negative = true;
257                                 foundSign = true;
258                                 pos += nfi.NegativeSign.Length;
259                         } 
260                         else if ((pos + nfi.PositiveSign.Length) < s.Length &&
261                                 s.IndexOf (nfi.PositiveSign, pos, nfi.PositiveSign.Length) == pos) {
262                                 negative = false;
263                                 pos += nfi.PositiveSign.Length;
264                                 foundSign = true;
265                         } 
266                 }
267
268                 internal static void FindCurrency (ref int pos,
269                                                  string s, 
270                                                  NumberFormatInfo nfi,
271                                                  ref bool foundCurrency)
272                 {
273                         if ((pos + nfi.CurrencySymbol.Length) <= s.Length &&
274                              s.Substring (pos, nfi.CurrencySymbol.Length) == nfi.CurrencySymbol) {
275                                 foundCurrency = true;
276                                 pos += nfi.CurrencySymbol.Length;
277                         } 
278                 }
279
280                 internal static bool FindExponent (ref int pos, string s)
281                 {
282                                 int i = s.IndexOfAny(new char [] {'e', 'E'}, pos);
283                                 if (i < 0)
284                                                 return false;
285                                 if (++i == s.Length)
286                                                 return false;
287                                 if (s [i] == '+' || s [i] == '-')
288                                                 if (++i == s.Length)
289                                                                 return false;
290                                 if (!Char.IsDigit (s [i]))
291                                                 return false;
292                                 for (; i < s.Length; ++i)
293                                                 if (!Char.IsDigit (s [i])) 
294                                                                 break;
295                                 pos = i;
296                                 return true;
297                 }
298
299                 internal static bool FindOther (ref int pos,
300                                               string s, 
301                                               string other)
302                 {
303                         if ((pos + other.Length) <= s.Length &&
304                              s.Substring (pos, other.Length) == other) {
305                                 pos += other.Length;
306                                 return true;
307                         } 
308
309                         return false;
310                 }
311
312                 internal static bool ValidDigit (char e, bool allowHex)
313                 {
314                         if (allowHex)
315                                 return Char.IsDigit (e) || (e >= 'A' && e <= 'F') || (e >= 'a' && e <= 'f');
316
317                         return Char.IsDigit (e);
318                 }
319                 
320                 internal static Exception GetFormatException ()
321                 {
322                         return new FormatException ("Input string was not in the correct format");
323                 }
324                 
325                 internal static bool Parse (string s, NumberStyles style, IFormatProvider fp, bool tryParse, out int result, out Exception exc)
326                 {
327                         result = 0;
328                         exc = null;
329
330                         if (s == null) {
331                                 if (!tryParse)
332                                         exc = GetFormatException ();
333                                 return false;
334                         }
335                         
336                         if (s == null) {
337                                 if (!tryParse)
338                                         exc = new ArgumentNullException ();
339                                 return false;
340                         }
341
342                         if (s.Length == 0) {
343                                 if (!tryParse)
344                                         exc = GetFormatException ();
345                                 return false;
346                         }
347
348                         NumberFormatInfo nfi;
349                         if (fp != null) {
350                                 Type typeNFI = typeof (System.Globalization.NumberFormatInfo);
351                                 nfi = (NumberFormatInfo) fp.GetFormat (typeNFI);
352                         }
353                         else
354                                 nfi = Thread.CurrentThread.CurrentCulture.NumberFormat;
355
356                         if (!CheckStyle (style, tryParse, ref exc))
357                                 return false;
358
359                         bool AllowCurrencySymbol = (style & NumberStyles.AllowCurrencySymbol) != 0;
360                         bool AllowHexSpecifier = (style & NumberStyles.AllowHexSpecifier) != 0;
361                         bool AllowThousands = (style & NumberStyles.AllowThousands) != 0;
362                         bool AllowDecimalPoint = (style & NumberStyles.AllowDecimalPoint) != 0;
363                         bool AllowParentheses = (style & NumberStyles.AllowParentheses) != 0;
364                         bool AllowTrailingSign = (style & NumberStyles.AllowTrailingSign) != 0;
365                         bool AllowLeadingSign = (style & NumberStyles.AllowLeadingSign) != 0;
366                         bool AllowTrailingWhite = (style & NumberStyles.AllowTrailingWhite) != 0;
367                         bool AllowLeadingWhite = (style & NumberStyles.AllowLeadingWhite) != 0;
368                         bool AllowExponent = (style & NumberStyles.AllowExponent) != 0;
369
370                         int pos = 0;
371
372                         if (AllowLeadingWhite && !JumpOverWhite (ref pos, s, true, tryParse, ref exc))
373                                 return false;
374
375                         bool foundOpenParentheses = false;
376                         bool negative = false;
377                         bool foundSign = false;
378                         bool foundCurrency = false;
379
380                         // Pre-number stuff
381                         if (AllowParentheses && s [pos] == '(') {
382                                 foundOpenParentheses = true;
383                                 foundSign = true;
384                                 negative = true; // MS always make the number negative when there parentheses
385                                                  // even when NumberFormatInfo.NumberNegativePattern != 0!!!
386                                 pos++;
387                                 if (AllowLeadingWhite && !!JumpOverWhite (ref pos, s, true, tryParse, ref exc))
388                                         return false;
389
390                                 if (s.Substring (pos, nfi.NegativeSign.Length) == nfi.NegativeSign) {
391                                         if (!tryParse)
392                                                 exc = GetFormatException ();
393                                         return false;
394                                 }
395                                 
396                                 if (s.Substring (pos, nfi.PositiveSign.Length) == nfi.PositiveSign) {
397                                         if (!tryParse)
398                                                 exc = GetFormatException ();
399                                         return false;
400                                 }
401                         }
402
403                         if (AllowLeadingSign && !foundSign) {
404                                 // Sign + Currency
405                                 FindSign (ref pos, s, nfi, ref foundSign, ref negative);
406                                 if (foundSign) {
407                                         if (AllowLeadingWhite && !JumpOverWhite (ref pos, s, true, tryParse, ref exc))
408                                                 return false;
409                                         if (AllowCurrencySymbol) {
410                                                 FindCurrency (ref pos, s, nfi,
411                                                               ref foundCurrency);
412                                                 if (foundCurrency && AllowLeadingWhite &&
413                                                                 !JumpOverWhite (ref pos, s, true, tryParse, ref exc))
414                                                         return false;
415                                         }
416                                 }
417                         }
418                         
419                         if (AllowCurrencySymbol && !foundCurrency) {
420                                 // Currency + sign
421                                 FindCurrency (ref pos, s, nfi, ref foundCurrency);
422                                 if (foundCurrency) {
423                                         if (AllowLeadingWhite && !JumpOverWhite (ref pos, s, true, tryParse, ref exc))
424                                                 return false;
425                                         if (foundCurrency) {
426                                                 if (!foundSign && AllowLeadingSign) {
427                                                         FindSign (ref pos, s, nfi, ref foundSign,
428                                                                   ref negative);
429                                                         if (foundSign && AllowLeadingWhite &&
430                                                                         !JumpOverWhite (ref pos, s, true, tryParse, ref exc))
431                                                                 return false;
432                                                 }
433                                         }
434                                 }
435                         }
436
437                         int number = 0;
438                         int nDigits = 0;
439                         bool decimalPointFound = false;
440                         int digitValue;
441                         char hexDigit;
442                                 
443                         // Number stuff
444                         do {
445
446                                 if (!ValidDigit (s [pos], AllowHexSpecifier)) {
447                                         if (AllowThousands &&
448                                             FindOther (ref pos, s, nfi.NumberGroupSeparator))
449                                             continue;
450                                         else
451                                         if (!decimalPointFound && AllowDecimalPoint &&
452                                             FindOther (ref pos, s, nfi.NumberDecimalSeparator)) {
453                                             decimalPointFound = true;
454                                             continue;
455                                         }
456
457                                         break;
458                                 }
459                                 else if (AllowHexSpecifier) {
460                                         nDigits++;
461                                         hexDigit = s [pos++];
462                                         if (Char.IsDigit (hexDigit))
463                                                 digitValue = (int) (hexDigit - '0');
464                                         else if (Char.IsLower (hexDigit))
465                                                 digitValue = (int) (hexDigit - 'a' + 10);
466                                         else
467                                                 digitValue = (int) (hexDigit - 'A' + 10);
468
469                                         uint unumber = (uint)number;
470                                         if (tryParse){
471                                                 if ((unumber & 0xf0000000) != 0)
472                                                         return false;
473                                                 
474                                                 number = (int) (unumber * 16u + (uint) digitValue);
475                                         } else {
476                                                 number = (int)checked (unumber * 16u + (uint)digitValue);
477                                         }
478                                 }
479                                 else if (decimalPointFound) {
480                                         nDigits++;
481                                         // Allows decimal point as long as it's only 
482                                         // followed by zeroes.
483                                         if (s [pos++] != '0') {
484                                                 if (!tryParse)
485                                                         exc = new OverflowException ("Value too large or too " +
486                                                                         "small.");
487                                                 return false;
488                                         }
489                                 }
490                                 else {
491                                         nDigits++;
492
493                                         try {
494                                                 // Calculations done as negative
495                                                 // (abs (MinValue) > abs (MaxValue))
496                                                 number = checked (
497                                                         number * 10 - 
498                                                         (int) (s [pos++] - '0')
499                                                         );
500                                         } catch (OverflowException) {
501                                                 if (!tryParse)
502                                                         exc = new OverflowException ("Value too large or too " +
503                                                                         "small.");
504                                                 return false;
505                                         }
506                                 }
507                         } while (pos < s.Length);
508
509                         // Post number stuff
510                         if (nDigits == 0) {
511                                 if (!tryParse)
512                                         exc = GetFormatException ();
513                                 return false;
514                         }
515
516                         if (AllowExponent) 
517                                         FindExponent(ref pos, s);
518
519                         if (AllowTrailingSign && !foundSign) {
520                                 // Sign + Currency
521                                 FindSign (ref pos, s, nfi, ref foundSign, ref negative);
522                                 if (foundSign) {
523                                         if (AllowTrailingWhite && !JumpOverWhite (ref pos, s, true, tryParse, ref exc))
524                                                 return false;
525                                         if (AllowCurrencySymbol)
526                                                 FindCurrency (ref pos, s, nfi,
527                                                               ref foundCurrency);
528                                 }
529                         }
530                         
531                         if (AllowCurrencySymbol && !foundCurrency) {
532                                 // Currency + sign
533                                 FindCurrency (ref pos, s, nfi, ref foundCurrency);
534                                 if (foundCurrency) {
535                                         if (AllowTrailingWhite && !JumpOverWhite (ref pos, s, true, tryParse, ref exc))
536                                                 return false;
537                                         if (!foundSign && AllowTrailingSign)
538                                                 FindSign (ref pos, s, nfi, ref foundSign,
539                                                           ref negative);
540                                 }
541                         }
542                         
543                         if (AllowTrailingWhite && pos < s.Length && !JumpOverWhite (ref pos, s, false, tryParse, ref exc))
544                                 return false;
545
546                         if (foundOpenParentheses) {
547                                 if (pos >= s.Length || s [pos++] != ')') {
548                                         if (!tryParse)
549                                                 exc = GetFormatException ();
550                                         return false;
551                                 }
552                                 if (AllowTrailingWhite && pos < s.Length &&
553                                                 !JumpOverWhite (ref pos, s, false, tryParse, ref exc))
554                                         return false;
555                         }
556
557                         if (pos < s.Length && s [pos] != '\u0000') {
558                                 if (!tryParse)
559                                         exc = GetFormatException ();
560                                 return false;
561                         }
562                         
563                         if (!negative && !AllowHexSpecifier){
564                                 if (tryParse){
565                                         long lval = -number;
566
567                                         if (lval < MinValue || lval > MaxValue)
568                                                 return false;
569                                         number = (int) lval;
570                                 } else
571                                         number = checked (-number);
572                         }
573                         
574                         result = number;
575
576                         return true;
577                 }
578
579                 public static int Parse (string s) 
580                 {
581                         Exception exc;
582                         int res;
583
584                         if (!Parse (s, false, out res, out exc))
585                                 throw exc;
586
587                         return res;
588                 }
589
590                 public static int Parse (string s, NumberStyles style, IFormatProvider fp) 
591                 {
592                         Exception exc;
593                         int res;
594
595                         if (!Parse (s, style, fp, false, out res, out exc))
596                                 throw exc;
597
598                         return res;
599                 }
600
601 #if NET_2_0
602                 public static bool TryParse (string s, out int result) 
603                 {
604                         Exception exc;
605                         
606                         if (!Parse (s, true, out result, out exc)) {
607                                 result = 0;
608                                 return false;
609                         }
610
611                         return true;
612                 }
613
614                 public static bool TryParse (string s, NumberStyles style, IFormatProvider provider, out int result) 
615                 {
616                         Exception exc;
617                         if (!Parse (s, style, provider, true, out result, out exc)) {
618                                 result = 0;
619                                 return false;
620                         }
621
622                         return true;
623                 }
624 #endif
625
626                 public override string ToString ()
627                 {
628                         return new NumberFormatter(null, m_value).FormatDecimal(-1, null);
629                 }
630
631                 public string ToString (IFormatProvider provider)
632                 {
633                         NumberFormatInfo nfi = NumberFormatInfo.GetInstance (provider);
634                         return new NumberFormatter(null, m_value).FormatDecimal(-1, nfi);
635                 }
636
637                 public string ToString (string format)
638                 {
639                         return ToString (format, null);
640                 }
641
642                 public string ToString (string format, IFormatProvider fp )
643                 {
644                         NumberFormatInfo nfi = NumberFormatInfo.GetInstance( fp );
645                         return NumberFormatter.NumberToString (format, m_value, nfi);
646                 }
647
648                 // =========== IConvertible Methods =========== //
649
650                 public TypeCode GetTypeCode ()
651                 {
652                         return TypeCode.Int32;
653                 }
654                 
655                 bool IConvertible.ToBoolean  (IFormatProvider provider)
656                 {
657                         return System.Convert.ToBoolean (m_value);
658                 }
659                 byte IConvertible.ToByte     (IFormatProvider provider)
660                 {
661                         return System.Convert.ToByte (m_value);
662                 }
663                 char IConvertible.ToChar     (IFormatProvider provider)
664                 {
665                         return System.Convert.ToChar (m_value);
666                 }
667                 DateTime IConvertible.ToDateTime (IFormatProvider provider)
668                 {
669                         return System.Convert.ToDateTime (m_value);
670                 }
671                 decimal IConvertible.ToDecimal  (IFormatProvider provider)
672                 {
673                         return System.Convert.ToDecimal (m_value);
674                 }
675                 double IConvertible.ToDouble   (IFormatProvider provider)
676                 {
677                         return System.Convert.ToDouble (m_value);
678                 }
679                 short IConvertible.ToInt16    (IFormatProvider provider)
680                 {
681                         return System.Convert.ToInt16 (m_value);
682                 }
683                 int IConvertible.ToInt32    (IFormatProvider provider)
684                 {
685                         return m_value;
686                 }
687                 long IConvertible.ToInt64    (IFormatProvider provider)
688                 {
689                         return System.Convert.ToInt64 (m_value);
690                 }
691
692                 sbyte IConvertible.ToSByte    (IFormatProvider provider)
693                 {
694                         return System.Convert.ToSByte (m_value);
695                 }
696                 float IConvertible.ToSingle   (IFormatProvider provider)
697                 {
698                         return System.Convert.ToSingle (m_value);
699                 }
700
701                 object IConvertible.ToType     (Type conversionType, IFormatProvider provider)
702                 {
703                         return System.Convert.ToType (m_value, conversionType, provider);
704                 }
705                 
706                 ushort IConvertible.ToUInt16   (IFormatProvider provider)
707                 {
708                         return System.Convert.ToUInt16 (m_value);
709                 }
710
711                 uint IConvertible.ToUInt32   (IFormatProvider provider)
712                 {
713                         return System.Convert.ToUInt32 (m_value);
714                 }
715                 ulong IConvertible.ToUInt64   (IFormatProvider provider)
716                 {
717                         return System.Convert.ToUInt64 (m_value);
718                 }
719         }
720 }