Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/corert.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/System.Private.CoreLib/shared/System/Number.Parsing.cs')
-rw-r--r--src/System.Private.CoreLib/shared/System/Number.Parsing.cs1489
1 files changed, 1165 insertions, 324 deletions
diff --git a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs
index 46951094e..7c7866a90 100644
--- a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs
+++ b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs
@@ -5,6 +5,11 @@
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
+#if MONO
+using System.Runtime.CompilerServices;
+#else
+using Internal.Runtime.CompilerServices;
+#endif
namespace System
{
@@ -30,129 +35,26 @@ namespace System
private const int Int64Precision = 19;
private const int UInt64Precision = 20;
- private static bool HexNumberToInt32(ref NumberBuffer number, ref int value)
+ /// <summary>256-element map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit.</summary>
+ 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 (*p >= '0' && *p <= '9')
- {
- 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 (*p >= '0' && *p <= '9')
- {
- 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 +75,7 @@ namespace System
n *= 10;
if (*p != '\0')
{
- n += (int)(*p++ - '0');
+ n += (*p++ - '0');
}
}
if (number.sign)
@@ -214,7 +116,7 @@ namespace System
n *= 10;
if (*p != '\0')
{
- n += (int)(*p++ - '0');
+ n += (*p++ - '0');
}
}
if (number.sign)
@@ -300,104 +202,147 @@ namespace System
return true;
}
- internal unsafe static int ParseInt32(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info)
+ internal static int ParseInt32(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info)
{
- NumberBuffer number = default;
- int i = 0;
-
- StringToNumber(s, style, ref number, info, false);
-
- if ((style & NumberStyles.AllowHexSpecifier) != 0)
+ if ((styles & ~NumberStyles.Integer) == 0)
{
- if (!HexNumberToInt32(ref number, ref i))
+ // Optimized path for the common case of anything that's allowed for integer style.
+ bool overflow = false;
+ if (!TryParseInt32IntegerStyle(value, styles, info, out int intResult, ref overflow))
{
- throw new OverflowException(SR.Overflow_Int32);
+ ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_Int32));
}
+ return intResult;
}
- else
+
+ if ((styles & NumberStyles.AllowHexSpecifier) != 0)
{
- if (!NumberToInt32(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;
}
- return i;
- }
- internal unsafe static long ParseInt64(ReadOnlySpan<char> value, NumberStyles options, NumberFormatInfo numfmt)
- {
NumberBuffer number = default;
- long i = 0;
-
- StringToNumber(value, options, ref number, numfmt, false);
+ int result = 0;
+ StringToNumber(value, styles, ref number, info, false);
+ if (!NumberToInt32(ref number, ref result))
+ {
+ ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_Int32));
+ }
+ return result;
+ }
- if ((options & NumberStyles.AllowHexSpecifier) != 0)
+ internal static long ParseInt64(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info)
+ {
+ if ((styles & ~NumberStyles.Integer) == 0)
{
- if (!HexNumberToInt64(ref number, ref i))
+ // Optimized path for the common case of anything that's allowed for integer style.
+ bool overflow = false;
+ if (!TryParseInt64IntegerStyle(value, styles, info, out long intResult, ref overflow))
{
- throw new OverflowException(SR.Overflow_Int64);
+ ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_Int64));
}
+ return intResult;
}
- else
+
+ if ((styles & NumberStyles.AllowHexSpecifier) != 0)
{
- if (!NumberToInt64(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;
}
- return i;
- }
- internal unsafe static uint ParseUInt32(ReadOnlySpan<char> value, NumberStyles options, NumberFormatInfo numfmt)
- {
NumberBuffer number = default;
- uint i = 0;
+ long result = 0;
+ StringToNumber(value, styles, ref number, info, false);
+ if (!NumberToInt64(ref number, ref result))
+ {
+ ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_Int64));
+ }
+ return result;
+ }
- StringToNumber(value, options, ref number, numfmt, false);
+ internal static uint ParseUInt32(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info)
+ {
+ uint result = 0;
- if ((options & NumberStyles.AllowHexSpecifier) != 0)
+ if ((styles & ~NumberStyles.Integer) == 0)
{
- if (!HexNumberToUInt32(ref number, ref i))
+ // Optimized path for the common case of anything that's allowed for integer style.
+ bool overflow = false;
+ if (!TryParseUInt32IntegerStyle(value, styles, info, out result, ref overflow))
{
- throw new OverflowException(SR.Overflow_UInt32);
+ ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_UInt32));
}
+ return result;
}
- else
+
+ if ((styles & NumberStyles.AllowHexSpecifier) != 0)
{
- if (!NumberToUInt32(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;
}
- return i;
+ NumberBuffer number = default;
+ StringToNumber(value, styles, ref number, info, false);
+ if (!NumberToUInt32(ref number, ref result))
+ {
+ ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_UInt32));
+ }
+ return result;
}
- internal unsafe static ulong ParseUInt64(ReadOnlySpan<char> value, NumberStyles options, NumberFormatInfo numfmt)
+ internal static ulong ParseUInt64(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info)
{
- NumberBuffer number = default;
- ulong i = 0;
+ ulong result = 0;
- StringToNumber(value, options, ref number, numfmt, false);
- if ((options & NumberStyles.AllowHexSpecifier) != 0)
+ if ((styles & ~NumberStyles.Integer) == 0)
{
- if (!HexNumberToUInt64(ref number, ref i))
+ // Optimized path for the common case of anything that's allowed for integer style.
+ bool overflow = false;
+ if (!TryParseUInt64IntegerStyle(value, styles, info, out result, ref overflow))
{
- throw new OverflowException(SR.Overflow_UInt64);
+ ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_UInt64));
}
+ return result;
}
- else
+
+ if ((styles & NumberStyles.AllowHexSpecifier) != 0)
{
- if (!NumberToUInt64(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;
}
- return i;
+
+ NumberBuffer number = default;
+ StringToNumber(value, styles, ref number, info, false);
+ if (!NumberToUInt64(ref number, ref result))
+ {
+ ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_UInt64));
+ }
+ return result;
}
- private unsafe static bool ParseNumber(ref char* str, 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;
const int StateDigits = 0x0004;
@@ -412,44 +357,44 @@ 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;
char* p = str;
- char ch = *p;
+ 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) || (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, numfmt.PositiveSign)) != null || ((next = MatchChars(p, 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;
}
- else if (currSymbol != null && (next = MatchChars(p, currSymbol)) != null)
+ else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null)
{
state |= StateCurrency;
currSymbol = null;
@@ -462,13 +407,13 @@ namespace System
break;
}
}
- ch = *++p;
+ ch = ++p < strEnd ? *p : '\0';
}
int digCount = 0;
int digEnd = 0;
while (true)
{
- if ((ch >= '0' && ch <= '9') || (((options & NumberStyles.AllowHexSpecifier) != 0) && ((ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'))))
+ if (IsDigit(ch))
{
state |= StateDigits;
@@ -493,12 +438,12 @@ namespace System
number.scale--;
}
}
- else if (((options & NumberStyles.AllowDecimalPoint) != 0) && ((state & StateDecimal) == 0) && ((next = MatchChars(p, decSep)) != null || ((parsingCurrency) && (state & StateCurrency) == 0) && (next = MatchChars(p, 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, groupSep)) != null || ((parsingCurrency) && (state & StateCurrency) == 0) && (next = MatchChars(p, 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;
}
@@ -506,7 +451,7 @@ namespace System
{
break;
}
- ch = *++p;
+ ch = ++p < strEnd ? *p : '\0';
}
bool negExp = false;
@@ -514,35 +459,35 @@ 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;
- if ((next = MatchChars(p, numfmt.positiveSign)) != null)
+ ch = ++p < strEnd ? *p : '\0';
+ if ((next = MatchChars(p, strEnd, info.positiveSign)) != null)
{
- ch = *(p = next);
+ ch = (p = next) < strEnd ? *p : '\0';
}
- else if ((next = MatchChars(p, numfmt.negativeSign)) != null)
+ else if ((next = MatchChars(p, strEnd, info.negativeSign)) != null)
{
- ch = *(p = next);
+ ch = (p = next) < strEnd ? *p : '\0';
negExp = true;
}
- if (ch >= '0' && ch <= '9')
+ if (IsDigit(ch))
{
int exp = 0;
do
{
exp = exp * 10 + (ch - '0');
- ch = *++p;
+ ch = ++p < strEnd ? *p : '\0';
if (exp > 1000)
{
exp = 9999;
- while (ch >= '0' && ch <= '9')
+ while (IsDigit(ch))
{
- ch = *++p;
+ ch = ++p < strEnd ? *p : '\0';
}
}
- } while (ch >= '0' && ch <= '9');
+ } while (IsDigit(ch));
if (negExp)
{
exp = -exp;
@@ -552,14 +497,14 @@ namespace System
else
{
p = temp;
- ch = *p;
+ ch = p < strEnd ? *p : '\0';
}
}
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, numfmt.PositiveSign)) != null || (((next = MatchChars(p, 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;
@@ -568,7 +513,7 @@ namespace System
{
state &= ~StateParens;
}
- else if (currSymbol != null && (next = MatchChars(p, currSymbol)) != null)
+ else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null)
{
currSymbol = null;
p = next - 1;
@@ -578,7 +523,7 @@ namespace System
break;
}
}
- ch = *++p;
+ ch = ++p < strEnd ? *p : '\0';
}
if ((state & StateParens) == 0)
{
@@ -601,206 +546,1109 @@ namespace System
return false;
}
- internal unsafe static bool TryParseInt32(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info, out int result)
+ internal static bool TryParseInt32(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out int result)
{
- NumberBuffer number = default;
+ if ((styles & ~NumberStyles.Integer) == 0)
+ {
+ // Optimized path for the common case of anything that's allowed for integer style.
+ bool overflow = false;
+ return TryParseInt32IntegerStyle(value, styles, info, out result, ref overflow);
+ }
+
result = 0;
- if (!TryStringToNumber(s, style, ref number, info, false))
+ if ((styles & NumberStyles.AllowHexSpecifier) != 0)
{
- return false;
+ bool overflow = false;
+ return TryParseUInt32HexNumberStyle(value, styles, info, out Unsafe.As<int, uint>(ref result), ref overflow);
}
- if ((style & NumberStyles.AllowHexSpecifier) != 0)
+ NumberBuffer number = default;
+ return
+ TryStringToNumber(value, styles, ref number, info, false) &&
+ NumberToInt32(ref number, ref result);
+ }
+
+ /// <summary>Parses int limited to styles that make up NumberStyles.Integer.</summary>
+ private static bool TryParseInt32IntegerStyle(ReadOnlySpan<char> 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)value.Length < 1) goto FalseExit;
+
+ bool overflow = false;
+ int sign = 1;
+ int index = 0;
+ int num = value[0];
+
+ // Skip past any whitespace at the beginning.
+ if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num))
{
- if (!HexNumberToInt32(ref number, ref result))
+ do
{
- return false;
+ index++;
+ if ((uint)index >= (uint)value.Length) goto FalseExit;
+ num = value[index];
}
+ while (IsWhite(num));
}
- else
+
+ // Parse leading sign.
+ if ((styles & NumberStyles.AllowLeadingSign) != 0)
{
- if (!NumberToInt32(ref number, ref result))
+ string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign;
+
+ if (positiveSign == "+" && negativeSign == "-")
{
- return false;
+ 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;
+ 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];
+ }
+ }
+ }
+
+ int answer = 0;
+
+ if (IsDigit(num))
+ {
+ // Skip past leading zeros.
+ if (num == '0')
+ {
+ do
+ {
+ index++;
+ if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
+ 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 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)value.Length) goto DoneAtEndButPotentialOverflow;
+ num = value[index];
+ if (!IsDigit(num)) goto HasTrailingChars;
+ index++;
+ if (answer > int.MaxValue / 10)
+ {
+ overflow = true;
+ }
+ answer = answer * 10 + num - '0';
+ if ((uint)answer > (uint)int.MaxValue + (-1 * sign + 1) / 2)
+ {
+ 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.
+ num = value[index];
+ while (IsDigit(num))
+ {
+ overflow = true;
+ 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;
}
+ result = answer * sign;
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 unsafe static bool TryParseInt64(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info, out long result)
+ /// <summary>Parses long inputs limited to styles that make up NumberStyles.Integer.</summary>
+ private static bool TryParseInt64IntegerStyle(
+ ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out long result, ref bool failureIsOverflow)
{
- NumberBuffer number = default;
- result = 0;
+ Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format");
+ Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false");
+
+ if ((uint)value.Length < 1) goto FalseExit;
- if (!TryStringToNumber(s, style, ref number, info, false))
+ bool overflow = false;
+ int sign = 1;
+ int index = 0;
+ int num = value[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)
+ // Parse leading sign.
+ if ((styles & NumberStyles.AllowLeadingSign) != 0)
{
- if (!HexNumberToInt64(ref number, ref result))
+ string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign;
+
+ if (positiveSign == "+" && negativeSign == "-")
{
- return false;
+ 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;
+ 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];
+ }
}
}
- else
+
+ long answer = 0;
+
+ if (IsDigit(num))
{
- if (!NumberToInt64(ref number, ref result))
+ // Skip past leading zeros.
+ if (num == '0')
{
- return false;
+ do
+ {
+ index++;
+ if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
+ 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 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)value.Length) goto DoneAtEndButPotentialOverflow;
+ num = value[index];
+ if (!IsDigit(num)) goto HasTrailingChars;
+ index++;
+ if (answer > long.MaxValue / 10)
+ {
+ overflow = true;
+ }
+ answer = answer * 10 + num - '0';
+ if ((ulong)answer > (ulong)long.MaxValue + (ulong)((-1 * sign + 1) / 2)) // + sign => 0, - sign => 1
+ {
+ 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.
+ num = value[index];
+ while (IsDigit(num))
+ {
+ overflow = true;
+ 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;
}
+ result = answer * sign;
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 unsafe static bool TryParseUInt32(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info, out uint result)
+ internal static bool TryParseInt64(ReadOnlySpan<char> 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.
+ bool overflow = false;
+ return TryParseInt64IntegerStyle(value, styles, info, out result, ref overflow);
+ }
+
+ result = 0;
+
+ if ((styles & NumberStyles.AllowHexSpecifier) != 0)
+ {
+ bool overflow = false;
+ return TryParseUInt64HexNumberStyle(value, styles, info, out Unsafe.As<long, ulong>(ref result), ref overflow);
+ }
+
+ NumberBuffer number = default;
+ return
+ TryStringToNumber(value, styles, ref number, info, false) &&
+ NumberToInt64(ref number, ref result);
+ }
+
+ internal static bool TryParseUInt32(ReadOnlySpan<char> 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(value, styles, info, out result, ref overflow);
+ }
+
+ if ((styles & NumberStyles.AllowHexSpecifier) != 0)
+ {
+ bool overflow = false;
+ return TryParseUInt32HexNumberStyle(value, styles, info, out result, ref overflow);
+ }
+
NumberBuffer number = default;
result = 0;
+ return
+ TryStringToNumber(value, styles, ref number, info, false) &&
+ NumberToUInt32(ref number, ref result);
+ }
+
+ /// <summary>Parses uint limited to styles that make up NumberStyles.Integer.</summary>
+ private static bool TryParseUInt32IntegerStyle(
+ ReadOnlySpan<char> 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)value.Length < 1) goto FalseExit;
- if (!TryStringToNumber(s, style, ref number, info, false))
+ bool overflow = false;
+ bool hasNegativeSign = false;
+ int index = 0;
+ int num = value[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)
+ // Parse leading sign.
+ if ((styles & NumberStyles.AllowLeadingSign) != 0)
{
- if (!HexNumberToUInt32(ref number, ref result))
+ string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign;
+
+ if (positiveSign == "+" && negativeSign == "-")
{
- return false;
+ if (num == '+')
+ {
+ index++;
+ if ((uint)index >= (uint)value.Length) goto FalseExit;
+ num = value[index];
+ }
+ else if (num == '-')
+ {
+ hasNegativeSign = true;
+ index++;
+ if ((uint)index >= (uint)value.Length) goto FalseExit;
+ num = value[index];
+ }
+ }
+ else
+ {
+ value = value.Slice(index);
+ index = 0;
+ 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))
+ {
+ hasNegativeSign = true;
+ index += negativeSign.Length;
+ if ((uint)index >= (uint)value.Length) goto FalseExit;
+ num = value[index];
+ }
}
}
- else
+
+ int answer = 0;
+
+ if (IsDigit(num))
{
- if (!NumberToUInt32(ref number, ref result))
+ // Skip past leading zeros.
+ if (num == '0')
{
- return false;
+ do
+ {
+ index++;
+ if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
+ 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 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)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'))
+ {
+ overflow = true;
}
+ 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 DoneAtEndButPotentialOverflow;
+ num = value[index];
+ }
+ goto HasTrailingChars;
}
+
+ FalseExit: // parsing failed
+ result = 0;
+ return false;
+
+ DoneAtEndButPotentialOverflow:
+ if (overflow || (hasNegativeSign && answer != 0))
+ {
+ failureIsOverflow = true;
+ goto FalseExit;
+ }
+ 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 ((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 unsafe static bool TryParseUInt64(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info, out ulong result)
+ /// <summary>Parses uint limited to styles that make up NumberStyles.HexNumber.</summary>
+ private static bool TryParseUInt32HexNumberStyle(
+ ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out uint 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));
+ }
+
+ int 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 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;
+ }
+
+ 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 ((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 bool TryParseUInt64(ReadOnlySpan<char> 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);
+ }
- if (!TryStringToNumber(s, style, ref number, info, false))
+ /// <summary>Parses ulong limited to styles that make up NumberStyles.Integer.</summary>
+ private static bool TryParseUInt64IntegerStyle(
+ ReadOnlySpan<char> 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)value.Length < 1) goto FalseExit;
+
+ bool overflow = false;
+ bool hasNegativeSign = false;
+ int index = 0;
+ int num = value[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)
+ // Parse leading sign.
+ if ((styles & NumberStyles.AllowLeadingSign) != 0)
{
- if (!HexNumberToUInt64(ref number, ref result))
+ string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign;
+
+ if (positiveSign == "+" && negativeSign == "-")
{
- return false;
+ if (num == '+')
+ {
+ index++;
+ if ((uint)index >= (uint)value.Length) goto FalseExit;
+ num = value[index];
+ }
+ else if (num == '-')
+ {
+ hasNegativeSign = true;
+ index++;
+ if ((uint)index >= (uint)value.Length) goto FalseExit;
+ num = value[index];
+ }
+ }
+ else
+ {
+ value = value.Slice(index);
+ index = 0;
+ 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))
+ {
+ hasNegativeSign = true;
+ index += negativeSign.Length;
+ if ((uint)index >= (uint)value.Length) goto FalseExit;
+ num = value[index];
+ }
}
}
- else
+
+ long answer = 0;
+
+ if (IsDigit(num))
{
- if (!NumberToUInt64(ref number, ref result))
+ // Skip past leading zeros.
+ if (num == '0')
{
- return false;
+ do
+ {
+ index++;
+ if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow;
+ 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 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';
}
+
+ // Potential overflow now processing the 20th digit.
+ 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'))
+ {
+ overflow = true;
+ }
+ 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 DoneAtEndButPotentialOverflow;
+ num = value[index];
+ }
+ goto HasTrailingChars;
+ }
+
+ FalseExit: // parsing failed
+ result = 0;
+ return false;
+
+ DoneAtEndButPotentialOverflow:
+ if (overflow || (hasNegativeSign && answer != 0))
+ {
+ failureIsOverflow = true;
+ goto FalseExit;
+ }
+ 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;
+ }
+
+ /// <summary>Parses ulong limited to styles that make up NumberStyles.HexNumber.</summary>
+ private static bool TryParseUInt64HexNumberStyle(
+ ReadOnlySpan<char> 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 unsafe static decimal ParseDecimal(ReadOnlySpan<char> value, NumberStyles options, NumberFormatInfo numfmt)
+ internal static decimal ParseDecimal(ReadOnlySpan<char> 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 unsafe static double ParseDouble(ReadOnlySpan<char> value, NumberStyles options, NumberFormatInfo numfmt)
+ private static unsafe bool NumberBufferToDecimal(ref NumberBuffer number, ref decimal value)
+ {
+ char* p = number.digits;
+ int e = number.scale;
+ bool sign = number.sign;
+ 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 the next digit is 5, round up if the number is odd or any following digit is non-zero
+ if (c == '5' && (low64 & 1) == 0)
+ {
+ c = *++p;
+ int count = 20; // Look at the next 20 digits to check to round
+ while (c == '0' && count != 0)
+ {
+ c = *++p;
+ count--;
+ }
+ if (c == 0 || count == 0)
+ goto NoRounding;// Do nothing
+ }
+
+ 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<char> 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<char> sTrim = value.Trim();
- if (StringSpanHelpers.Equals(sTrim, numfmt.PositiveInfinitySymbol))
+ if (sTrim.EqualsOrdinal(info.PositiveInfinitySymbol))
{
return double.PositiveInfinity;
}
- if (StringSpanHelpers.Equals(sTrim, numfmt.NegativeInfinitySymbol))
+ if (sTrim.EqualsOrdinal(info.NegativeInfinitySymbol))
{
return double.NegativeInfinity;
}
- if (StringSpanHelpers.Equals(sTrim, 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 unsafe static float ParseSingle(ReadOnlySpan<char> value, NumberStyles options, NumberFormatInfo numfmt)
+ internal static float ParseSingle(ReadOnlySpan<char> 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<char> sTrim = value.Trim();
- if (StringSpanHelpers.Equals(sTrim, numfmt.PositiveInfinitySymbol))
+ if (sTrim.EqualsOrdinal(info.PositiveInfinitySymbol))
{
return float.PositiveInfinity;
}
- if (StringSpanHelpers.Equals(sTrim, numfmt.NegativeInfinitySymbol))
+ if (sTrim.EqualsOrdinal(info.NegativeInfinitySymbol))
{
return float.NegativeInfinity;
}
- if (StringSpanHelpers.Equals(sTrim, 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 unsafe static bool TryParseDecimal(ReadOnlySpan<char> value, NumberStyles options, NumberFormatInfo numfmt, out decimal result)
+ internal static bool TryParseDecimal(ReadOnlySpan<char> 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;
}
@@ -812,13 +1660,12 @@ namespace System
return true;
}
- internal unsafe static bool TryParseDouble(ReadOnlySpan<char> value, NumberStyles options, NumberFormatInfo numfmt, out double result)
+ internal static bool TryParseDouble(ReadOnlySpan<char> 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;
}
@@ -829,13 +1676,13 @@ namespace System
return true;
}
- internal unsafe static bool TryParseSingle(ReadOnlySpan<char> value, NumberStyles options, NumberFormatInfo numfmt, out float result)
+ internal static bool TryParseSingle(ReadOnlySpan<char> 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;
}
@@ -853,28 +1700,28 @@ namespace System
return true;
}
- private static unsafe void StringToNumber(ReadOnlySpan<char> str, NumberStyles options, ref NumberBuffer number, NumberFormatInfo info, bool parseDecimal)
+ private static unsafe void StringToNumber(ReadOnlySpan<char> 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, 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<char> str, NumberStyles options, ref NumberBuffer number, NumberFormatInfo numfmt, bool parseDecimal)
+ internal static unsafe bool TryStringToNumber(ReadOnlySpan<char> 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, 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;
}
@@ -883,12 +1730,12 @@ namespace System
return true;
}
- private static bool TrailingZeros(ReadOnlySpan<char> s, int index)
+ private static bool TrailingZeros(ReadOnlySpan<char> 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;
}
@@ -897,68 +1744,62 @@ namespace System
return true;
}
- private unsafe static char* MatchChars(char* p, string str)
+ private static unsafe char* MatchChars(char* p, char* pEnd, string value)
{
- fixed (char* stringPointer = str)
+ Debug.Assert(p != null && pEnd != null && p <= pEnd && value != null);
+ fixed (char* stringPointer = value)
{
- return MatchChars(p, stringPointer);
+ 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 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;
+ }
+ }
}
+
+ return null;
}
- private unsafe static char* MatchChars(char* p, char* str)
- {
- Debug.Assert(p != null && str != null);
+ private static bool IsWhite(int ch) => ch == 0x20 || ((uint)(ch - 0x09) <= (0x0D - 0x09));
- 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 (*p == *str || (*str == '\u00a0' && *p == '\u0020'))
- {
- p++;
- str++;
- if (*str == '\0') return p;
- }
+ private static bool IsDigit(int ch) => ((uint)ch - '0') <= 9;
- return null;
+ private static void ThrowOverflowOrFormatException(bool overflow, string overflowResourceKey)
+ {
+ throw overflow ?
+ new OverflowException(SR.GetResourceString(overflowResourceKey)) :
+ (Exception)new FormatException(SR.Format_InvalidString);
}
- private static bool IsWhite(char ch) => ch == 0x20 || (ch >= 0x09 && ch <= 0x0D);
-
private static bool NumberBufferToDouble(ref NumberBuffer number, ref double value)
{
double d = NumberToDouble(ref number);
- uint e = DoubleHelper.Exponent(d);
- ulong m = DoubleHelper.Mantissa(d);
-
- if (e == 0x7FF)
+ if (!double.IsFinite(d))
{
+ value = default;
return false;
}
- if (e == 0 && m == 0)
+ if (d == 0.0)
{
- d = 0;
+ // normalize -0.0 to 0.0
+ d = 0.0;
}
value = d;
return true;
}
-
- private static class DoubleHelper
- {
- public static unsafe uint Exponent(double d) =>
- (*((uint*)&d + 1) >> 20) & 0x000007ff;
-
- public static unsafe ulong Mantissa(double d) =>
- *((ulong*)&d) & 0x000fffffffffffff;
-
- public static unsafe bool Sign(double d) =>
- (*((uint*)&d + 1) >> 31) != 0;
- }
}
}