TryParseNumber_char
258 lines
private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info)
private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info)
{
{
Debug.Assert(str != null);
Debug.Assert(str != null);
Debug.Assert(strEnd != null);
Debug.Assert(strEnd != null);
Debug.Assert(str <= strEnd);
Debug.Assert(str <= strEnd);
Debug.Assert((styles & (NumberStyles.AllowHexSpecifier | NumberStyles.AllowBinarySpecifier)) == 0);
Debug.Assert((styles & (NumberStyles.AllowHexSpecifier | NumberStyles.AllowBinarySpecifier)) == 0);
const int StateSign = 0x0001;
const int StateSign = 0x0001;
const int StateParens = 0x0002;
const int StateParens = 0x0002;
const int StateDigits = 0x0004;
const int StateDigits = 0x0004;
const int StateNonZero = 0x0008;
const int StateNonZero = 0x0008;
const int StateDecimal = 0x0010;
const int StateDecimal = 0x0010;
const int StateCurrency = 0x0020;
const int StateCurrency = 0x0020;
Debug.Assert(number.DigitsCount == 0);
Debug.Assert(number.DigitsCount == 0);
Debug.Assert(number.Scale == 0);
Debug.Assert(number.Scale == 0);
Debug.Assert(!number.IsNegative);
Debug.Assert(!number.IsNegative);
Debug.Assert(!number.HasNonZeroTail);
Debug.Assert(!number.HasNonZeroTail);
number.CheckConsistency();
number.CheckConsistency();
string decSep; // decimal separator from NumberFormatInfo.
string decSep; // decimal separator from NumberFormatInfo.
string groupSep; // group separator from NumberFormatInfo.
string groupSep; // group separator from NumberFormatInfo.
string? currSymbol = null; // currency symbol from NumberFormatInfo.
string? currSymbol = null; // currency symbol from NumberFormatInfo.
bool parsingCurrency = false;
bool parsingCurrency = false;
if ((styles & NumberStyles.AllowCurrencySymbol) != 0)
if ((styles & NumberStyles.AllowCurrencySymbol) != 0)
{
{
currSymbol = info.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 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).
// 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;
decSep = info.CurrencyDecimalSeparator;
groupSep = info.CurrencyGroupSeparator;
groupSep = info.CurrencyGroupSeparator;
parsingCurrency = true;
parsingCurrency = true;
}
}
else
else
{
{
decSep = info.NumberDecimalSeparator;
decSep = info.NumberDecimalSeparator;
groupSep = info.NumberGroupSeparator;
groupSep = info.NumberGroupSeparator;
}
}
int state = 0;
int state = 0;
char* p = str;
char* p = str;
char ch = p < strEnd ? *p : '\0';
char ch = p < strEnd ? *p : '\0';
char* next;
char* next;
while (true)
while (true)
{
{
// Eat whitespace unless we've found a sign which isn't followed by a currency symbol.
// 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.
// "-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 (!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 = MatchNegativeSignChars(p, strEnd, info)) != null && (number.IsNegative = true))))
if ((((styles & NumberStyles.AllowLeadingSign) != 0) && (state & StateSign) == 0) && ((next = MatchChars(p, strEnd, info.PositiveSign)) != null || ((next = MatchNegativeSignChars(p, strEnd, info)) != null && (number.IsNegative = true))))
{
{
state |= StateSign;
state |= StateSign;
p = next - 1;
p = next - 1;
}
}
else if (ch == '(' && ((styles & NumberStyles.AllowParentheses) != 0) && ((state & StateSign) == 0))
else if (ch == '(' && ((styles & NumberStyles.AllowParentheses) != 0) && ((state & StateSign) == 0))
{
{
state |= StateSign | StateParens;
state |= StateSign | StateParens;
number.IsNegative = true;
number.IsNegative = true;
}
}
else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null)
else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null)
{
{
state |= StateCurrency;
state |= StateCurrency;
currSymbol = null;
currSymbol = null;
// We already found the currency symbol. There should not be more currency symbols. Set
// 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.
// currSymbol to NULL so that we won't search it again in the later code path.
p = next - 1;
p = next - 1;
}
}
else
else
{
{
break;
break;
}
}
}
}
ch = ++p < strEnd ? *p : '\0';
ch = ++p < strEnd ? *p : '\0';
}
}
int digCount = 0;
int digCount = 0;
int digEnd = 0;
int digEnd = 0;
int maxDigCount = number.Digits.Length - 1;
int maxDigCount = number.Digits.Length - 1;
int numberOfTrailingZeros = 0;
int numberOfTrailingZeros = 0;
while (true)
while (true)
{
{
if (IsDigit(ch))
if (IsDigit(ch))
{
{
state |= StateDigits;
state |= StateDigits;
if (ch != '0' || (state & StateNonZero) != 0)
if (ch != '0' || (state & StateNonZero) != 0)
{
{
if (digCount < maxDigCount)
if (digCount < maxDigCount)
{
{
number.Digits[digCount] = (byte)(ch);
number.Digits[digCount] = (byte)(ch);
if ((ch != '0') || (number.Kind != NumberBufferKind.Integer))
if ((ch != '0') || (number.Kind != NumberBufferKind.Integer))
{
{
digEnd = digCount + 1;
digEnd = digCount + 1;
}
}
}
}
else if (ch != '0')
else if (ch != '0')
{
{
// For decimal and binary floating-point numbers, we only
// For decimal and binary floating-point numbers, we only
// need to store digits up to maxDigCount. However, we still
// need to store digits up to maxDigCount. However, we still
// need to keep track of whether any additional digits past
// need to keep track of whether any additional digits past
// maxDigCount were non-zero, as that can impact rounding
// maxDigCount were non-zero, as that can impact rounding
// for an input that falls evenly between two representable
// for an input that falls evenly between two representable
// results.
// results.
number.HasNonZeroTail = true;
number.HasNonZeroTail = true;
}
}
if ((state & StateDecimal) == 0)
if ((state & StateDecimal) == 0)
{
{
number.Scale++;
number.Scale++;
}
}
if (digCount < maxDigCount)
if (digCount < maxDigCount)
{
{
// Handle a case like "53.0". We need to ignore trailing zeros in the fractional part for floating point numbers, so we keep a count of the number of trailing zeros and update digCount later
// Handle a case like "53.0". We need to ignore trailing zeros in the fractional part for floating point numbers, so we keep a count of the number of trailing zeros and update digCount later
if (ch == '0')
if (ch == '0')
{
{
numberOfTrailingZeros++;
numberOfTrailingZeros++;
}
}
else
else
{
{
numberOfTrailingZeros = 0;
numberOfTrailingZeros = 0;
}
}
}
}
digCount++;
digCount++;
state |= StateNonZero;
state |= StateNonZero;
}
}
else if ((state & StateDecimal) != 0)
else if ((state & StateDecimal) != 0)
{
{
number.Scale--;
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))
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;
state |= StateDecimal;
p = next - 1;
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))
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;
p = next - 1;
}
}
else
else
{
{
break;
break;
}
}
ch = ++p < strEnd ? *p : '\0';
ch = ++p < strEnd ? *p : '\0';
}
}
bool negExp = false;
bool negExp = false;
number.DigitsCount = digEnd;
number.DigitsCount = digEnd;
number.Digits[digEnd] = (byte)('\0');
number.Digits[digEnd] = (byte)('\0');
if ((state & StateDigits) != 0)
if ((state & StateDigits) != 0)
{
{
if ((ch == 'E' || ch == 'e') && ((styles & NumberStyles.AllowExponent) != 0))
if ((ch == 'E' || ch == 'e') && ((styles & NumberStyles.AllowExponent) != 0))
{
{
char* temp = p;
char* temp = p;
ch = ++p < strEnd ? *p : '\0';
ch = ++p < strEnd ? *p : '\0';
if ((next = MatchChars(p, strEnd, info._positiveSign)) != null)
if ((next = MatchChars(p, strEnd, info.PositiveSign)) != null)
{
{
ch = (p = next) < strEnd ? *p : '\0';
ch = (p = next) < strEnd ? *p : '\0';
}
}
else if ((next = MatchNegativeSignChars(p, strEnd, info)) != null)
else if ((next = MatchNegativeSignChars(p, strEnd, info)) != null)
{
{
ch = (p = next) < strEnd ? *p : '\0';
ch = (p = next) < strEnd ? *p : '\0';
negExp = true;
negExp = true;
}
}
if (IsDigit(ch))
if (IsDigit(ch))
{
{
int exp = 0;
int exp = 0;
do
do
{
{
exp = exp * 10 + (ch - '0');
// Check if we are about to overflow past our limit of 9 digits
ch = ++p < strEnd ? *p : '\0';
if (exp >= 100_000_000)
if (exp > 1000)
{
{
exp = 9999;
// Set exp to Int.MaxValue to signify the requested exponent is too large. This will lead to an OverflowException later.
while (IsDigit(ch))
exp = int.MaxValue;
number.Scale = 0;
// Finish parsing the number, a FormatException could still occur later on.
while (char.IsAsciiDigit(ch))
{
{
ch = ++p < strEnd ? *p : '\0';
ch = ++p < strEnd ? *p : '\0';
}
}
break;
}
}
exp = exp * 10 + (ch - '0');
ch = ++p < strEnd ? *p : '\0';
} while (IsDigit(ch));
} while (IsDigit(ch));
if (negExp)
if (negExp)
{
{
exp = -exp;
exp = -exp;
}
}
number.Scale += exp;
number.Scale += exp;
}
}
else
else
{
{
p = temp;
p = temp;
ch = p < strEnd ? *p : '\0';
ch = p < strEnd ? *p : '\0';
}
}
}
}
if (number.Kind == NumberBufferKind.FloatingPoint && !number.HasNonZeroTail)
if (number.Kind == NumberBufferKind.FloatingPoint && !number.HasNonZeroTail)
{
{
// Adjust the number buffer for trailing zeros
// Adjust the number buffer for trailing zeros
int numberOfFractionalDigits = digEnd - number.Scale;
int numberOfFractionalDigits = digEnd - number.Scale;
if (numberOfFractionalDigits > 0)
if (numberOfFractionalDigits > 0)
{
{
numberOfTrailingZeros = Math.Min(numberOfTrailingZeros, numberOfFractionalDigits);
numberOfTrailingZeros = Math.Min(numberOfTrailingZeros, numberOfFractionalDigits);
Debug.Assert(numberOfTrailingZeros >= 0);
Debug.Assert(numberOfTrailingZeros >= 0);
number.DigitsCount = digEnd - numberOfTrailingZeros;
number.DigitsCount = digEnd - numberOfTrailingZeros;
number.Digits[number.DigitsCount] = (byte)('\0');
number.Digits[number.DigitsCount] = (byte)('\0');
}
}
}
}
while (true)
while (true)
{
{
if (!IsWhite(ch) || (styles & NumberStyles.AllowTrailingWhite) == 0)
if (!IsWhite(ch) || (styles & NumberStyles.AllowTrailingWhite) == 0)
{
{
if ((styles & NumberStyles.AllowTrailingSign) != 0 && ((state & StateSign) == 0) && ((next = MatchChars(p, strEnd, info.PositiveSign)) != null || (((next = MatchNegativeSignChars(p, strEnd, info)) != null) && (number.IsNegative = true))))
if ((styles & NumberStyles.AllowTrailingSign) != 0 && ((state & StateSign) == 0) && ((next = MatchChars(p, strEnd, info.PositiveSign)) != null || (((next = MatchNegativeSignChars(p, strEnd, info)) != null) && (number.IsNegative = true))))
{
{
state |= StateSign;
state |= StateSign;
p = next - 1;
p = next - 1;
}
}
else if (ch == ')' && ((state & StateParens) != 0))
else if (ch == ')' && ((state & StateParens) != 0))
{
{
state &= ~StateParens;
state &= ~StateParens;
}
}
else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null)
else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null)
{
{
currSymbol = null;
currSymbol = null;
p = next - 1;
p = next - 1;
}
}
else
else
{
{
break;
break;
}
}
}
}
ch = ++p < strEnd ? *p : '\0';
ch = ++p < strEnd ? *p : '\0';
}
}
if ((state & StateParens) == 0)
if ((state & StateParens) == 0)
{
{
if ((state & StateNonZero) == 0)
if ((state & StateNonZero) == 0)
{
{
if (number.Kind != NumberBufferKind.Decimal)
if (number.Kind != NumberBufferKind.Decimal)
{
{
number.Scale = 0;
number.Scale = 0;
}
}
if ((number.Kind == NumberBufferKind.Integer) && (state & StateDecimal) == 0)
if ((number.Kind == NumberBufferKind.Integer) && (state & StateDecimal) == 0)
{
{
number.IsNegative = false;
number.IsNegative = false;
}
}
}
}
str = p;
str = p;
return true;
return true;
}
}
}
}
str = p;
str = p;
return false;
return false;
}
}