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