From 624f72d55a92e49aef3c3cd6e69150fa3b085fac Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 17 Jul 2018 06:00:11 -0700 Subject: [PATCH] Improve throughput of {U}Int32/64.{Try}Parse for hex (#18952) * Improve throughput of {U}Int32/64.{Try}Parse for hex - Copies/modifies the recently added TryParse32/64IntegerStyle routines for hex in order to provide more optimized parsing. - Since AllowHexSpecifier can't be used with any other styles (other than allowing leading/trailing whitespace), deletes existing code paths for parsing hex. - As I was copying code around, I kept getting bitten by different names used for the same thing, so I also standardized some parameter names in various helper functions. * Clean up - Remove a bunch of unnecessary 'unsafe' keywords - Rephrase some while(true) loops as do/while loops for consistency and to avoid some repetition - Remove an unnecessary wrapper function --- .../shared/System/Number.Parsing.cs | 1062 +++++++++-------- 1 file changed, 554 insertions(+), 508 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs index 0fe2c08da0..c764f08420 100644 --- a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs +++ b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Globalization; using System.Runtime.InteropServices; +using Internal.Runtime.CompilerServices; namespace System { @@ -30,129 +31,26 @@ namespace System private const int Int64Precision = 19; private const int UInt64Precision = 20; - private static bool HexNumberToInt32(ref NumberBuffer number, ref int value) + /// 256-element map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit. + private static readonly int[] s_charToHexLookup = { - uint passedValue = 0; - bool returnValue = HexNumberToUInt32(ref number, ref passedValue); - value = (int)passedValue; - return returnValue; - } - - private static bool HexNumberToInt64(ref NumberBuffer number, ref long value) - { - ulong passedValue = 0; - bool returnValue = HexNumberToUInt64(ref number, ref passedValue); - value = (long)passedValue; - return returnValue; - } - - private static unsafe bool HexNumberToUInt32(ref NumberBuffer number, ref uint value) - { - int i = number.scale; - if (i > UInt32Precision || i < number.precision) - { - return false; - } - char* p = number.digits; - Debug.Assert(p != null); - - uint n = 0; - while (--i >= 0) - { - if (n > ((uint)0xFFFFFFFF / 16)) - { - return false; - } - n *= 16; - if (*p != '\0') - { - uint newN = n; - if (*p != '\0') - { - if (IsDigit(*p)) - { - newN += (uint)(*p - '0'); - } - else - { - if (*p >= 'A' && *p <= 'F') - { - newN += (uint)((*p - 'A') + 10); - } - else - { - Debug.Assert(*p >= 'a' && *p <= 'f'); - newN += (uint)((*p - 'a') + 10); - } - } - p++; - } - - // Detect an overflow here... - if (newN < n) - { - return false; - } - n = newN; - } - } - value = n; - return true; - } - - private static unsafe bool HexNumberToUInt64(ref NumberBuffer number, ref ulong value) - { - int i = number.scale; - if (i > UInt64Precision || i < number.precision) - { - return false; - } - char* p = number.digits; - Debug.Assert(p != null); - - ulong n = 0; - while (--i >= 0) - { - if (n > (0xFFFFFFFFFFFFFFFF / 16)) - { - return false; - } - n *= 16; - if (*p != '\0') - { - ulong newN = n; - if (*p != '\0') - { - if (IsDigit(*p)) - { - newN += (ulong)(*p - '0'); - } - else - { - if (*p >= 'A' && *p <= 'F') - { - newN += (ulong)((*p - 'A') + 10); - } - else - { - Debug.Assert(*p >= 'a' && *p <= 'f'); - newN += (ulong)((*p - 'a') + 10); - } - } - p++; - } - - // Detect an overflow here... - if (newN < n) - { - return false; - } - n = newN; - } - } - value = n; - return true; - } + 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, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 255 + }; private static unsafe bool NumberToInt32(ref NumberBuffer number, ref int value) { @@ -173,7 +71,7 @@ namespace System n *= 10; if (*p != '\0') { - n += (int)(*p++ - '0'); + n += (*p++ - '0'); } } if (number.sign) @@ -214,7 +112,7 @@ namespace System n *= 10; if (*p != '\0') { - n += (int)(*p++ - '0'); + n += (*p++ - '0'); } } if (number.sign) @@ -300,159 +198,146 @@ namespace System return true; } - internal static unsafe int ParseInt32(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info) + internal static int ParseInt32(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { - int i = 0; - - if ((style & ~NumberStyles.Integer) == 0) + if ((styles & ~NumberStyles.Integer) == 0) { // Optimized path for the common case of anything that's allowed for integer style. bool overflow = false; - if (!TryParseInt32IntegerStyle(s, style, info, out i, ref overflow)) + if (!TryParseInt32IntegerStyle(value, styles, info, out int intResult, ref overflow)) { - throw overflow ? - new OverflowException(SR.Overflow_Int32) : - (Exception)new FormatException(SR.Format_InvalidString); + ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_Int32)); } - return i; + return intResult; } - NumberBuffer number = default; - StringToNumber(s, style, ref number, info, false); - - if ((style & NumberStyles.AllowHexSpecifier) != 0) + if ((styles & NumberStyles.AllowHexSpecifier) != 0) { - if (!HexNumberToInt32(ref number, ref i)) + bool overflow = false; + if (!TryParseUInt32HexNumberStyle(value, styles, info, out uint hexResult, ref overflow)) { - throw new OverflowException(SR.Overflow_Int32); + ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_Int32)); } + return (int)hexResult; } - else + + NumberBuffer number = default; + int result = 0; + StringToNumber(value, styles, ref number, info, false); + if (!NumberToInt32(ref number, ref result)) { - if (!NumberToInt32(ref number, ref i)) - { - throw new OverflowException(SR.Overflow_Int32); - } + ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_Int32)); } - return i; + return result; } - internal static unsafe long ParseInt64(ReadOnlySpan value, NumberStyles options, NumberFormatInfo numfmt) + internal static long ParseInt64(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { - long i = 0; - - if ((options & ~NumberStyles.Integer) == 0) + if ((styles & ~NumberStyles.Integer) == 0) { // Optimized path for the common case of anything that's allowed for integer style. bool overflow = false; - if (!TryParseInt64IntegerStyle(value, options, numfmt, out i, ref overflow)) + if (!TryParseInt64IntegerStyle(value, styles, info, out long intResult, ref overflow)) { - throw overflow ? - new OverflowException(SR.Overflow_Int64) : - (Exception)new FormatException(SR.Format_InvalidString); + ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_Int64)); } - return i; + return intResult; } - NumberBuffer number = default; - StringToNumber(value, options, ref number, numfmt, false); - - if ((options & NumberStyles.AllowHexSpecifier) != 0) + if ((styles & NumberStyles.AllowHexSpecifier) != 0) { - if (!HexNumberToInt64(ref number, ref i)) + bool overflow = false; + if (!TryParseUInt64HexNumberStyle(value, styles, info, out ulong hexResult, ref overflow)) { - throw new OverflowException(SR.Overflow_Int64); + ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_Int64)); } + return (long)hexResult; } - else + + NumberBuffer number = default; + long result = 0; + StringToNumber(value, styles, ref number, info, false); + if (!NumberToInt64(ref number, ref result)) { - if (!NumberToInt64(ref number, ref i)) - { - throw new OverflowException(SR.Overflow_Int64); - } + ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_Int64)); } - return i; + return result; } - internal static unsafe uint ParseUInt32(ReadOnlySpan value, NumberStyles options, NumberFormatInfo numfmt) + internal static uint ParseUInt32(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { - uint i = 0; + uint result = 0; - if ((options & ~NumberStyles.Integer) == 0) + if ((styles & ~NumberStyles.Integer) == 0) { // Optimized path for the common case of anything that's allowed for integer style. bool overflow = false; - if (!TryParseUInt32IntegerStyle(value, options, numfmt, out i, ref overflow)) + if (!TryParseUInt32IntegerStyle(value, styles, info, out result, ref overflow)) { - throw overflow ? - new OverflowException(SR.Overflow_UInt32) : - (Exception)new FormatException(SR.Format_InvalidString); + ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_UInt32)); } - return i; + return result; } - NumberBuffer number = default; - StringToNumber(value, options, ref number, numfmt, false); - - if ((options & NumberStyles.AllowHexSpecifier) != 0) + if ((styles & NumberStyles.AllowHexSpecifier) != 0) { - if (!HexNumberToUInt32(ref number, ref i)) + bool overflow = false; + if (!TryParseUInt32HexNumberStyle(value, styles, info, out result, ref overflow)) { - throw new OverflowException(SR.Overflow_UInt32); + ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_UInt32)); } + return result; } - else + + NumberBuffer number = default; + StringToNumber(value, styles, ref number, info, false); + if (!NumberToUInt32(ref number, ref result)) { - if (!NumberToUInt32(ref number, ref i)) - { - throw new OverflowException(SR.Overflow_UInt32); - } + ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_UInt32)); } - - return i; + return result; } - internal static unsafe ulong ParseUInt64(ReadOnlySpan value, NumberStyles options, NumberFormatInfo numfmt) + internal static ulong ParseUInt64(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { - ulong i = 0; + ulong result = 0; - if ((options & ~NumberStyles.Integer) == 0) + if ((styles & ~NumberStyles.Integer) == 0) { // Optimized path for the common case of anything that's allowed for integer style. bool overflow = false; - if (!TryParseUInt64IntegerStyle(value, options, numfmt, out i, ref overflow)) + if (!TryParseUInt64IntegerStyle(value, styles, info, out result, ref overflow)) { - throw overflow ? - new OverflowException(SR.Overflow_UInt64) : - (Exception)new FormatException(SR.Format_InvalidString); + ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_UInt64)); } - return i; + return result; } - NumberBuffer number = default; - StringToNumber(value, options, ref number, numfmt, false); - if ((options & NumberStyles.AllowHexSpecifier) != 0) + if ((styles & NumberStyles.AllowHexSpecifier) != 0) { - if (!HexNumberToUInt64(ref number, ref i)) + bool overflow = false; + if (!TryParseUInt64HexNumberStyle(value, styles, info, out result, ref overflow)) { - throw new OverflowException(SR.Overflow_UInt64); + ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_UInt64)); } + return result; } - else + + NumberBuffer number = default; + StringToNumber(value, styles, ref number, info, false); + if (!NumberToUInt64(ref number, ref result)) { - if (!NumberToUInt64(ref number, ref i)) - { - throw new OverflowException(SR.Overflow_UInt64); - } + ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_UInt64)); } - return i; + return result; } - private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles options, ref NumberBuffer number, NumberFormatInfo numfmt, bool parseDecimal) + private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info, bool parseDecimal) { 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; @@ -468,20 +353,20 @@ namespace System string currSymbol = null; // currency symbol from NumberFormatInfo. bool parsingCurrency = false; - if ((options & NumberStyles.AllowCurrencySymbol) != 0) + if ((styles & NumberStyles.AllowCurrencySymbol) != 0) { - currSymbol = numfmt.CurrencySymbol; + 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 = numfmt.CurrencyDecimalSeparator; - groupSep = numfmt.CurrencyGroupSeparator; + decSep = info.CurrencyDecimalSeparator; + groupSep = info.CurrencyGroupSeparator; parsingCurrency = true; } else { - decSep = numfmt.NumberDecimalSeparator; - groupSep = numfmt.NumberGroupSeparator; + decSep = info.NumberDecimalSeparator; + groupSep = info.NumberGroupSeparator; } int state = 0; @@ -493,14 +378,14 @@ namespace System { // 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) || (options & NumberStyles.AllowLeadingWhite) == 0 || ((state & StateSign) != 0 && ((state & StateCurrency) == 0 && numfmt.NumberNegativePattern != 2))) + if (!IsWhite(ch) || (styles & NumberStyles.AllowLeadingWhite) == 0 || ((state & StateSign) != 0 && ((state & StateCurrency) == 0 && info.NumberNegativePattern != 2))) { - if ((((options & NumberStyles.AllowLeadingSign) != 0) && (state & StateSign) == 0) && ((next = MatchChars(p, strEnd, numfmt.PositiveSign)) != null || ((next = MatchChars(p, strEnd, numfmt.NegativeSign)) != null && (number.sign = true)))) + 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)))) { state |= StateSign; p = next - 1; } - else if (ch == '(' && ((options & NumberStyles.AllowParentheses) != 0) && ((state & StateSign) == 0)) + else if (ch == '(' && ((styles & NumberStyles.AllowParentheses) != 0) && ((state & StateSign) == 0)) { state |= StateSign | StateParens; number.sign = true; @@ -524,7 +409,7 @@ namespace System int digEnd = 0; while (true) { - if (IsDigit(ch) || (((options & NumberStyles.AllowHexSpecifier) != 0) && ((ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')))) + if (IsDigit(ch)) { state |= StateDigits; @@ -549,12 +434,12 @@ namespace System number.scale--; } } - else if (((options & NumberStyles.AllowDecimalPoint) != 0) && ((state & StateDecimal) == 0) && ((next = MatchChars(p, strEnd, decSep)) != null || ((parsingCurrency) && (state & StateCurrency) == 0) && (next = MatchChars(p, strEnd, numfmt.NumberDecimalSeparator)) != null)) + 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 (((options & NumberStyles.AllowThousands) != 0) && ((state & StateDigits) != 0) && ((state & StateDecimal) == 0) && ((next = MatchChars(p, strEnd, groupSep)) != null || ((parsingCurrency) && (state & StateCurrency) == 0) && (next = MatchChars(p, strEnd, numfmt.NumberGroupSeparator)) != null)) + 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; } @@ -570,15 +455,15 @@ namespace System number.digits[digEnd] = '\0'; if ((state & StateDigits) != 0) { - if ((ch == 'E' || ch == 'e') && ((options & NumberStyles.AllowExponent) != 0)) + if ((ch == 'E' || ch == 'e') && ((styles & NumberStyles.AllowExponent) != 0)) { char* temp = p; ch = ++p < strEnd ? *p : '\0'; - if ((next = MatchChars(p, strEnd, numfmt.positiveSign)) != null) + if ((next = MatchChars(p, strEnd, info.positiveSign)) != null) { ch = (p = next) < strEnd ? *p : '\0'; } - else if ((next = MatchChars(p, strEnd, numfmt.negativeSign)) != null) + else if ((next = MatchChars(p, strEnd, info.negativeSign)) != null) { ch = (p = next) < strEnd ? *p : '\0'; negExp = true; @@ -613,9 +498,9 @@ namespace System } while (true) { - if (!IsWhite(ch) || (options & NumberStyles.AllowTrailingWhite) == 0) + if (!IsWhite(ch) || (styles & NumberStyles.AllowTrailingWhite) == 0) { - if (((options & NumberStyles.AllowTrailingSign) != 0 && ((state & StateSign) == 0)) && ((next = MatchChars(p, strEnd, numfmt.PositiveSign)) != null || (((next = MatchChars(p, strEnd, numfmt.NegativeSign)) != null) && (number.sign = true)))) + 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)))) { state |= StateSign; p = next - 1; @@ -657,64 +542,52 @@ namespace System return false; } - internal static bool TryParseInt32(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info, out int result) + internal static bool TryParseInt32(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out int result) { - NumberBuffer number = default; - result = 0; - - if ((style & ~NumberStyles.Integer) == 0) + if ((styles & ~NumberStyles.Integer) == 0) { // Optimized path for the common case of anything that's allowed for integer style. bool overflow = false; - return TryParseInt32IntegerStyle(s, style, info, out result, ref overflow); + return TryParseInt32IntegerStyle(value, styles, info, out result, ref overflow); } - if (!TryStringToNumber(s, style, ref number, info, false)) - { - return false; - } + result = 0; - if ((style & NumberStyles.AllowHexSpecifier) != 0) + if ((styles & NumberStyles.AllowHexSpecifier) != 0) { - if (!HexNumberToInt32(ref number, ref result)) - { - return false; - } - } - else - { - if (!NumberToInt32(ref number, ref result)) - { - return false; - } + bool overflow = false; + return TryParseUInt32HexNumberStyle(value, styles, info, out Unsafe.As(ref result), ref overflow); } - return true; + + NumberBuffer number = default; + return + TryStringToNumber(value, styles, ref number, info, false) && + NumberToInt32(ref number, ref result); } /// Parses int limited to styles that make up NumberStyles.Integer. - private static bool TryParseInt32IntegerStyle(ReadOnlySpan source, NumberStyles styles, NumberFormatInfo info, out int result, ref bool failureIsOverflow) + private static bool TryParseInt32IntegerStyle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out int result, ref bool failureIsOverflow) { Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format"); Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false"); - if ((uint)source.Length < 1) goto FalseExit; + if ((uint)value.Length < 1) goto FalseExit; bool overflow = false; int sign = 1; int index = 0; - int num = source[0]; + int num = value[0]; // Skip past any whitespace at the beginning. if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num)) { - index++; - while (true) + do { - if ((uint)index >= (uint)source.Length) goto FalseExit; - num = source[index]; - if (!IsWhite(num)) break; index++; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; } + while (IsWhite(num)); } // Parse leading sign. @@ -728,32 +601,32 @@ namespace System { sign = -1; index++; - if ((uint)index >= (uint)source.Length) goto FalseExit; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; } else if (num == '+') { index++; - if ((uint)index >= (uint)source.Length) goto FalseExit; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; } } else { - source = source.Slice(index); + value = value.Slice(index); index = 0; - if (!string.IsNullOrEmpty(positiveSign) && source.StartsWith(positiveSign)) + if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign)) { index += positiveSign.Length; - if ((uint)index >= (uint)source.Length) goto FalseExit; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; } - else if (!string.IsNullOrEmpty(negativeSign) && source.StartsWith(negativeSign)) + else if (!string.IsNullOrEmpty(negativeSign) && value.StartsWith(negativeSign)) { sign = -1; index += negativeSign.Length; - if ((uint)index >= (uint)source.Length) goto FalseExit; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; } } } @@ -768,8 +641,8 @@ namespace System do { index++; - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + num = value[index]; } while (num == '0'); if (!IsDigit(num)) goto HasTrailingChars; } @@ -779,16 +652,16 @@ namespace System index++; for (int i = 0; i < 8; i++) // next 8 digits can't overflow { - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; index++; answer = 10 * answer + num - '0'; } // Potential overflow now processing the 10th digit. - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; index++; if (answer > int.MaxValue / 10) @@ -800,18 +673,18 @@ namespace System { overflow = true; } - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; + 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 = source[index]; + num = value[index]; while (IsDigit(num)) { overflow = true; index++; - if ((uint)index >= (uint)source.Length) + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; - num = source[index]; + num = value[index]; } goto HasTrailingChars; } @@ -834,43 +707,42 @@ namespace System if (IsWhite(num)) { if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit; - for (index++; index < source.Length; index++) + for (index++; index < value.Length; index++) { - if (!IsWhite(source[index])) break; + if (!IsWhite(value[index])) break; } - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; } - if (!TrailingZeros(source, index)) goto FalseExit; + if (!TrailingZeros(value, index)) goto FalseExit; goto DoneAtEndButPotentialOverflow; } /// Parses long inputs limited to styles that make up NumberStyles.Integer. private static bool TryParseInt64IntegerStyle( - ReadOnlySpan source, NumberStyles styles, NumberFormatInfo info, out long result, ref bool failureIsOverflow) + ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out long result, ref bool failureIsOverflow) { Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format"); Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false"); - if ((uint)source.Length < 1) goto FalseExit; + if ((uint)value.Length < 1) goto FalseExit; bool overflow = false; int sign = 1; int index = 0; - int num = source[0]; + int num = value[0]; // Skip past any whitespace at the beginning. if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num)) { - index++; - while (true) + do { - if ((uint)index >= (uint)source.Length) goto FalseExit; - num = source[index]; - if (!IsWhite(num)) break; index++; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; } + while (IsWhite(num)); } // Parse leading sign. @@ -884,32 +756,32 @@ namespace System { sign = -1; index++; - if ((uint)index >= (uint)source.Length) goto FalseExit; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; } else if (num == '+') { index++; - if ((uint)index >= (uint)source.Length) goto FalseExit; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; } } else { - source = source.Slice(index); + value = value.Slice(index); index = 0; - if (!string.IsNullOrEmpty(positiveSign) && source.StartsWith(positiveSign)) + if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign)) { index += positiveSign.Length; - if ((uint)index >= (uint)source.Length) goto FalseExit; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; } - else if (!string.IsNullOrEmpty(negativeSign) && source.StartsWith(negativeSign)) + else if (!string.IsNullOrEmpty(negativeSign) && value.StartsWith(negativeSign)) { sign = -1; index += negativeSign.Length; - if ((uint)index >= (uint)source.Length) goto FalseExit; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; } } } @@ -924,8 +796,8 @@ namespace System do { index++; - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + num = value[index]; } while (num == '0'); if (!IsDigit(num)) goto HasTrailingChars; } @@ -935,16 +807,16 @@ namespace System index++; for (int i = 0; i < 17; i++) // next 17 digits can't overflow { - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; index++; answer = 10 * answer + num - '0'; } // Potential overflow now processing the 19th digit. - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; index++; if (answer > long.MaxValue / 10) @@ -956,17 +828,17 @@ namespace System { overflow = true; } - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; + 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 = source[index]; + num = value[index]; while (IsDigit(num)) { overflow = true; index++; - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + num = value[index]; } goto HasTrailingChars; } @@ -989,111 +861,87 @@ namespace System if (IsWhite(num)) { if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit; - for (index++; index < source.Length; index++) + for (index++; index < value.Length; index++) { - if (!IsWhite(source[index])) break; + if (!IsWhite(value[index])) break; } - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; } - if (!TrailingZeros(source, index)) goto FalseExit; + if (!TrailingZeros(value, index)) goto FalseExit; goto DoneAtEndButPotentialOverflow; } - internal static unsafe bool TryParseInt64(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info, out long result) + internal static bool TryParseInt64(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out long result) { - result = 0; - - if ((style & ~NumberStyles.Integer) == 0) + if ((styles & ~NumberStyles.Integer) == 0) { // Optimized path for the common case of anything that's allowed for integer style. bool overflow = false; - return TryParseInt64IntegerStyle(s, style, info, out result, ref overflow); + return TryParseInt64IntegerStyle(value, styles, info, out result, ref overflow); } - NumberBuffer number = default; - if (!TryStringToNumber(s, style, ref number, info, false)) - { - return false; - } + result = 0; - if ((style & NumberStyles.AllowHexSpecifier) != 0) - { - if (!HexNumberToInt64(ref number, ref result)) - { - return false; - } - } - else + if ((styles & NumberStyles.AllowHexSpecifier) != 0) { - if (!NumberToInt64(ref number, ref result)) - { - return false; - } + bool overflow = false; + return TryParseUInt64HexNumberStyle(value, styles, info, out Unsafe.As(ref result), ref overflow); } - return true; - } - internal static unsafe bool TryParseUInt32(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info, out uint result) - { NumberBuffer number = default; - result = 0; + return + TryStringToNumber(value, styles, ref number, info, false) && + NumberToInt64(ref number, ref result); + } - if ((style & ~NumberStyles.Integer) == 0) + internal static bool 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. bool overflow = false; - return TryParseUInt32IntegerStyle(s, style, info, out result, ref overflow); + return TryParseUInt32IntegerStyle(value, styles, info, out result, ref overflow); } - if (!TryStringToNumber(s, style, ref number, info, false)) + if ((styles & NumberStyles.AllowHexSpecifier) != 0) { - return false; + bool overflow = false; + return TryParseUInt32HexNumberStyle(value, styles, info, out result, ref overflow); } - if ((style & NumberStyles.AllowHexSpecifier) != 0) - { - if (!HexNumberToUInt32(ref number, ref result)) - { - return false; - } - } - else - { - if (!NumberToUInt32(ref number, ref result)) - { - return false; - } - } - return true; + NumberBuffer number = default; + result = 0; + return + TryStringToNumber(value, styles, ref number, info, false) && + NumberToUInt32(ref number, ref result); } /// Parses uint limited to styles that make up NumberStyles.Integer. private static bool TryParseUInt32IntegerStyle( - ReadOnlySpan source, NumberStyles styles, NumberFormatInfo info, out uint result, ref bool failureIsOverflow) + ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out uint result, ref bool failureIsOverflow) { Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format"); Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false"); - if ((uint)source.Length < 1) goto FalseExit; + if ((uint)value.Length < 1) goto FalseExit; bool overflow = false; bool hasNegativeSign = false; int index = 0; - int num = source[0]; + int num = value[0]; // Skip past any whitespace at the beginning. if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num)) { - index++; - while (true) + do { - if ((uint)index >= (uint)source.Length) goto FalseExit; - num = source[index]; - if (!IsWhite(num)) break; index++; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; } + while (IsWhite(num)); } // Parse leading sign. @@ -1106,33 +954,33 @@ namespace System if (num == '+') { index++; - if ((uint)index >= (uint)source.Length) goto FalseExit; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; } else if (num == '-') { hasNegativeSign = true; index++; - if ((uint)index >= (uint)source.Length) goto FalseExit; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; } } else { - source = source.Slice(index); + value = value.Slice(index); index = 0; - if (!string.IsNullOrEmpty(positiveSign) && source.StartsWith(positiveSign)) + if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign)) { index += positiveSign.Length; - if ((uint)index >= (uint)source.Length) goto FalseExit; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; } - else if (!string.IsNullOrEmpty(negativeSign) && source.StartsWith(negativeSign)) + else if (!string.IsNullOrEmpty(negativeSign) && value.StartsWith(negativeSign)) { hasNegativeSign = true; index += negativeSign.Length; - if ((uint)index >= (uint)source.Length) goto FalseExit; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; } } } @@ -1147,8 +995,8 @@ namespace System do { index++; - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + num = value[index]; } while (num == '0'); if (!IsDigit(num)) goto HasTrailingChars; } @@ -1158,16 +1006,16 @@ namespace System index++; for (int i = 0; i < 8; i++) // next 8 digits can't overflow { - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; index++; answer = 10 * answer + num - '0'; } // Potential overflow now processing the 10th digit. - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; index++; if ((uint)answer > uint.MaxValue / 10 || ((uint)answer == uint.MaxValue / 10 && num > '5')) @@ -1175,17 +1023,17 @@ namespace System overflow = true; } answer = answer * 10 + num - '0'; - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; + 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 = source[index]; + num = value[index]; while (IsDigit(num)) { overflow = true; index++; - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + num = value[index]; } goto HasTrailingChars; } @@ -1208,77 +1056,170 @@ namespace System if (IsWhite(num)) { if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit; - for (index++; index < source.Length; index++) + for (index++; index < value.Length; index++) { - if (!IsWhite(source[index])) break; + if (!IsWhite(value[index])) break; } - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; } - if (!TrailingZeros(source, index)) goto FalseExit; + if (!TrailingZeros(value, index)) goto FalseExit; goto DoneAtEndButPotentialOverflow; } - internal static unsafe bool TryParseUInt64(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info, out ulong result) + /// Parses uint limited to styles that make up NumberStyles.HexNumber. + private static bool TryParseUInt32HexNumberStyle( + ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out uint result, ref bool failureIsOverflow) { - NumberBuffer number = default; - result = 0; + Debug.Assert((styles & ~NumberStyles.HexNumber) == 0, "Only handles subsets of HexNumber format"); + Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false"); - if ((style & ~NumberStyles.Integer) == 0) - { - // Optimized path for the common case of anything that's allowed for integer style. - bool overflow = false; - return TryParseUInt64IntegerStyle(s, style, info, out result, ref overflow); - } + if ((uint)value.Length < 1) goto FalseExit; - if (!TryStringToNumber(s, style, ref number, info, false)) + bool overflow = false; + int index = 0; + int num = value[0]; + int numValue = 0; + + // Skip past any whitespace at the beginning. + if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num)) { - return false; + do + { + index++; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; + } + while (IsWhite(num)); } - if ((style & NumberStyles.AllowHexSpecifier) != 0) + int answer = 0; + int[] charToHexLookup = s_charToHexLookup; + + if ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF) { - if (!HexNumberToUInt64(ref number, ref result)) + // Skip past leading zeros. + if (num == '0') { - return false; + 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; + index++; + overflow = true; + 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. Read through any remaining digits. + num = value[index]; + while ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF) + { + index++; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + num = value[index]; + } + goto HasTrailingChars; } - else + + FalseExit: // parsing failed + result = 0; + return false; + + DoneAtEndButPotentialOverflow: + if (overflow) + { + failureIsOverflow = true; + goto FalseExit; + } + DoneAtEnd: + result = (uint)answer; + return true; + + 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 (!NumberToUInt64(ref number, ref result)) + if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit; + for (index++; index < value.Length; index++) { - return false; + if (!IsWhite(value[index])) break; } + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; } - return true; + + if (!TrailingZeros(value, index)) goto FalseExit; + + goto DoneAtEndButPotentialOverflow; + } + + internal static bool 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. + bool overflow = false; + return TryParseUInt64IntegerStyle(value, styles, info, out result, ref overflow); + } + + if ((styles & NumberStyles.AllowHexSpecifier) != 0) + { + bool overflow = false; + return TryParseUInt64HexNumberStyle(value, styles, info, out result, ref overflow); + } + + NumberBuffer number = default; + result = 0; + return + TryStringToNumber(value, styles, ref number, info, false) && + NumberToUInt64(ref number, ref result); } /// Parses ulong limited to styles that make up NumberStyles.Integer. private static bool TryParseUInt64IntegerStyle( - ReadOnlySpan source, NumberStyles styles, NumberFormatInfo info, out ulong result, ref bool failureIsOverflow) + ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out ulong result, ref bool failureIsOverflow) { Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format"); Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false"); - if ((uint)source.Length < 1) goto FalseExit; + if ((uint)value.Length < 1) goto FalseExit; bool overflow = false; bool hasNegativeSign = false; int index = 0; - int num = source[0]; + int num = value[0]; // Skip past any whitespace at the beginning. if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num)) { - index++; - while (true) + do { - if ((uint)index >= (uint)source.Length) goto FalseExit; - num = source[index]; - if (!IsWhite(num)) break; index++; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; } + while (IsWhite(num)); } // Parse leading sign. @@ -1291,33 +1232,33 @@ namespace System if (num == '+') { index++; - if ((uint)index >= (uint)source.Length) goto FalseExit; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; } else if (num == '-') { hasNegativeSign = true; index++; - if ((uint)index >= (uint)source.Length) goto FalseExit; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; } } else { - source = source.Slice(index); + value = value.Slice(index); index = 0; - if (!string.IsNullOrEmpty(positiveSign) && source.StartsWith(positiveSign)) + if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign)) { index += positiveSign.Length; - if ((uint)index >= (uint)source.Length) goto FalseExit; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; } - else if (!string.IsNullOrEmpty(negativeSign) && source.StartsWith(negativeSign)) + else if (!string.IsNullOrEmpty(negativeSign) && value.StartsWith(negativeSign)) { hasNegativeSign = true; index += negativeSign.Length; - if ((uint)index >= (uint)source.Length) goto FalseExit; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto FalseExit; + num = value[index]; } } } @@ -1332,8 +1273,8 @@ namespace System do { index++; - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + num = value[index]; } while (num == '0'); if (!IsDigit(num)) goto HasTrailingChars; } @@ -1343,16 +1284,16 @@ namespace System index++; for (int i = 0; i < 18; i++) // next 18 digits can't overflow { - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; index++; answer = 10 * answer + num - '0'; } // Potential overflow now processing the 20th digit. - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; index++; if ((ulong)answer > ulong.MaxValue / 10 || ((ulong)answer == ulong.MaxValue / 10 && num > '5')) @@ -1360,17 +1301,17 @@ namespace System overflow = true; } answer = answer * 10 + num - '0'; - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; + 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 = source[index]; + num = value[index]; while (IsDigit(num)) { overflow = true; index++; - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; - num = source[index]; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + num = value[index]; } goto HasTrailingChars; } @@ -1393,110 +1334,216 @@ namespace System if (IsWhite(num)) { if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit; - for (index++; index < source.Length; index++) + for (index++; index < value.Length; index++) { - if (!IsWhite(source[index])) break; + if (!IsWhite(value[index])) break; } - if ((uint)index >= (uint)source.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; } - if (!TrailingZeros(source, index)) goto FalseExit; + if (!TrailingZeros(value, index)) goto FalseExit; goto DoneAtEndButPotentialOverflow; } - internal static unsafe decimal ParseDecimal(ReadOnlySpan value, NumberStyles options, NumberFormatInfo numfmt) + /// Parses ulong limited to styles that make up NumberStyles.HexNumber. + private static bool TryParseUInt64HexNumberStyle( + ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out ulong result, ref bool failureIsOverflow) + { + Debug.Assert((styles & ~NumberStyles.HexNumber) == 0, "Only handles subsets of HexNumber format"); + Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false"); + + if ((uint)value.Length < 1) goto FalseExit; + + bool overflow = false; + int index = 0; + int num = value[0]; + int numValue = 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)); + } + + long answer = 0; + int[] charToHexLookup = s_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; + index++; + overflow = true; + 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. Read through any remaining digits. + num = value[index]; + while ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF) + { + index++; + if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + num = value[index]; + } + goto HasTrailingChars; + } + + FalseExit: // parsing failed + result = 0; + return false; + + DoneAtEndButPotentialOverflow: + if (overflow) + { + failureIsOverflow = true; + goto FalseExit; + } + DoneAtEnd: + result = (ulong)answer; + return true; + + 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) { NumberBuffer number = default; decimal result = 0; - StringToNumber(value, options, ref number, numfmt, true); + StringToNumber(value, styles, ref number, info, true); if (!NumberBufferToDecimal(ref number, ref result)) { - throw new OverflowException(SR.Overflow_Decimal); + ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_Decimal)); } return result; } - internal static unsafe double ParseDouble(ReadOnlySpan value, NumberStyles options, NumberFormatInfo numfmt) + internal static double ParseDouble(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { NumberBuffer number = default; double d = 0; - if (!TryStringToNumber(value, options, ref number, numfmt, false)) + if (!TryStringToNumber(value, styles, ref number, info, false)) { //If we failed TryStringToNumber, it may be from one of our special strings. //Check the three with which we're concerned and rethrow if it's not one of //those strings. ReadOnlySpan sTrim = value.Trim(); - if (sTrim.EqualsOrdinal(numfmt.PositiveInfinitySymbol)) + if (sTrim.EqualsOrdinal(info.PositiveInfinitySymbol)) { return double.PositiveInfinity; } - if (sTrim.EqualsOrdinal(numfmt.NegativeInfinitySymbol)) + if (sTrim.EqualsOrdinal(info.NegativeInfinitySymbol)) { return double.NegativeInfinity; } - if (sTrim.EqualsOrdinal(numfmt.NaNSymbol)) + if (sTrim.EqualsOrdinal(info.NaNSymbol)) { return double.NaN; } - throw new FormatException(SR.Format_InvalidString); + ThrowOverflowOrFormatException(overflow: false, null); } if (!NumberBufferToDouble(ref number, ref d)) { - throw new OverflowException(SR.Overflow_Double); + ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_Double)); } return d; } - internal static unsafe float ParseSingle(ReadOnlySpan value, NumberStyles options, NumberFormatInfo numfmt) + internal static float ParseSingle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { NumberBuffer number = default; double d = 0; - if (!TryStringToNumber(value, options, ref number, numfmt, false)) + if (!TryStringToNumber(value, styles, ref number, info, false)) { //If we failed TryStringToNumber, it may be from one of our special strings. //Check the three with which we're concerned and rethrow if it's not one of //those strings. ReadOnlySpan sTrim = value.Trim(); - if (sTrim.EqualsOrdinal(numfmt.PositiveInfinitySymbol)) + if (sTrim.EqualsOrdinal(info.PositiveInfinitySymbol)) { return float.PositiveInfinity; } - if (sTrim.EqualsOrdinal(numfmt.NegativeInfinitySymbol)) + if (sTrim.EqualsOrdinal(info.NegativeInfinitySymbol)) { return float.NegativeInfinity; } - if (sTrim.EqualsOrdinal(numfmt.NaNSymbol)) + if (sTrim.EqualsOrdinal(info.NaNSymbol)) { return float.NaN; } - throw new FormatException(SR.Format_InvalidString); + ThrowOverflowOrFormatException(overflow: false, null); } if (!NumberBufferToDouble(ref number, ref d)) { - throw new OverflowException(SR.Overflow_Single); + ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_Single)); } float castSingle = (float)d; if (float.IsInfinity(castSingle)) { - throw new OverflowException(SR.Overflow_Single); + ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_Single)); } return castSingle; } - internal static unsafe bool TryParseDecimal(ReadOnlySpan value, NumberStyles options, NumberFormatInfo numfmt, out decimal result) + internal static bool TryParseDecimal(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out decimal result) { NumberBuffer number = default; result = 0; - if (!TryStringToNumber(value, options, ref number, numfmt, true)) + if (!TryStringToNumber(value, styles, ref number, info, true)) { return false; } @@ -1508,13 +1555,12 @@ namespace System return true; } - internal static unsafe bool TryParseDouble(ReadOnlySpan value, NumberStyles options, NumberFormatInfo numfmt, out double result) + internal static bool TryParseDouble(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out double result) { NumberBuffer number = default; result = 0; - - if (!TryStringToNumber(value, options, ref number, numfmt, false)) + if (!TryStringToNumber(value, styles, ref number, info, false)) { return false; } @@ -1525,13 +1571,13 @@ namespace System return true; } - internal static unsafe bool TryParseSingle(ReadOnlySpan value, NumberStyles options, NumberFormatInfo numfmt, out float result) + internal static bool TryParseSingle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out float result) { NumberBuffer number = default; result = 0; double d = 0; - if (!TryStringToNumber(value, options, ref number, numfmt, false)) + if (!TryStringToNumber(value, styles, ref number, info, false)) { return false; } @@ -1549,28 +1595,28 @@ namespace System return true; } - private static unsafe void StringToNumber(ReadOnlySpan str, NumberStyles options, ref NumberBuffer number, NumberFormatInfo info, bool parseDecimal) + private static unsafe void StringToNumber(ReadOnlySpan value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info, bool parseDecimal) { Debug.Assert(info != null); - fixed (char* stringPointer = &MemoryMarshal.GetReference(str)) + fixed (char* stringPointer = &MemoryMarshal.GetReference(value)) { char* p = stringPointer; - if (!ParseNumber(ref p, p + str.Length, options, ref number, info, parseDecimal) - || (p - stringPointer < str.Length && !TrailingZeros(str, (int)(p - stringPointer)))) + if (!ParseNumber(ref p, p + value.Length, styles, ref number, info, parseDecimal) + || (p - stringPointer < value.Length && !TrailingZeros(value, (int)(p - stringPointer)))) { - throw new FormatException(SR.Format_InvalidString); + ThrowOverflowOrFormatException(overflow: false, null); } } } - internal static unsafe bool TryStringToNumber(ReadOnlySpan str, NumberStyles options, ref NumberBuffer number, NumberFormatInfo numfmt, bool parseDecimal) + internal static unsafe bool TryStringToNumber(ReadOnlySpan value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info, bool parseDecimal) { - Debug.Assert(numfmt != null); - fixed (char* stringPointer = &MemoryMarshal.GetReference(str)) + Debug.Assert(info != null); + fixed (char* stringPointer = &MemoryMarshal.GetReference(value)) { char* p = stringPointer; - if (!ParseNumber(ref p, p + str.Length, options, ref number, numfmt, parseDecimal) - || (p - stringPointer < str.Length && !TrailingZeros(str, (int)(p - stringPointer)))) + if (!ParseNumber(ref p, p + value.Length, styles, ref number, info, parseDecimal) + || (p - stringPointer < value.Length && !TrailingZeros(value, (int)(p - stringPointer)))) { return false; } @@ -1579,12 +1625,12 @@ namespace System return true; } - private static bool TrailingZeros(ReadOnlySpan s, int index) + 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; i < s.Length; i++) + for (int i = index; i < value.Length; i++) { - if (s[i] != '\0') + if (value[i] != '\0') { return false; } @@ -1593,36 +1639,29 @@ namespace System return true; } - private static unsafe char* MatchChars(char* p, char* pEnd, string str) + private static unsafe char* MatchChars(char* p, char* pEnd, string value) { - fixed (char* stringPointer = str) - { - return MatchChars(p, pEnd, stringPointer); - } - } - - private static unsafe char* MatchChars(char* p, char* pEnd, char* str) - { - Debug.Assert(p != null && pEnd != null && p <= pEnd && str != null); - - if (*str == '\0') - { - return null; - } - - // We only hurt the failure case - // This fix is for French or Kazakh cultures. Since a user cannot type 0xA0 as a - // space character we use 0x20 space character instead to mean the same. - while (true) + Debug.Assert(p != null && pEnd != null && p <= pEnd && value != null); + fixed (char* stringPointer = value) { - char cp = p < pEnd ? *p : '\0'; - if (cp != *str && !(*str == '\u00a0' && cp == '\u0020')) + char* str = stringPointer; + if (*str != '\0') { - break; + // We only hurt the failure case + // This fix is for French or Kazakh cultures. Since a user cannot type 0xA0 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 && !(*str == '\u00a0' && cp == '\u0020')) + { + break; + } + p++; + str++; + if (*str == '\0') return p; + } } - p++; - str++; - if (*str == '\0') return p; } return null; @@ -1632,10 +1671,17 @@ namespace System private static bool IsDigit(int ch) => ((uint)ch - '0') <= 9; + private static void ThrowOverflowOrFormatException(bool overflow, string overflowResourceKey) + { + throw overflow ? + new OverflowException(SR.GetResourceString(overflowResourceKey)) : + (Exception)new FormatException(SR.Format_InvalidString); + } + private static bool NumberBufferToDouble(ref NumberBuffer number, ref double value) { double d = NumberToDouble(ref number); - if (!Double.IsFinite(d)) + if (!double.IsFinite(d)) { value = default; return false; -- 2.34.1