1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 using System.Diagnostics;
6 using System.Globalization;
7 using System.Runtime.InteropServices;
8 using Internal.Runtime.CompilerServices;
12 // The Parse methods provided by the numeric classes convert a
13 // string to a numeric value. The optional style parameter specifies the
14 // permitted style of the numeric string. It must be a combination of bit flags
15 // from the NumberStyles enumeration. The optional info parameter
16 // specifies the NumberFormatInfo instance to use when parsing the
17 // string. If the info parameter is null or omitted, the numeric
18 // formatting information is obtained from the current culture.
20 // Numeric strings produced by the Format methods using the Currency,
21 // Decimal, Engineering, Fixed point, General, or Number standard formats
22 // (the C, D, E, F, G, and N format specifiers) are guaranteed to be parseable
23 // by the Parse methods if the NumberStyles.Any style is
24 // specified. Note, however, that the Parse methods do not accept
25 // NaNs or Infinities.
27 internal partial class Number
29 private const int Int32Precision = 10;
30 private const int UInt32Precision = Int32Precision;
31 private const int Int64Precision = 19;
32 private const int UInt64Precision = 20;
34 /// <summary>256-element map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit.</summary>
35 internal static readonly int[] s_charToHexLookup =
37 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15
38 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31
39 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47
40 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63
41 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79
42 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95
43 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111
44 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127
45 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143
46 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159
47 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175
48 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191
49 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207
50 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223
51 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239
52 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 255
55 private static unsafe bool NumberToInt32(ref NumberBuffer number, ref int value)
58 if (i > Int32Precision || i < number.precision)
62 char* p = number.GetDigitsPointer();
63 Debug.Assert(p != null);
67 if ((uint)n > (0x7FFFFFFF / 10))
96 private static unsafe bool NumberToInt64(ref NumberBuffer number, ref long value)
99 if (i > Int64Precision || i < number.precision)
103 char* p = number.GetDigitsPointer();
104 Debug.Assert(p != null);
108 if ((ulong)n > (0x7FFFFFFFFFFFFFFF / 10))
137 private static unsafe bool NumberToUInt32(ref NumberBuffer number, ref uint value)
139 int i = number.scale;
140 if (i > UInt32Precision || i < number.precision || number.sign)
144 char* p = number.GetDigitsPointer();
145 Debug.Assert(p != null);
149 if (n > (0xFFFFFFFF / 10))
156 uint newN = n + (uint)(*p++ - '0');
157 // Detect an overflow here...
169 private static unsafe bool NumberToUInt64(ref NumberBuffer number, ref ulong value)
171 int i = number.scale;
172 if (i > UInt64Precision || i < number.precision || number.sign)
176 char* p = number.GetDigitsPointer();
177 Debug.Assert(p != null);
181 if (n > (0xFFFFFFFFFFFFFFFF / 10))
188 ulong newN = n + (ulong)(*p++ - '0');
189 // Detect an overflow here...
201 internal static int ParseInt32(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info)
203 if ((styles & ~NumberStyles.Integer) == 0)
205 // Optimized path for the common case of anything that's allowed for integer style.
206 bool overflow = false;
207 if (!TryParseInt32IntegerStyle(value, styles, info, out int intResult, ref overflow))
209 ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_Int32));
214 if ((styles & NumberStyles.AllowHexSpecifier) != 0)
216 bool overflow = false;
217 if (!TryParseUInt32HexNumberStyle(value, styles, out uint hexResult, ref overflow))
219 ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_Int32));
221 return (int)hexResult;
224 NumberBuffer number = default;
226 StringToNumber(value, styles, ref number, info, false);
227 if (!NumberToInt32(ref number, ref result))
229 ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_Int32));
234 internal static long ParseInt64(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info)
236 if ((styles & ~NumberStyles.Integer) == 0)
238 // Optimized path for the common case of anything that's allowed for integer style.
239 bool overflow = false;
240 if (!TryParseInt64IntegerStyle(value, styles, info, out long intResult, ref overflow))
242 ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_Int64));
247 if ((styles & NumberStyles.AllowHexSpecifier) != 0)
249 bool overflow = false;
250 if (!TryParseUInt64HexNumberStyle(value, styles, out ulong hexResult, ref overflow))
252 ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_Int64));
254 return (long)hexResult;
257 NumberBuffer number = default;
259 StringToNumber(value, styles, ref number, info, false);
260 if (!NumberToInt64(ref number, ref result))
262 ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_Int64));
267 internal static uint ParseUInt32(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info)
271 if ((styles & ~NumberStyles.Integer) == 0)
273 // Optimized path for the common case of anything that's allowed for integer style.
274 bool overflow = false;
275 if (!TryParseUInt32IntegerStyle(value, styles, info, out result, ref overflow))
277 ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_UInt32));
282 if ((styles & NumberStyles.AllowHexSpecifier) != 0)
284 bool overflow = false;
285 if (!TryParseUInt32HexNumberStyle(value, styles, out result, ref overflow))
287 ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_UInt32));
292 NumberBuffer number = default;
293 StringToNumber(value, styles, ref number, info, false);
294 if (!NumberToUInt32(ref number, ref result))
296 ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_UInt32));
301 internal static ulong ParseUInt64(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info)
305 if ((styles & ~NumberStyles.Integer) == 0)
307 // Optimized path for the common case of anything that's allowed for integer style.
308 bool overflow = false;
309 if (!TryParseUInt64IntegerStyle(value, styles, info, out result, ref overflow))
311 ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_UInt64));
316 if ((styles & NumberStyles.AllowHexSpecifier) != 0)
318 bool overflow = false;
319 if (!TryParseUInt64HexNumberStyle(value, styles, out result, ref overflow))
321 ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_UInt64));
326 NumberBuffer number = default;
327 StringToNumber(value, styles, ref number, info, false);
328 if (!NumberToUInt64(ref number, ref result))
330 ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_UInt64));
335 private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info, bool parseDecimal)
337 Debug.Assert(str != null);
338 Debug.Assert(strEnd != null);
339 Debug.Assert(str <= strEnd);
340 Debug.Assert((styles & NumberStyles.AllowHexSpecifier) == 0);
342 const int StateSign = 0x0001;
343 const int StateParens = 0x0002;
344 const int StateDigits = 0x0004;
345 const int StateNonZero = 0x0008;
346 const int StateDecimal = 0x0010;
347 const int StateCurrency = 0x0020;
351 string decSep; // decimal separator from NumberFormatInfo.
352 string groupSep; // group separator from NumberFormatInfo.
353 string currSymbol = null; // currency symbol from NumberFormatInfo.
355 bool parsingCurrency = false;
356 if ((styles & NumberStyles.AllowCurrencySymbol) != 0)
358 currSymbol = info.CurrencySymbol;
360 // The idea here is to match the currency separators and on failure match the number separators to keep the perf of VB's IsNumeric fast.
361 // The values of decSep are setup to use the correct relevant separator (currency in the if part and decimal in the else part).
362 decSep = info.CurrencyDecimalSeparator;
363 groupSep = info.CurrencyGroupSeparator;
364 parsingCurrency = true;
368 decSep = info.NumberDecimalSeparator;
369 groupSep = info.NumberGroupSeparator;
374 char ch = p < strEnd ? *p : '\0';
379 // Eat whitespace unless we've found a sign which isn't followed by a currency symbol.
380 // "-Kr 1231.47" is legal but "- 1231.47" is not.
381 if (!IsWhite(ch) || (styles & NumberStyles.AllowLeadingWhite) == 0 || ((state & StateSign) != 0 && ((state & StateCurrency) == 0 && info.NumberNegativePattern != 2)))
383 if ((((styles & NumberStyles.AllowLeadingSign) != 0) && (state & StateSign) == 0) && ((next = MatchChars(p, strEnd, info.PositiveSign)) != null || ((next = MatchChars(p, strEnd, info.NegativeSign)) != null && (number.sign = true))))
388 else if (ch == '(' && ((styles & NumberStyles.AllowParentheses) != 0) && ((state & StateSign) == 0))
390 state |= StateSign | StateParens;
393 else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null)
395 state |= StateCurrency;
397 // We already found the currency symbol. There should not be more currency symbols. Set
398 // currSymbol to NULL so that we won't search it again in the later code path.
406 ch = ++p < strEnd ? *p : '\0';
414 state |= StateDigits;
416 if (ch != '0' || (state & StateNonZero) != 0)
418 if (digCount < NumberMaxDigits)
420 number.digits[digCount++] = ch;
421 if (ch != '0' || parseDecimal)
426 if ((state & StateDecimal) == 0)
430 state |= StateNonZero;
432 else if ((state & StateDecimal) != 0)
437 else if (((styles & NumberStyles.AllowDecimalPoint) != 0) && ((state & StateDecimal) == 0) && ((next = MatchChars(p, strEnd, decSep)) != null || ((parsingCurrency) && (state & StateCurrency) == 0) && (next = MatchChars(p, strEnd, info.NumberDecimalSeparator)) != null))
439 state |= StateDecimal;
442 else if (((styles & NumberStyles.AllowThousands) != 0) && ((state & StateDigits) != 0) && ((state & StateDecimal) == 0) && ((next = MatchChars(p, strEnd, groupSep)) != null || ((parsingCurrency) && (state & StateCurrency) == 0) && (next = MatchChars(p, strEnd, info.NumberGroupSeparator)) != null))
450 ch = ++p < strEnd ? *p : '\0';
454 number.precision = digEnd;
455 number.digits[digEnd] = '\0';
456 if ((state & StateDigits) != 0)
458 if ((ch == 'E' || ch == 'e') && ((styles & NumberStyles.AllowExponent) != 0))
461 ch = ++p < strEnd ? *p : '\0';
462 if ((next = MatchChars(p, strEnd, info.positiveSign)) != null)
464 ch = (p = next) < strEnd ? *p : '\0';
466 else if ((next = MatchChars(p, strEnd, info.negativeSign)) != null)
468 ch = (p = next) < strEnd ? *p : '\0';
476 exp = exp * 10 + (ch - '0');
477 ch = ++p < strEnd ? *p : '\0';
483 ch = ++p < strEnd ? *p : '\0';
486 } while (IsDigit(ch));
496 ch = p < strEnd ? *p : '\0';
501 if (!IsWhite(ch) || (styles & NumberStyles.AllowTrailingWhite) == 0)
503 if (((styles & NumberStyles.AllowTrailingSign) != 0 && ((state & StateSign) == 0)) && ((next = MatchChars(p, strEnd, info.PositiveSign)) != null || (((next = MatchChars(p, strEnd, info.NegativeSign)) != null) && (number.sign = true))))
508 else if (ch == ')' && ((state & StateParens) != 0))
510 state &= ~StateParens;
512 else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null)
522 ch = ++p < strEnd ? *p : '\0';
524 if ((state & StateParens) == 0)
526 if ((state & StateNonZero) == 0)
532 if ((state & StateDecimal) == 0)
545 internal static bool TryParseInt32(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out int result)
547 if ((styles & ~NumberStyles.Integer) == 0)
549 // Optimized path for the common case of anything that's allowed for integer style.
550 bool overflow = false;
551 return TryParseInt32IntegerStyle(value, styles, info, out result, ref overflow);
556 if ((styles & NumberStyles.AllowHexSpecifier) != 0)
558 bool overflow = false;
559 return TryParseUInt32HexNumberStyle(value, styles, out Unsafe.As<int, uint>(ref result), ref overflow);
562 NumberBuffer number = default;
564 TryStringToNumber(value, styles, ref number, info, false) &&
565 NumberToInt32(ref number, ref result);
568 /// <summary>Parses int limited to styles that make up NumberStyles.Integer.</summary>
569 private static bool TryParseInt32IntegerStyle(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out int result, ref bool failureIsOverflow)
571 Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format");
572 Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false");
574 if ((uint)value.Length < 1) goto FalseExit;
576 bool overflow = false;
581 // Skip past any whitespace at the beginning.
582 if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num))
587 if ((uint)index >= (uint)value.Length) goto FalseExit;
590 while (IsWhite(num));
593 // Parse leading sign.
594 if ((styles & NumberStyles.AllowLeadingSign) != 0)
596 string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign;
598 if (positiveSign == "+" && negativeSign == "-")
604 if ((uint)index >= (uint)value.Length) goto FalseExit;
610 if ((uint)index >= (uint)value.Length) goto FalseExit;
616 value = value.Slice(index);
618 if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign))
620 index += positiveSign.Length;
621 if ((uint)index >= (uint)value.Length) goto FalseExit;
624 else if (!string.IsNullOrEmpty(negativeSign) && value.StartsWith(negativeSign))
627 index += negativeSign.Length;
628 if ((uint)index >= (uint)value.Length) goto FalseExit;
638 // Skip past leading zeros.
644 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
646 } while (num == '0');
647 if (!IsDigit(num)) goto HasTrailingChars;
650 // Parse most digits, up to the potential for overflow, which can't happen until after 9 digits.
651 answer = num - '0'; // first digit
653 for (int i = 0; i < 8; i++) // next 8 digits can't overflow
655 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
657 if (!IsDigit(num)) goto HasTrailingChars;
659 answer = 10 * answer + num - '0';
662 // Potential overflow now processing the 10th digit.
663 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
665 if (!IsDigit(num)) goto HasTrailingChars;
667 if (answer > int.MaxValue / 10)
671 answer = answer * 10 + num - '0';
672 if ((uint)answer > (uint)int.MaxValue + (-1 * sign + 1) / 2)
676 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
678 // At this point, we're either overflowing or hitting a formatting error.
679 // Format errors take precedence for compatibility.
685 if ((uint)index >= (uint)value.Length)
686 goto DoneAtEndButPotentialOverflow;
689 goto HasTrailingChars;
692 FalseExit: // parsing failed
696 DoneAtEndButPotentialOverflow:
699 failureIsOverflow = true;
702 result = answer * sign;
705 HasTrailingChars: // we've successfully parsed, but there are still remaining characters in the span
706 // Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail.
709 if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit;
710 for (index++; index < value.Length; index++)
712 if (!IsWhite(value[index])) break;
714 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
717 if (!TrailingZeros(value, index)) goto FalseExit;
719 goto DoneAtEndButPotentialOverflow;
722 /// <summary>Parses long inputs limited to styles that make up NumberStyles.Integer.</summary>
723 private static bool TryParseInt64IntegerStyle(
724 ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out long result, ref bool failureIsOverflow)
726 Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format");
727 Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false");
729 if ((uint)value.Length < 1) goto FalseExit;
731 bool overflow = false;
736 // Skip past any whitespace at the beginning.
737 if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num))
742 if ((uint)index >= (uint)value.Length) goto FalseExit;
745 while (IsWhite(num));
748 // Parse leading sign.
749 if ((styles & NumberStyles.AllowLeadingSign) != 0)
751 string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign;
753 if (positiveSign == "+" && negativeSign == "-")
759 if ((uint)index >= (uint)value.Length) goto FalseExit;
765 if ((uint)index >= (uint)value.Length) goto FalseExit;
771 value = value.Slice(index);
773 if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign))
775 index += positiveSign.Length;
776 if ((uint)index >= (uint)value.Length) goto FalseExit;
779 else if (!string.IsNullOrEmpty(negativeSign) && value.StartsWith(negativeSign))
782 index += negativeSign.Length;
783 if ((uint)index >= (uint)value.Length) goto FalseExit;
793 // Skip past leading zeros.
799 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
801 } while (num == '0');
802 if (!IsDigit(num)) goto HasTrailingChars;
805 // Parse most digits, up to the potential for overflow, which can't happen until after 18 digits.
806 answer = num - '0'; // first digit
808 for (int i = 0; i < 17; i++) // next 17 digits can't overflow
810 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
812 if (!IsDigit(num)) goto HasTrailingChars;
814 answer = 10 * answer + num - '0';
817 // Potential overflow now processing the 19th digit.
818 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
820 if (!IsDigit(num)) goto HasTrailingChars;
822 if (answer > long.MaxValue / 10)
826 answer = answer * 10 + num - '0';
827 if ((ulong)answer > (ulong)long.MaxValue + (ulong)((-1 * sign + 1) / 2)) // + sign => 0, - sign => 1
831 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
833 // At this point, we're either overflowing or hitting a formatting error.
834 // Format errors take precedence for compatibility.
840 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
843 goto HasTrailingChars;
846 FalseExit: // parsing failed
850 DoneAtEndButPotentialOverflow:
853 failureIsOverflow = true;
856 result = answer * sign;
859 HasTrailingChars: // we've successfully parsed, but there are still remaining characters in the span
860 // Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail.
863 if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit;
864 for (index++; index < value.Length; index++)
866 if (!IsWhite(value[index])) break;
868 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
871 if (!TrailingZeros(value, index)) goto FalseExit;
873 goto DoneAtEndButPotentialOverflow;
876 internal static bool TryParseInt64(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out long result)
878 if ((styles & ~NumberStyles.Integer) == 0)
880 // Optimized path for the common case of anything that's allowed for integer style.
881 bool overflow = false;
882 return TryParseInt64IntegerStyle(value, styles, info, out result, ref overflow);
887 if ((styles & NumberStyles.AllowHexSpecifier) != 0)
889 bool overflow = false;
890 return TryParseUInt64HexNumberStyle(value, styles, out Unsafe.As<long, ulong>(ref result), ref overflow);
893 NumberBuffer number = default;
895 TryStringToNumber(value, styles, ref number, info, false) &&
896 NumberToInt64(ref number, ref result);
899 internal static bool TryParseUInt32(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out uint result)
901 if ((styles & ~NumberStyles.Integer) == 0)
903 // Optimized path for the common case of anything that's allowed for integer style.
904 bool overflow = false;
905 return TryParseUInt32IntegerStyle(value, styles, info, out result, ref overflow);
908 if ((styles & NumberStyles.AllowHexSpecifier) != 0)
910 bool overflow = false;
911 return TryParseUInt32HexNumberStyle(value, styles, out result, ref overflow);
914 NumberBuffer number = default;
917 TryStringToNumber(value, styles, ref number, info, false) &&
918 NumberToUInt32(ref number, ref result);
921 /// <summary>Parses uint limited to styles that make up NumberStyles.Integer.</summary>
922 private static bool TryParseUInt32IntegerStyle(
923 ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out uint result, ref bool failureIsOverflow)
925 Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format");
926 Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false");
928 if ((uint)value.Length < 1) goto FalseExit;
930 bool overflow = false;
931 bool hasNegativeSign = false;
935 // Skip past any whitespace at the beginning.
936 if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num))
941 if ((uint)index >= (uint)value.Length) goto FalseExit;
944 while (IsWhite(num));
947 // Parse leading sign.
948 if ((styles & NumberStyles.AllowLeadingSign) != 0)
950 string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign;
952 if (positiveSign == "+" && negativeSign == "-")
957 if ((uint)index >= (uint)value.Length) goto FalseExit;
962 hasNegativeSign = true;
964 if ((uint)index >= (uint)value.Length) goto FalseExit;
970 value = value.Slice(index);
972 if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign))
974 index += positiveSign.Length;
975 if ((uint)index >= (uint)value.Length) goto FalseExit;
978 else if (!string.IsNullOrEmpty(negativeSign) && value.StartsWith(negativeSign))
980 hasNegativeSign = true;
981 index += negativeSign.Length;
982 if ((uint)index >= (uint)value.Length) goto FalseExit;
992 // Skip past leading zeros.
998 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
1000 } while (num == '0');
1001 if (!IsDigit(num)) goto HasTrailingChars;
1004 // Parse most digits, up to the potential for overflow, which can't happen until after 9 digits.
1005 answer = num - '0'; // first digit
1007 for (int i = 0; i < 8; i++) // next 8 digits can't overflow
1009 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
1011 if (!IsDigit(num)) goto HasTrailingChars;
1013 answer = 10 * answer + num - '0';
1016 // Potential overflow now processing the 10th digit.
1017 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
1019 if (!IsDigit(num)) goto HasTrailingChars;
1021 if ((uint)answer > uint.MaxValue / 10 || ((uint)answer == uint.MaxValue / 10 && num > '5'))
1025 answer = answer * 10 + num - '0';
1026 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
1028 // At this point, we're either overflowing or hitting a formatting error.
1029 // Format errors take precedence for compatibility.
1031 while (IsDigit(num))
1035 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
1038 goto HasTrailingChars;
1041 FalseExit: // parsing failed
1045 DoneAtEndButPotentialOverflow:
1046 if (overflow || (hasNegativeSign && answer != 0))
1048 failureIsOverflow = true;
1051 result = (uint)answer;
1054 HasTrailingChars: // we've successfully parsed, but there are still remaining characters in the span
1055 // Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail.
1058 if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit;
1059 for (index++; index < value.Length; index++)
1061 if (!IsWhite(value[index])) break;
1063 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
1066 if (!TrailingZeros(value, index)) goto FalseExit;
1068 goto DoneAtEndButPotentialOverflow;
1071 /// <summary>Parses uint limited to styles that make up NumberStyles.HexNumber.</summary>
1072 private static bool TryParseUInt32HexNumberStyle(
1073 ReadOnlySpan<char> value, NumberStyles styles, out uint result, ref bool failureIsOverflow)
1075 Debug.Assert((styles & ~NumberStyles.HexNumber) == 0, "Only handles subsets of HexNumber format");
1076 Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false");
1078 if ((uint)value.Length < 1) goto FalseExit;
1080 bool overflow = false;
1085 // Skip past any whitespace at the beginning.
1086 if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num))
1091 if ((uint)index >= (uint)value.Length) goto FalseExit;
1094 while (IsWhite(num));
1098 int[] charToHexLookup = s_charToHexLookup;
1100 if ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF)
1102 // Skip past leading zeros.
1108 if ((uint)index >= (uint)value.Length) goto DoneAtEnd;
1110 } while (num == '0');
1111 if ((uint)num >= (uint)charToHexLookup.Length || charToHexLookup[num] == 0xFF) goto HasTrailingChars;
1114 // Parse up through 8 digits, as no overflow is possible
1115 answer = charToHexLookup[num]; // first digit
1117 for (int i = 0; i < 7; i++) // next 7 digits can't overflow
1119 if ((uint)index >= (uint)value.Length) goto DoneAtEnd;
1121 if ((uint)num >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[num]) == 0xFF) goto HasTrailingChars;
1123 answer = 16 * answer + numValue;
1126 // If there's another digit, it's an overflow.
1127 if ((uint)index >= (uint)value.Length) goto DoneAtEnd;
1129 if ((uint)num >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[num]) == 0xFF) goto HasTrailingChars;
1132 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
1134 // At this point, we're either overflowing or hitting a formatting error.
1135 // Format errors take precedence for compatibility. Read through any remaining digits.
1137 while ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF)
1140 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
1143 goto HasTrailingChars;
1146 FalseExit: // parsing failed
1150 DoneAtEndButPotentialOverflow:
1153 failureIsOverflow = true;
1157 result = (uint)answer;
1160 HasTrailingChars: // we've successfully parsed, but there are still remaining characters in the span
1161 // Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail.
1164 if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit;
1165 for (index++; index < value.Length; index++)
1167 if (!IsWhite(value[index])) break;
1169 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
1172 if (!TrailingZeros(value, index)) goto FalseExit;
1174 goto DoneAtEndButPotentialOverflow;
1177 internal static bool TryParseUInt64(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out ulong result)
1179 if ((styles & ~NumberStyles.Integer) == 0)
1181 // Optimized path for the common case of anything that's allowed for integer style.
1182 bool overflow = false;
1183 return TryParseUInt64IntegerStyle(value, styles, info, out result, ref overflow);
1186 if ((styles & NumberStyles.AllowHexSpecifier) != 0)
1188 bool overflow = false;
1189 return TryParseUInt64HexNumberStyle(value, styles, out result, ref overflow);
1192 NumberBuffer number = default;
1195 TryStringToNumber(value, styles, ref number, info, false) &&
1196 NumberToUInt64(ref number, ref result);
1199 /// <summary>Parses ulong limited to styles that make up NumberStyles.Integer.</summary>
1200 private static bool TryParseUInt64IntegerStyle(
1201 ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out ulong result, ref bool failureIsOverflow)
1203 Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format");
1204 Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false");
1206 if ((uint)value.Length < 1) goto FalseExit;
1208 bool overflow = false;
1209 bool hasNegativeSign = false;
1213 // Skip past any whitespace at the beginning.
1214 if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num))
1219 if ((uint)index >= (uint)value.Length) goto FalseExit;
1222 while (IsWhite(num));
1225 // Parse leading sign.
1226 if ((styles & NumberStyles.AllowLeadingSign) != 0)
1228 string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign;
1230 if (positiveSign == "+" && negativeSign == "-")
1235 if ((uint)index >= (uint)value.Length) goto FalseExit;
1238 else if (num == '-')
1240 hasNegativeSign = true;
1242 if ((uint)index >= (uint)value.Length) goto FalseExit;
1248 value = value.Slice(index);
1250 if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign))
1252 index += positiveSign.Length;
1253 if ((uint)index >= (uint)value.Length) goto FalseExit;
1256 else if (!string.IsNullOrEmpty(negativeSign) && value.StartsWith(negativeSign))
1258 hasNegativeSign = true;
1259 index += negativeSign.Length;
1260 if ((uint)index >= (uint)value.Length) goto FalseExit;
1270 // Skip past leading zeros.
1276 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
1278 } while (num == '0');
1279 if (!IsDigit(num)) goto HasTrailingChars;
1282 // Parse most digits, up to the potential for overflow, which can't happen until after 19 digits.
1283 answer = num - '0'; // first digit
1285 for (int i = 0; i < 18; i++) // next 18 digits can't overflow
1287 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
1289 if (!IsDigit(num)) goto HasTrailingChars;
1291 answer = 10 * answer + num - '0';
1294 // Potential overflow now processing the 20th digit.
1295 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
1297 if (!IsDigit(num)) goto HasTrailingChars;
1299 if ((ulong)answer > ulong.MaxValue / 10 || ((ulong)answer == ulong.MaxValue / 10 && num > '5'))
1303 answer = answer * 10 + num - '0';
1304 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
1306 // At this point, we're either overflowing or hitting a formatting error.
1307 // Format errors take precedence for compatibility.
1309 while (IsDigit(num))
1313 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
1316 goto HasTrailingChars;
1319 FalseExit: // parsing failed
1323 DoneAtEndButPotentialOverflow:
1324 if (overflow || (hasNegativeSign && answer != 0))
1326 failureIsOverflow = true;
1329 result = (ulong)answer;
1332 HasTrailingChars: // we've successfully parsed, but there are still remaining characters in the span
1333 // Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail.
1336 if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit;
1337 for (index++; index < value.Length; index++)
1339 if (!IsWhite(value[index])) break;
1341 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
1344 if (!TrailingZeros(value, index)) goto FalseExit;
1346 goto DoneAtEndButPotentialOverflow;
1349 /// <summary>Parses ulong limited to styles that make up NumberStyles.HexNumber.</summary>
1350 private static bool TryParseUInt64HexNumberStyle(
1351 ReadOnlySpan<char> value, NumberStyles styles, out ulong result, ref bool failureIsOverflow)
1353 Debug.Assert((styles & ~NumberStyles.HexNumber) == 0, "Only handles subsets of HexNumber format");
1354 Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false");
1356 if ((uint)value.Length < 1) goto FalseExit;
1358 bool overflow = false;
1363 // Skip past any whitespace at the beginning.
1364 if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num))
1369 if ((uint)index >= (uint)value.Length) goto FalseExit;
1372 while (IsWhite(num));
1376 int[] charToHexLookup = s_charToHexLookup;
1378 if ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF)
1380 // Skip past leading zeros.
1386 if ((uint)index >= (uint)value.Length) goto DoneAtEnd;
1388 } while (num == '0');
1389 if ((uint)num >= (uint)charToHexLookup.Length || charToHexLookup[num] == 0xFF) goto HasTrailingChars;
1392 // Parse up through 16 digits, as no overflow is possible
1393 answer = charToHexLookup[num]; // first digit
1395 for (int i = 0; i < 15; i++) // next 15 digits can't overflow
1397 if ((uint)index >= (uint)value.Length) goto DoneAtEnd;
1399 if ((uint)num >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[num]) == 0xFF) goto HasTrailingChars;
1401 answer = 16 * answer + numValue;
1404 // If there's another digit, it's an overflow.
1405 if ((uint)index >= (uint)value.Length) goto DoneAtEnd;
1407 if ((uint)num >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[num]) == 0xFF) goto HasTrailingChars;
1410 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
1412 // At this point, we're either overflowing or hitting a formatting error.
1413 // Format errors take precedence for compatibility. Read through any remaining digits.
1415 while ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF)
1418 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
1421 goto HasTrailingChars;
1424 FalseExit: // parsing failed
1428 DoneAtEndButPotentialOverflow:
1431 failureIsOverflow = true;
1435 result = (ulong)answer;
1438 HasTrailingChars: // we've successfully parsed, but there are still remaining characters in the span
1439 // Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail.
1442 if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit;
1443 for (index++; index < value.Length; index++)
1445 if (!IsWhite(value[index])) break;
1447 if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
1450 if (!TrailingZeros(value, index)) goto FalseExit;
1452 goto DoneAtEndButPotentialOverflow;
1455 internal static decimal ParseDecimal(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info)
1457 NumberBuffer number = default;
1460 StringToNumber(value, styles, ref number, info, true);
1462 if (!NumberBufferToDecimal(ref number, ref result))
1464 ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_Decimal));
1469 private static unsafe bool NumberBufferToDecimal(ref NumberBuffer number, ref decimal value)
1471 char* p = number.GetDigitsPointer();
1472 int e = number.scale;
1473 bool sign = number.sign;
1477 // To avoid risking an app-compat issue with pre 4.5 (where some app was illegally using Reflection to examine the internal scale bits), we'll only force
1478 // the scale to 0 if the scale was previously positive (previously, such cases were unparsable to a bug.)
1479 value = new decimal(0, 0, 0, sign, (byte)Math.Clamp(-e, 0, 28));
1483 if (e > DecimalPrecision)
1493 if (low64 >= ulong.MaxValue / 10)
1501 if (low64 >= ulong.MaxValue / 10)
1509 while ((e > 0 || (c != 0 && e > -28)) &&
1510 (high < uint.MaxValue / 10 || (high == uint.MaxValue / 10 && (low64 < 0x99999999_99999999 || (low64 == 0x99999999_99999999 && c <= '5')))))
1513 ulong tmpLow = (uint)low64 * 10UL;
1514 ulong tmp64 = (uint)(low64 >> 32) * 10UL + (tmpLow >> 32);
1515 low64 = (uint)tmpLow + (tmp64 << 32);
1516 high = (uint)(tmp64 >> 32) + high * 10;
1531 // If the next digit is 5, round up if the number is odd or any following digit is non-zero
1532 if (c == '5' && (low64 & 1) == 0)
1535 int count = 20; // Look at the next 20 digits to check to round
1536 while (c == '0' && count != 0)
1541 if (c == 0 || count == 0)
1542 goto NoRounding;// Do nothing
1545 if (++low64 == 0 && ++high == 0)
1547 low64 = 0x99999999_9999999A;
1548 high = uint.MaxValue / 10;
1557 if (e <= -DecimalPrecision)
1559 // Parsing a large scale zero can give you more precision than fits in the decimal.
1560 // This should only happen for actual zeros or very small numbers that round to zero.
1561 value = new decimal(0, 0, 0, sign, DecimalPrecision - 1);
1565 value = new decimal((int)low64, (int)(low64 >> 32), (int)high, sign, (byte)-e);
1570 internal static double ParseDouble(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info)
1572 NumberBuffer number = default;
1575 if (!TryStringToNumber(value, styles, ref number, info, false))
1577 //If we failed TryStringToNumber, it may be from one of our special strings.
1578 //Check the three with which we're concerned and rethrow if it's not one of
1580 ReadOnlySpan<char> sTrim = value.Trim();
1581 if (sTrim.EqualsOrdinal(info.PositiveInfinitySymbol))
1583 return double.PositiveInfinity;
1585 if (sTrim.EqualsOrdinal(info.NegativeInfinitySymbol))
1587 return double.NegativeInfinity;
1589 if (sTrim.EqualsOrdinal(info.NaNSymbol))
1593 ThrowOverflowOrFormatException(overflow: false, null);
1596 if (!NumberBufferToDouble(ref number, ref d))
1598 ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_Double));
1604 internal static float ParseSingle(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info)
1606 NumberBuffer number = default;
1609 if (!TryStringToNumber(value, styles, ref number, info, false))
1611 //If we failed TryStringToNumber, it may be from one of our special strings.
1612 //Check the three with which we're concerned and rethrow if it's not one of
1614 ReadOnlySpan<char> sTrim = value.Trim();
1615 if (sTrim.EqualsOrdinal(info.PositiveInfinitySymbol))
1617 return float.PositiveInfinity;
1619 if (sTrim.EqualsOrdinal(info.NegativeInfinitySymbol))
1621 return float.NegativeInfinity;
1623 if (sTrim.EqualsOrdinal(info.NaNSymbol))
1627 ThrowOverflowOrFormatException(overflow: false, null);
1630 if (!NumberBufferToDouble(ref number, ref d))
1632 ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_Single));
1634 float castSingle = (float)d;
1635 if (float.IsInfinity(castSingle))
1637 ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_Single));
1642 internal static bool TryParseDecimal(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out decimal result)
1644 NumberBuffer number = default;
1647 if (!TryStringToNumber(value, styles, ref number, info, true))
1652 if (!NumberBufferToDecimal(ref number, ref result))
1659 internal static bool TryParseDouble(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out double result)
1661 NumberBuffer number = default;
1664 if (!TryStringToNumber(value, styles, ref number, info, false))
1668 if (!NumberBufferToDouble(ref number, ref result))
1675 internal static bool TryParseSingle(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out float result)
1677 NumberBuffer number = default;
1681 if (!TryStringToNumber(value, styles, ref number, info, false))
1685 if (!NumberBufferToDouble(ref number, ref d))
1689 float castSingle = (float)d;
1690 if (float.IsInfinity(castSingle))
1695 result = castSingle;
1699 private static unsafe void StringToNumber(ReadOnlySpan<char> value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info, bool parseDecimal)
1701 Debug.Assert(info != null);
1702 fixed (char* stringPointer = &MemoryMarshal.GetReference(value))
1704 char* p = stringPointer;
1705 if (!ParseNumber(ref p, p + value.Length, styles, ref number, info, parseDecimal)
1706 || (p - stringPointer < value.Length && !TrailingZeros(value, (int)(p - stringPointer))))
1708 ThrowOverflowOrFormatException(overflow: false, null);
1713 internal static unsafe bool TryStringToNumber(ReadOnlySpan<char> value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info, bool parseDecimal)
1715 Debug.Assert(info != null);
1716 fixed (char* stringPointer = &MemoryMarshal.GetReference(value))
1718 char* p = stringPointer;
1719 if (!ParseNumber(ref p, p + value.Length, styles, ref number, info, parseDecimal)
1720 || (p - stringPointer < value.Length && !TrailingZeros(value, (int)(p - stringPointer))))
1729 private static bool TrailingZeros(ReadOnlySpan<char> value, int index)
1731 // For compatibility, we need to allow trailing zeros at the end of a number string
1732 for (int i = index; i < value.Length; i++)
1734 if (value[i] != '\0')
1743 private static unsafe char* MatchChars(char* p, char* pEnd, string value)
1745 Debug.Assert(p != null && pEnd != null && p <= pEnd && value != null);
1746 fixed (char* stringPointer = value)
1748 char* str = stringPointer;
1751 // We only hurt the failure case
1752 // This fix is for French or Kazakh cultures. Since a user cannot type 0xA0 as a
1753 // space character we use 0x20 space character instead to mean the same.
1756 char cp = p < pEnd ? *p : '\0';
1757 if (cp != *str && !(*str == '\u00a0' && cp == '\u0020'))
1763 if (*str == '\0') return p;
1771 private static bool IsWhite(int ch) => ch == 0x20 || ((uint)(ch - 0x09) <= (0x0D - 0x09));
1773 private static bool IsDigit(int ch) => ((uint)ch - '0') <= 9;
1775 private static void ThrowOverflowOrFormatException(bool overflow, string overflowResourceKey)
1778 new OverflowException(SR.GetResourceString(overflowResourceKey)) :
1779 (Exception)new FormatException(SR.Format_InvalidString);
1782 private static bool NumberBufferToDouble(ref NumberBuffer number, ref double value)
1784 double d = NumberToDouble(ref number);
1785 if (!double.IsFinite(d))
1793 // normalize -0.0 to 0.0