// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Internal.Runtime.CompilerServices; namespace System { // The Parse methods provided by the numeric classes convert a // string to a numeric value. The optional style parameter specifies the // permitted style of the numeric string. It must be a combination of bit flags // from the NumberStyles enumeration. The optional info parameter // specifies the NumberFormatInfo instance to use when parsing the // string. If the info parameter is null or omitted, the numeric // formatting information is obtained from the current culture. // // Numeric strings produced by the Format methods using the Currency, // Decimal, Engineering, Fixed point, General, or Number standard formats // (the C, D, E, F, G, and N format specifiers) are guaranteed to be parseable // by the Parse methods if the NumberStyles.Any style is // specified. Note, however, that the Parse methods do not accept // NaNs or Infinities. internal partial class Number { private const int Int32Precision = 10; private const int UInt32Precision = Int32Precision; private const int Int64Precision = 19; private const int UInt64Precision = 20; private const int DoubleMaxExponent = 309; private const int DoubleMinExponent = -324; private const int FloatingPointMaxExponent = DoubleMaxExponent; private const int FloatingPointMinExponent = DoubleMinExponent; private const int SingleMaxExponent = 39; private const int SingleMinExponent = -45; /// Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit. internal static ReadOnlySpan CharToHexLookup => new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf // 102 }; private static unsafe bool TryNumberToInt32(ref NumberBuffer number, ref int value) { number.CheckConsistency(); int i = number.Scale; if (i > Int32Precision || i < number.DigitsCount) { return false; } byte* p = number.GetDigitsPointer(); Debug.Assert(p != null); int n = 0; while (--i >= 0) { if ((uint)n > (0x7FFFFFFF / 10)) { return false; } n *= 10; if (*p != '\0') { n += (*p++ - '0'); } } if (number.IsNegative) { n = -n; if (n > 0) { return false; } } else { if (n < 0) { return false; } } value = n; return true; } private static unsafe bool TryNumberToInt64(ref NumberBuffer number, ref long value) { number.CheckConsistency(); int i = number.Scale; if (i > Int64Precision || i < number.DigitsCount) { return false; } byte* p = number.GetDigitsPointer(); Debug.Assert(p != null); long n = 0; while (--i >= 0) { if ((ulong)n > (0x7FFFFFFFFFFFFFFF / 10)) { return false; } n *= 10; if (*p != '\0') { n += (*p++ - '0'); } } if (number.IsNegative) { n = -n; if (n > 0) { return false; } } else { if (n < 0) { return false; } } value = n; return true; } private static unsafe bool TryNumberToUInt32(ref NumberBuffer number, ref uint value) { number.CheckConsistency(); int i = number.Scale; if (i > UInt32Precision || i < number.DigitsCount || number.IsNegative) { return false; } byte* p = number.GetDigitsPointer(); Debug.Assert(p != null); uint n = 0; while (--i >= 0) { if (n > (0xFFFFFFFF / 10)) { return false; } n *= 10; if (*p != '\0') { uint newN = n + (uint)(*p++ - '0'); // Detect an overflow here... if (newN < n) { return false; } n = newN; } } value = n; return true; } private static unsafe bool TryNumberToUInt64(ref NumberBuffer number, ref ulong value) { number.CheckConsistency(); int i = number.Scale; if (i > UInt64Precision || i < number.DigitsCount || number.IsNegative) { return false; } byte* p = number.GetDigitsPointer(); Debug.Assert(p != null); ulong n = 0; while (--i >= 0) { if (n > (0xFFFFFFFFFFFFFFFF / 10)) { return false; } n *= 10; if (*p != '\0') { ulong newN = n + (ulong)(*p++ - '0'); // Detect an overflow here... if (newN < n) { return false; } n = newN; } } value = n; return true; } internal static int ParseInt32(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { ParsingStatus status = TryParseInt32(value, styles, info, out int result); if (status != ParsingStatus.OK) { ThrowOverflowOrFormatException(status, TypeCode.Int32); } return result; } internal static long ParseInt64(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { ParsingStatus status = TryParseInt64(value, styles, info, out long result); if (status != ParsingStatus.OK) { ThrowOverflowOrFormatException(status, TypeCode.Int64); } return result; } internal static uint ParseUInt32(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { ParsingStatus status = TryParseUInt32(value, styles, info, out uint result); if (status != ParsingStatus.OK) { ThrowOverflowOrFormatException(status, TypeCode.UInt32); } return result; } internal static ulong ParseUInt64(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { ParsingStatus status = TryParseUInt64(value, styles, info, out ulong result); if (status != ParsingStatus.OK) { ThrowOverflowOrFormatException(status, TypeCode.UInt64); } return result; } private static unsafe bool TryParseNumber(ref char* str, char* strEnd, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info) { Debug.Assert(str != null); Debug.Assert(strEnd != null); Debug.Assert(str <= strEnd); Debug.Assert((styles & NumberStyles.AllowHexSpecifier) == 0); const int StateSign = 0x0001; const int StateParens = 0x0002; const int StateDigits = 0x0004; const int StateNonZero = 0x0008; const int StateDecimal = 0x0010; const int StateCurrency = 0x0020; Debug.Assert(number.DigitsCount == 0); Debug.Assert(number.Scale == 0); Debug.Assert(number.IsNegative == false); Debug.Assert(number.HasNonZeroTail == false); number.CheckConsistency(); string decSep; // decimal separator from NumberFormatInfo. string groupSep; // group separator from NumberFormatInfo. string? currSymbol = null; // currency symbol from NumberFormatInfo. bool parsingCurrency = false; if ((styles & NumberStyles.AllowCurrencySymbol) != 0) { currSymbol = info.CurrencySymbol; // 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. // The values of decSep are setup to use the correct relevant separator (currency in the if part and decimal in the else part). decSep = info.CurrencyDecimalSeparator; groupSep = info.CurrencyGroupSeparator; parsingCurrency = true; } else { decSep = info.NumberDecimalSeparator; groupSep = info.NumberGroupSeparator; } int state = 0; char* p = str; char ch = p < strEnd ? *p : '\0'; char* next; while (true) { // Eat whitespace unless we've found a sign which isn't followed by a currency symbol. // "-Kr 1231.47" is legal but "- 1231.47" is not. if (!IsWhite(ch) || (styles & NumberStyles.AllowLeadingWhite) == 0 || ((state & StateSign) != 0 && ((state & StateCurrency) == 0 && info.NumberNegativePattern != 2))) { if ((((styles & NumberStyles.AllowLeadingSign) != 0) && (state & StateSign) == 0) && ((next = MatchChars(p, strEnd, info.PositiveSign)) != null || ((next = MatchChars(p, strEnd, info.NegativeSign)) != null && (number.IsNegative = true)))) { state |= StateSign; p = next - 1; } else if (ch == '(' && ((styles & NumberStyles.AllowParentheses) != 0) && ((state & StateSign) == 0)) { state |= StateSign | StateParens; number.IsNegative = true; } else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null) { state |= StateCurrency; currSymbol = null; // We already found the currency symbol. There should not be more currency symbols. Set // currSymbol to NULL so that we won't search it again in the later code path. p = next - 1; } else { break; } } ch = ++p < strEnd ? *p : '\0'; } int digCount = 0; int digEnd = 0; int maxDigCount = number.Digits.Length - 1; while (true) { if (IsDigit(ch)) { state |= StateDigits; if (ch != '0' || (state & StateNonZero) != 0) { if (digCount < maxDigCount) { number.Digits[digCount++] = (byte)(ch); if ((ch != '0') || (number.Kind != NumberBufferKind.Integer)) { digEnd = digCount; } } else if (ch != '0') { // For decimal and binary floating-point numbers, we only // need to store digits up to maxDigCount. However, we still // need to keep track of whether any additional digits past // maxDigCount were non-zero, as that can impact rounding // for an input that falls evenly between two representable // results. number.HasNonZeroTail = true; } if ((state & StateDecimal) == 0) { number.Scale++; } state |= StateNonZero; } else if ((state & StateDecimal) != 0) { number.Scale--; } } 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)) { state |= StateDecimal; p = next - 1; } 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)) { p = next - 1; } else { break; } ch = ++p < strEnd ? *p : '\0'; } bool negExp = false; number.DigitsCount = digEnd; number.Digits[digEnd] = (byte)('\0'); if ((state & StateDigits) != 0) { if ((ch == 'E' || ch == 'e') && ((styles & NumberStyles.AllowExponent) != 0)) { char* temp = p; ch = ++p < strEnd ? *p : '\0'; if ((next = MatchChars(p, strEnd, info._positiveSign)) != null) { ch = (p = next) < strEnd ? *p : '\0'; } else if ((next = MatchChars(p, strEnd, info._negativeSign)) != null) { ch = (p = next) < strEnd ? *p : '\0'; negExp = true; } if (IsDigit(ch)) { int exp = 0; do { exp = exp * 10 + (ch - '0'); ch = ++p < strEnd ? *p : '\0'; if (exp > 1000) { exp = 9999; while (IsDigit(ch)) { ch = ++p < strEnd ? *p : '\0'; } } } while (IsDigit(ch)); if (negExp) { exp = -exp; } number.Scale += exp; } else { p = temp; ch = p < strEnd ? *p : '\0'; } } while (true) { if (!IsWhite(ch) || (styles & NumberStyles.AllowTrailingWhite) == 0) { if (((styles & NumberStyles.AllowTrailingSign) != 0 && ((state & StateSign) == 0)) && ((next = MatchChars(p, strEnd, info.PositiveSign)) != null || (((next = MatchChars(p, strEnd, info.NegativeSign)) != null) && (number.IsNegative = true)))) { state |= StateSign; p = next - 1; } else if (ch == ')' && ((state & StateParens) != 0)) { state &= ~StateParens; } else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null) { currSymbol = null; p = next - 1; } else { break; } } ch = ++p < strEnd ? *p : '\0'; } if ((state & StateParens) == 0) { if ((state & StateNonZero) == 0) { if (number.Kind != NumberBufferKind.Decimal) { number.Scale = 0; } if ((number.Kind == NumberBufferKind.Integer) && (state & StateDecimal) == 0) { number.IsNegative = false; } } str = p; return true; } } str = p; return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ParsingStatus TryParseInt32(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out int result) { if ((styles & ~NumberStyles.Integer) == 0) { // Optimized path for the common case of anything that's allowed for integer style. return TryParseInt32IntegerStyle(value, styles, info, out result); } if ((styles & NumberStyles.AllowHexSpecifier) != 0) { result = 0; return TryParseUInt32HexNumberStyle(value, styles, out Unsafe.As(ref result)); } return TryParseInt32Number(value, styles, info, out result); } private static unsafe ParsingStatus TryParseInt32Number(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out int result) { result = 0; byte* pDigits = stackalloc byte[Int32NumberBufferLength]; NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, Int32NumberBufferLength); if (!TryStringToNumber(value, styles, ref number, info)) { return ParsingStatus.Failed; } if (!TryNumberToInt32(ref number, ref result)) { return ParsingStatus.Overflow; } return ParsingStatus.OK; } /// Parses int limited to styles that make up NumberStyles.Integer. internal static ParsingStatus TryParseInt32IntegerStyle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out int result) { Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format"); if (value.IsEmpty) goto FalseExit; int index = 0; int num = value[0]; // Skip past any whitespace at the beginning. if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num)) { do { index++; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } while (IsWhite(num)); } // Parse leading sign. int sign = 1; if ((styles & NumberStyles.AllowLeadingSign) != 0) { if (info.HasInvariantNumberSigns) { if (num == '-') { sign = -1; index++; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } else if (num == '+') { index++; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } } else { value = value.Slice(index); index = 0; string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign; if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign)) { index += positiveSign.Length; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } else if (!string.IsNullOrEmpty(negativeSign) && value.StartsWith(negativeSign)) { sign = -1; index += negativeSign.Length; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } } } bool overflow = false; int answer = 0; if (IsDigit(num)) { // Skip past leading zeros. if (num == '0') { do { index++; if ((uint)index >= (uint)value.Length) goto DoneAtEnd; num = value[index]; } while (num == '0'); if (!IsDigit(num)) goto HasTrailingChars; } // Parse most digits, up to the potential for overflow, which can't happen until after 9 digits. answer = num - '0'; // first digit index++; for (int i = 0; i < 8; i++) // next 8 digits can't overflow { if ((uint)index >= (uint)value.Length) goto DoneAtEnd; num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; index++; answer = 10 * answer + num - '0'; } if ((uint)index >= (uint)value.Length) goto DoneAtEnd; num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; index++; // Potential overflow now processing the 10th digit. overflow = answer > int.MaxValue / 10; answer = answer * 10 + num - '0'; overflow |= (uint)answer > int.MaxValue + (((uint)sign) >> 31); if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; // At this point, we're either overflowing or hitting a formatting error. // Format errors take precedence for compatibility. num = value[index]; while (IsDigit(num)) { overflow = true; index++; if ((uint)index >= (uint)value.Length) goto OverflowExit; num = value[index]; } goto HasTrailingChars; } goto FalseExit; DoneAtEndButPotentialOverflow: if (overflow) { goto OverflowExit; } DoneAtEnd: result = answer * sign; ParsingStatus status = ParsingStatus.OK; Exit: return status; FalseExit: // parsing failed result = 0; status = ParsingStatus.Failed; goto Exit; OverflowExit: result = 0; status = ParsingStatus.Overflow; goto Exit; HasTrailingChars: // we've successfully parsed, but there are still remaining characters in the span // Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail. if (IsWhite(num)) { if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit; for (index++; index < value.Length; index++) { if (!IsWhite(value[index])) break; } if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; } if (!TrailingZeros(value, index)) goto FalseExit; goto DoneAtEndButPotentialOverflow; } /// Parses long inputs limited to styles that make up NumberStyles.Integer. internal static ParsingStatus TryParseInt64IntegerStyle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out long result) { Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format"); if (value.IsEmpty) goto FalseExit; int index = 0; int num = value[0]; // Skip past any whitespace at the beginning. if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num)) { do { index++; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } while (IsWhite(num)); } // Parse leading sign. int sign = 1; if ((styles & NumberStyles.AllowLeadingSign) != 0) { if (info.HasInvariantNumberSigns) { if (num == '-') { sign = -1; index++; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } else if (num == '+') { index++; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } } else { value = value.Slice(index); index = 0; string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign; if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign)) { index += positiveSign.Length; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } else if (!string.IsNullOrEmpty(negativeSign) && value.StartsWith(negativeSign)) { sign = -1; index += negativeSign.Length; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } } } bool overflow = false; long answer = 0; if (IsDigit(num)) { // Skip past leading zeros. if (num == '0') { do { index++; if ((uint)index >= (uint)value.Length) goto DoneAtEnd; num = value[index]; } while (num == '0'); if (!IsDigit(num)) goto HasTrailingChars; } // Parse most digits, up to the potential for overflow, which can't happen until after 18 digits. answer = num - '0'; // first digit index++; for (int i = 0; i < 17; i++) // next 17 digits can't overflow { if ((uint)index >= (uint)value.Length) goto DoneAtEnd; num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; index++; answer = 10 * answer + num - '0'; } if ((uint)index >= (uint)value.Length) goto DoneAtEnd; num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; index++; // Potential overflow now processing the 19th digit. overflow = answer > long.MaxValue / 10; answer = answer * 10 + num - '0'; overflow |= (ulong)answer > (ulong)long.MaxValue + (((uint)sign) >> 31); if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; // At this point, we're either overflowing or hitting a formatting error. // Format errors take precedence for compatibility. num = value[index]; while (IsDigit(num)) { overflow = true; index++; if ((uint)index >= (uint)value.Length) goto OverflowExit; num = value[index]; } goto HasTrailingChars; } goto FalseExit; DoneAtEndButPotentialOverflow: if (overflow) { goto OverflowExit; } DoneAtEnd: result = answer * sign; ParsingStatus status = ParsingStatus.OK; Exit: return status; FalseExit: // parsing failed result = 0; status = ParsingStatus.Failed; goto Exit; OverflowExit: result = 0; status = ParsingStatus.Overflow; goto Exit; HasTrailingChars: // we've successfully parsed, but there are still remaining characters in the span // Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail. if (IsWhite(num)) { if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit; for (index++; index < value.Length; index++) { if (!IsWhite(value[index])) break; } if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; } if (!TrailingZeros(value, index)) goto FalseExit; goto DoneAtEndButPotentialOverflow; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ParsingStatus TryParseInt64(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out long result) { if ((styles & ~NumberStyles.Integer) == 0) { // Optimized path for the common case of anything that's allowed for integer style. return TryParseInt64IntegerStyle(value, styles, info, out result); } if ((styles & NumberStyles.AllowHexSpecifier) != 0) { result = 0; return TryParseUInt64HexNumberStyle(value, styles, out Unsafe.As(ref result)); } return TryParseInt64Number(value, styles, info, out result); } private static unsafe ParsingStatus TryParseInt64Number(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out long result) { result = 0; byte* pDigits = stackalloc byte[Int64NumberBufferLength]; NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, Int64NumberBufferLength); if (!TryStringToNumber(value, styles, ref number, info)) { return ParsingStatus.Failed; } if (!TryNumberToInt64(ref number, ref result)) { return ParsingStatus.Overflow; } return ParsingStatus.OK; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ParsingStatus TryParseUInt32(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out uint result) { if ((styles & ~NumberStyles.Integer) == 0) { // Optimized path for the common case of anything that's allowed for integer style. return TryParseUInt32IntegerStyle(value, styles, info, out result); } if ((styles & NumberStyles.AllowHexSpecifier) != 0) { return TryParseUInt32HexNumberStyle(value, styles, out result); } return TryParseUInt32Number(value, styles, info, out result); } private static unsafe ParsingStatus TryParseUInt32Number(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out uint result) { result = 0; byte* pDigits = stackalloc byte[UInt32NumberBufferLength]; NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, UInt32NumberBufferLength); if (!TryStringToNumber(value, styles, ref number, info)) { return ParsingStatus.Failed; } if (!TryNumberToUInt32(ref number, ref result)) { return ParsingStatus.Overflow; } return ParsingStatus.OK; } /// Parses uint limited to styles that make up NumberStyles.Integer. internal static ParsingStatus TryParseUInt32IntegerStyle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out uint result) { Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format"); if (value.IsEmpty) goto FalseExit; int index = 0; int num = value[0]; // Skip past any whitespace at the beginning. if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num)) { do { index++; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } while (IsWhite(num)); } // Parse leading sign. bool overflow = false; if ((styles & NumberStyles.AllowLeadingSign) != 0) { if (info.HasInvariantNumberSigns) { if (num == '+') { index++; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } else if (num == '-') { overflow = true; index++; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } } else { value = value.Slice(index); index = 0; string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign; if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign)) { index += positiveSign.Length; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } else if (!string.IsNullOrEmpty(negativeSign) && value.StartsWith(negativeSign)) { overflow = true; index += negativeSign.Length; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } } } int answer = 0; if (IsDigit(num)) { // Skip past leading zeros. if (num == '0') { do { index++; if ((uint)index >= (uint)value.Length) goto DoneAtEnd; num = value[index]; } while (num == '0'); if (!IsDigit(num)) goto HasTrailingCharsZero; } // Parse most digits, up to the potential for overflow, which can't happen until after 9 digits. answer = num - '0'; // first digit index++; for (int i = 0; i < 8; i++) // next 8 digits can't overflow { if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; index++; answer = 10 * answer + num - '0'; } if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; index++; // Potential overflow now processing the 10th digit. overflow |= (uint)answer > uint.MaxValue / 10 || ((uint)answer == uint.MaxValue / 10 && num > '5'); answer = answer * 10 + num - '0'; if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; // At this point, we're either overflowing or hitting a formatting error. // Format errors take precedence for compatibility. num = value[index]; while (IsDigit(num)) { overflow = true; index++; if ((uint)index >= (uint)value.Length) goto OverflowExit; num = value[index]; } goto HasTrailingChars; } goto FalseExit; DoneAtEndButPotentialOverflow: if (overflow) { goto OverflowExit; } DoneAtEnd: result = (uint)answer; ParsingStatus status = ParsingStatus.OK; Exit: return status; FalseExit: // parsing failed result = 0; status = ParsingStatus.Failed; goto Exit; OverflowExit: result = 0; status = ParsingStatus.Overflow; goto Exit; HasTrailingCharsZero: overflow = false; HasTrailingChars: // we've successfully parsed, but there are still remaining characters in the span // Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail. if (IsWhite(num)) { if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit; for (index++; index < value.Length; index++) { if (!IsWhite(value[index])) break; } if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; } if (!TrailingZeros(value, index)) goto FalseExit; goto DoneAtEndButPotentialOverflow; } /// Parses uint limited to styles that make up NumberStyles.HexNumber. private static ParsingStatus TryParseUInt32HexNumberStyle(ReadOnlySpan value, NumberStyles styles, out uint result) { Debug.Assert((styles & ~NumberStyles.HexNumber) == 0, "Only handles subsets of HexNumber format"); if (value.IsEmpty) goto FalseExit; int index = 0; int num = value[0]; uint numValue; // Skip past any whitespace at the beginning. if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num)) { do { index++; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } while (IsWhite(num)); } bool overflow = false; uint answer = 0; ReadOnlySpan charToHexLookup = CharToHexLookup; if ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF) { // Skip past leading zeros. if (num == '0') { do { index++; if ((uint)index >= (uint)value.Length) goto DoneAtEnd; num = value[index]; } while (num == '0'); if ((uint)num >= (uint)charToHexLookup.Length || charToHexLookup[num] == 0xFF) goto HasTrailingChars; } // Parse up through 8 digits, as no overflow is possible answer = charToHexLookup[num]; // first digit index++; for (int i = 0; i < 7; i++) // next 7 digits can't overflow { if ((uint)index >= (uint)value.Length) goto DoneAtEnd; num = value[index]; if ((uint)num >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[num]) == 0xFF) goto HasTrailingChars; index++; answer = 16 * answer + numValue; } // If there's another digit, it's an overflow. if ((uint)index >= (uint)value.Length) goto DoneAtEnd; num = value[index]; if ((uint)num >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[num]) == 0xFF) goto HasTrailingChars; // At this point, we're either overflowing or hitting a formatting error. // Format errors take precedence for compatibility. Read through any remaining digits. do { index++; if ((uint)index >= (uint)value.Length) goto OverflowExit; num = value[index]; } while ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF); overflow = true; goto HasTrailingChars; } goto FalseExit; DoneAtEndButPotentialOverflow: if (overflow) { goto OverflowExit; } DoneAtEnd: result = answer; ParsingStatus status = ParsingStatus.OK; Exit: return status; FalseExit: // parsing failed result = 0; status = ParsingStatus.Failed; goto Exit; OverflowExit: result = 0; status = ParsingStatus.Overflow; goto Exit; HasTrailingChars: // we've successfully parsed, but there are still remaining characters in the span // Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail. if (IsWhite(num)) { if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit; for (index++; index < value.Length; index++) { if (!IsWhite(value[index])) break; } if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; } if (!TrailingZeros(value, index)) goto FalseExit; goto DoneAtEndButPotentialOverflow; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ParsingStatus TryParseUInt64(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out ulong result) { if ((styles & ~NumberStyles.Integer) == 0) { // Optimized path for the common case of anything that's allowed for integer style. return TryParseUInt64IntegerStyle(value, styles, info, out result); } if ((styles & NumberStyles.AllowHexSpecifier) != 0) { return TryParseUInt64HexNumberStyle(value, styles, out result); } return TryParseUInt64Number(value, styles, info, out result); } private static unsafe ParsingStatus TryParseUInt64Number(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out ulong result) { result = 0; byte* pDigits = stackalloc byte[UInt64NumberBufferLength]; NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, UInt64NumberBufferLength); if (!TryStringToNumber(value, styles, ref number, info)) { return ParsingStatus.Failed; } if (!TryNumberToUInt64(ref number, ref result)) { return ParsingStatus.Overflow; } return ParsingStatus.OK; } /// Parses ulong limited to styles that make up NumberStyles.Integer. internal static ParsingStatus TryParseUInt64IntegerStyle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out ulong result) { Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format"); if (value.IsEmpty) goto FalseExit; int index = 0; int num = value[0]; // Skip past any whitespace at the beginning. if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num)) { do { index++; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } while (IsWhite(num)); } // Parse leading sign. bool overflow = false; if ((styles & NumberStyles.AllowLeadingSign) != 0) { if (info.HasInvariantNumberSigns) { if (num == '+') { index++; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } else if (num == '-') { overflow = true; index++; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } } else { value = value.Slice(index); index = 0; string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign; if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign)) { index += positiveSign.Length; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } else if (!string.IsNullOrEmpty(negativeSign) && value.StartsWith(negativeSign)) { overflow = true; index += negativeSign.Length; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } } } long answer = 0; if (IsDigit(num)) { // Skip past leading zeros. if (num == '0') { do { index++; if ((uint)index >= (uint)value.Length) goto DoneAtEnd; num = value[index]; } while (num == '0'); if (!IsDigit(num)) goto HasTrailingCharsZero; } // Parse most digits, up to the potential for overflow, which can't happen until after 19 digits. answer = num - '0'; // first digit index++; for (int i = 0; i < 18; i++) // next 18 digits can't overflow { if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; index++; answer = 10 * answer + num - '0'; } if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; index++; // Potential overflow now processing the 20th digit. overflow |= (ulong)answer > ulong.MaxValue / 10 || ((ulong)answer == ulong.MaxValue / 10 && num > '5'); answer = answer * 10 + num - '0'; if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; // At this point, we're either overflowing or hitting a formatting error. // Format errors take precedence for compatibility. num = value[index]; while (IsDigit(num)) { overflow = true; index++; if ((uint)index >= (uint)value.Length) goto OverflowExit; num = value[index]; } goto HasTrailingChars; } goto FalseExit; DoneAtEndButPotentialOverflow: if (overflow) { goto OverflowExit; } DoneAtEnd: result = (ulong)answer; ParsingStatus status = ParsingStatus.OK; Exit: return status; FalseExit: // parsing failed result = 0; status = ParsingStatus.Failed; goto Exit; OverflowExit: result = 0; status = ParsingStatus.Overflow; goto Exit; HasTrailingCharsZero: overflow = false; HasTrailingChars: // we've successfully parsed, but there are still remaining characters in the span // Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail. if (IsWhite(num)) { if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit; for (index++; index < value.Length; index++) { if (!IsWhite(value[index])) break; } if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; } if (!TrailingZeros(value, index)) goto FalseExit; goto DoneAtEndButPotentialOverflow; } /// Parses ulong limited to styles that make up NumberStyles.HexNumber. private static ParsingStatus TryParseUInt64HexNumberStyle(ReadOnlySpan value, NumberStyles styles, out ulong result) { Debug.Assert((styles & ~NumberStyles.HexNumber) == 0, "Only handles subsets of HexNumber format"); if (value.IsEmpty) goto FalseExit; int index = 0; int num = value[0]; uint numValue; // Skip past any whitespace at the beginning. if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num)) { do { index++; if ((uint)index >= (uint)value.Length) goto FalseExit; num = value[index]; } while (IsWhite(num)); } bool overflow = false; ulong answer = 0; ReadOnlySpan charToHexLookup = CharToHexLookup; if ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF) { // Skip past leading zeros. if (num == '0') { do { index++; if ((uint)index >= (uint)value.Length) goto DoneAtEnd; num = value[index]; } while (num == '0'); if ((uint)num >= (uint)charToHexLookup.Length || charToHexLookup[num] == 0xFF) goto HasTrailingChars; } // Parse up through 16 digits, as no overflow is possible answer = charToHexLookup[num]; // first digit index++; for (int i = 0; i < 15; i++) // next 15 digits can't overflow { if ((uint)index >= (uint)value.Length) goto DoneAtEnd; num = value[index]; if ((uint)num >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[num]) == 0xFF) goto HasTrailingChars; index++; answer = 16 * answer + numValue; } // If there's another digit, it's an overflow. if ((uint)index >= (uint)value.Length) goto DoneAtEnd; num = value[index]; if ((uint)num >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[num]) == 0xFF) goto HasTrailingChars; // At this point, we're either overflowing or hitting a formatting error. // Format errors take precedence for compatibility. Read through any remaining digits. do { index++; if ((uint)index >= (uint)value.Length) goto OverflowExit; num = value[index]; } while ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF); overflow = true; goto HasTrailingChars; } goto FalseExit; DoneAtEndButPotentialOverflow: if (overflow) { goto OverflowExit; } DoneAtEnd: result = answer; ParsingStatus status = ParsingStatus.OK; Exit: return status; FalseExit: // parsing failed result = 0; status = ParsingStatus.Failed; goto Exit; OverflowExit: result = 0; status = ParsingStatus.Overflow; goto Exit; HasTrailingChars: // we've successfully parsed, but there are still remaining characters in the span // Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail. if (IsWhite(num)) { if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit; for (index++; index < value.Length; index++) { if (!IsWhite(value[index])) break; } if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; } if (!TrailingZeros(value, index)) goto FalseExit; goto DoneAtEndButPotentialOverflow; } internal static decimal ParseDecimal(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { ParsingStatus status = TryParseDecimal(value, styles, info, out decimal result); if (status != ParsingStatus.OK) { ThrowOverflowOrFormatException(status, TypeCode.Decimal); } return result; } internal static unsafe bool TryNumberToDecimal(ref NumberBuffer number, ref decimal value) { number.CheckConsistency(); byte* p = number.GetDigitsPointer(); int e = number.Scale; bool sign = number.IsNegative; uint c = *p; if (c == 0) { // 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 // the scale to 0 if the scale was previously positive (previously, such cases were unparsable to a bug.) value = new decimal(0, 0, 0, sign, (byte)Math.Clamp(-e, 0, 28)); return true; } if (e > DecimalPrecision) return false; ulong low64 = 0; while (e > -28) { e--; low64 *= 10; low64 += c - '0'; c = *++p; if (low64 >= ulong.MaxValue / 10) break; if (c == 0) { while (e > 0) { e--; low64 *= 10; if (low64 >= ulong.MaxValue / 10) break; } break; } } uint high = 0; while ((e > 0 || (c != 0 && e > -28)) && (high < uint.MaxValue / 10 || (high == uint.MaxValue / 10 && (low64 < 0x99999999_99999999 || (low64 == 0x99999999_99999999 && c <= '5'))))) { // multiply by 10 ulong tmpLow = (uint)low64 * 10UL; ulong tmp64 = (uint)(low64 >> 32) * 10UL + (tmpLow >> 32); low64 = (uint)tmpLow + (tmp64 << 32); high = (uint)(tmp64 >> 32) + high * 10; if (c != 0) { c -= '0'; low64 += c; if (low64 < c) high++; c = *++p; } e--; } if (c >= '5') { if ((c == '5') && ((low64 & 1) == 0)) { c = *++p; bool hasZeroTail = !number.HasNonZeroTail; // We might still have some additional digits, in which case they need // to be considered as part of hasZeroTail. Some examples of this are: // * 3.0500000000000000000001e-27 // * 3.05000000000000000000001e-27 // In these cases, we will have processed 3 and 0, and ended on 5. The // buffer, however, will still contain a number of trailing zeros and // a trailing non-zero number. while ((c != 0) && hasZeroTail) { hasZeroTail &= (c == '0'); c = *++p; } // We should either be at the end of the stream or have a non-zero tail Debug.Assert((c == 0) || !hasZeroTail); if (hasZeroTail) { // When the next digit is 5, the number is even, and all following // digits are zero we don't need to round. goto NoRounding; } } if (++low64 == 0 && ++high == 0) { low64 = 0x99999999_9999999A; high = uint.MaxValue / 10; e++; } } NoRounding: if (e > 0) return false; if (e <= -DecimalPrecision) { // Parsing a large scale zero can give you more precision than fits in the decimal. // This should only happen for actual zeros or very small numbers that round to zero. value = new decimal(0, 0, 0, sign, DecimalPrecision - 1); } else { value = new decimal((int)low64, (int)(low64 >> 32), (int)high, sign, (byte)-e); } return true; } internal static double ParseDouble(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { if (!TryParseDouble(value, styles, info, out double result)) { ThrowOverflowOrFormatException(ParsingStatus.Failed); } return result; } internal static float ParseSingle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { if (!TryParseSingle(value, styles, info, out float result)) { ThrowOverflowOrFormatException(ParsingStatus.Failed); } return result; } internal static unsafe ParsingStatus TryParseDecimal(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out decimal result) { byte* pDigits = stackalloc byte[DecimalNumberBufferLength]; NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, pDigits, DecimalNumberBufferLength); result = 0; if (!TryStringToNumber(value, styles, ref number, info)) { return ParsingStatus.Failed; } if (!TryNumberToDecimal(ref number, ref result)) { return ParsingStatus.Overflow; } return ParsingStatus.OK; } internal static unsafe bool TryParseDouble(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out double result) { byte* pDigits = stackalloc byte[DoubleNumberBufferLength]; NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, DoubleNumberBufferLength); if (!TryStringToNumber(value, styles, ref number, info)) { ReadOnlySpan valueTrim = value.Trim(); // This code would be simpler if we only had the concept of `InfinitySymbol`, but // we don't so we'll check the existing cases first and then handle `PositiveSign` + // `PositiveInfinitySymbol` and `PositiveSign/NegativeSign` + `NaNSymbol` last. if (valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol)) { result = double.PositiveInfinity; } else if (valueTrim.EqualsOrdinalIgnoreCase(info.NegativeInfinitySymbol)) { result = double.NegativeInfinity; } else if (valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) { result = double.NaN; } else if (valueTrim.StartsWith(info.PositiveSign, StringComparison.OrdinalIgnoreCase)) { valueTrim = valueTrim.Slice(info.PositiveSign.Length); if (valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol)) { result = double.PositiveInfinity; } else if (valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) { result = double.NaN; } else { result = 0; return false; } } else if (valueTrim.StartsWith(info.NegativeSign, StringComparison.OrdinalIgnoreCase) && valueTrim.Slice(info.NegativeSign.Length).EqualsOrdinalIgnoreCase(info.NaNSymbol)) { result = double.NaN; } else { result = 0; return false; // We really failed } } else { result = NumberToDouble(ref number); } return true; } internal static unsafe bool TryParseSingle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out float result) { byte* pDigits = stackalloc byte[SingleNumberBufferLength]; NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, SingleNumberBufferLength); if (!TryStringToNumber(value, styles, ref number, info)) { ReadOnlySpan valueTrim = value.Trim(); // This code would be simpler if we only had the concept of `InfinitySymbol`, but // we don't so we'll check the existing cases first and then handle `PositiveSign` + // `PositiveInfinitySymbol` and `PositiveSign/NegativeSign` + `NaNSymbol` last. // // Additionally, since some cultures ("wo") actually define `PositiveInfinitySymbol` // to include `PositiveSign`, we need to check whether `PositiveInfinitySymbol` fits // that case so that we don't start parsing things like `++infini`. if (valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol)) { result = float.PositiveInfinity; } else if (valueTrim.EqualsOrdinalIgnoreCase(info.NegativeInfinitySymbol)) { result = float.NegativeInfinity; } else if (valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) { result = float.NaN; } else if (valueTrim.StartsWith(info.PositiveSign, StringComparison.OrdinalIgnoreCase)) { valueTrim = valueTrim.Slice(info.PositiveSign.Length); if (!info.PositiveInfinitySymbol.StartsWith(info.PositiveSign, StringComparison.OrdinalIgnoreCase) && valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol)) { result = float.PositiveInfinity; } else if (!info.NaNSymbol.StartsWith(info.PositiveSign, StringComparison.OrdinalIgnoreCase) && valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) { result = float.NaN; } else { result = 0; return false; } } else if (valueTrim.StartsWith(info.NegativeSign, StringComparison.OrdinalIgnoreCase) && !info.NaNSymbol.StartsWith(info.NegativeSign, StringComparison.OrdinalIgnoreCase) && valueTrim.Slice(info.NegativeSign.Length).EqualsOrdinalIgnoreCase(info.NaNSymbol)) { result = float.NaN; } else { result = 0; return false; // We really failed } } else { result = NumberToSingle(ref number); } return true; } internal static unsafe bool TryStringToNumber(ReadOnlySpan value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info) { Debug.Assert(info != null); fixed (char* stringPointer = &MemoryMarshal.GetReference(value)) { char* p = stringPointer; if (!TryParseNumber(ref p, p + value.Length, styles, ref number, info) || ((int)(p - stringPointer) < value.Length && !TrailingZeros(value, (int)(p - stringPointer)))) { number.CheckConsistency(); return false; } } number.CheckConsistency(); return true; } private static bool TrailingZeros(ReadOnlySpan value, int index) { // For compatibility, we need to allow trailing zeros at the end of a number string for (int i = index; (uint)i < (uint)value.Length; i++) { if (value[i] != '\0') { return false; } } return true; } private static bool IsSpaceReplacingChar(char c) => c == '\u00a0' || c == '\u202f'; private static unsafe char* MatchChars(char* p, char* pEnd, string value) { Debug.Assert(p != null && pEnd != null && p <= pEnd && value != null); fixed (char* stringPointer = value) { char* str = stringPointer; if (*str != '\0') { // We only hurt the failure case // This fix is for French or Kazakh cultures. Since a user cannot type 0xA0 or 0x202F as a // space character we use 0x20 space character instead to mean the same. while (true) { char cp = p < pEnd ? *p : '\0'; if (cp != *str && !(IsSpaceReplacingChar(*str) && cp == '\u0020')) { break; } p++; str++; if (*str == '\0') return p; } } } return null; } // Ternary op is a workaround for https://github.com/dotnet/coreclr/issues/914 private static bool IsWhite(int ch) => ch == 0x20 || (uint)(ch - 0x09) <= (0x0D - 0x09) ? true : false; private static bool IsDigit(int ch) => ((uint)ch - '0') <= 9; internal enum ParsingStatus { OK, Failed, Overflow } [DoesNotReturn] internal static void ThrowOverflowOrFormatException(ParsingStatus status, TypeCode type = 0) => throw GetException(status, type); [DoesNotReturn] internal static void ThrowOverflowException(TypeCode type) => throw GetException(ParsingStatus.Overflow, type); private static Exception GetException(ParsingStatus status, TypeCode type) { if (status == ParsingStatus.Failed) return new FormatException(SR.Format_InvalidString); string s; switch (type) { case TypeCode.SByte: s = SR.Overflow_SByte; break; case TypeCode.Byte: s = SR.Overflow_Byte; break; case TypeCode.Int16: s = SR.Overflow_Int16; break; case TypeCode.UInt16: s = SR.Overflow_UInt16; break; case TypeCode.Int32: s = SR.Overflow_Int32; break; case TypeCode.UInt32: s = SR.Overflow_UInt32; break; case TypeCode.Int64: s = SR.Overflow_Int64; break; case TypeCode.UInt64: s = SR.Overflow_UInt64; break; default: Debug.Assert(type == TypeCode.Decimal); s = SR.Overflow_Decimal; break; } return new OverflowException(s); } internal static double NumberToDouble(ref NumberBuffer number) { number.CheckConsistency(); double result; if ((number.DigitsCount == 0) || (number.Scale < DoubleMinExponent)) { result = 0; } else if (number.Scale > DoubleMaxExponent) { result = double.PositiveInfinity; } else { ulong bits = NumberToFloatingPointBits(ref number, in FloatingPointInfo.Double); result = BitConverter.Int64BitsToDouble((long)(bits)); } return number.IsNegative ? -result : result; } internal static float NumberToSingle(ref NumberBuffer number) { number.CheckConsistency(); float result; if ((number.DigitsCount == 0) || (number.Scale < SingleMinExponent)) { result = 0; } else if (number.Scale > SingleMaxExponent) { result = float.PositiveInfinity; } else { uint bits = (uint)(NumberToFloatingPointBits(ref number, in FloatingPointInfo.Single)); result = BitConverter.Int32BitsToSingle((int)(bits)); } return number.IsNegative ? -result : result; } } }