Optimized decimal parsing (#1204)
This commit is contained in:
committed by
James Newton-King
parent
1a67b34deb
commit
4600bc54b8
@@ -158,6 +158,82 @@ namespace Newtonsoft.Json.Tests.Utilities
|
||||
AssertDoubleTryParse("4.94065645841247E+555", ParseResult.Overflow, null);
|
||||
}
|
||||
|
||||
private void AssertDecimalTryParse(string s, ParseResult expectedResult, decimal? expectedValue)
|
||||
{
|
||||
decimal d;
|
||||
char[] c = s.ToCharArray();
|
||||
ParseResult result = ConvertUtils.DecimalTryParse(c, 0, c.Length, out d);
|
||||
|
||||
decimal d2;
|
||||
bool result2 = decimal.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out d2)
|
||||
&& !s.StartsWith(".")
|
||||
&& !s.EndsWith(".")
|
||||
&& !(s.StartsWith("0") && s.Length > 1 && !s.StartsWith("0.") && !s.StartsWith("0e", StringComparison.OrdinalIgnoreCase))
|
||||
&& !(s.StartsWith("-0") && s.Length > 2 && !s.StartsWith("-0.") && !s.StartsWith("-0e", StringComparison.OrdinalIgnoreCase))
|
||||
&& s.IndexOf(".e", StringComparison.OrdinalIgnoreCase) == -1;
|
||||
|
||||
Assert.AreEqual(expectedResult, result);
|
||||
Assert.AreEqual(expectedResult == ParseResult.Success, result2);
|
||||
|
||||
if (result2)
|
||||
{
|
||||
Assert.IsTrue(expectedValue.HasValue);
|
||||
|
||||
Assert.AreEqual(expectedValue.Value, d, "Input string: " + s);
|
||||
|
||||
Assert.AreEqual(expectedValue.Value, d2, "DecimalTryParse result is not equal to decimal.Parse. Input string: " + s);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DecimalTryParse()
|
||||
{
|
||||
AssertDecimalTryParse("1", ParseResult.Success, 1M);
|
||||
AssertDecimalTryParse("1E1", ParseResult.Success, 10M);
|
||||
AssertDecimalTryParse("1E28", ParseResult.Success, 10000000000000000000000000000M);
|
||||
|
||||
AssertDecimalTryParse("1.2345678901234567890123456789", ParseResult.Success, 1.2345678901234567890123456789M);
|
||||
|
||||
AssertDecimalTryParse(decimal.MaxValue.ToString(), ParseResult.Success, decimal.MaxValue);
|
||||
AssertDecimalTryParse(decimal.MinValue.ToString(), ParseResult.Success, decimal.MinValue);
|
||||
|
||||
AssertDecimalTryParse("12345678901234567890123456789", ParseResult.Success, 12345678901234567890123456789M);
|
||||
AssertDecimalTryParse("12345678901234567890123456789.4", ParseResult.Success, 12345678901234567890123456789M);
|
||||
AssertDecimalTryParse("12345678901234567890123456789.5", ParseResult.Success, 12345678901234567890123456790M);
|
||||
AssertDecimalTryParse("-12345678901234567890123456789", ParseResult.Success, -12345678901234567890123456789M);
|
||||
AssertDecimalTryParse("-12345678901234567890123456789.4", ParseResult.Success, -12345678901234567890123456789M);
|
||||
AssertDecimalTryParse("-12345678901234567890123456789.5", ParseResult.Success, -12345678901234567890123456790M);
|
||||
|
||||
AssertDecimalTryParse("1.2345678901234567890123456789e-25", ParseResult.Success, 0.0000000000000000000000001235M);
|
||||
AssertDecimalTryParse("1.2345678901234567890123456789e-26", ParseResult.Success, 0.0000000000000000000000000123M);
|
||||
AssertDecimalTryParse("1.2345678901234567890123456789e-28", ParseResult.Success, 0.0000000000000000000000000001M);
|
||||
AssertDecimalTryParse("1.2345678901234567890123456789e-29", ParseResult.Success, 0M);
|
||||
AssertDecimalTryParse("1E-999", ParseResult.Success, 0M);
|
||||
|
||||
AssertDecimalTryParse("1E+29", ParseResult.Overflow, null);
|
||||
AssertDecimalTryParse("-1E+29", ParseResult.Overflow, null);
|
||||
|
||||
AssertDecimalTryParse("01E28", ParseResult.Invalid, null);
|
||||
AssertDecimalTryParse("-01E28", ParseResult.Invalid, null);
|
||||
AssertDecimalTryParse("1.", ParseResult.Invalid, null);
|
||||
AssertDecimalTryParse("0.", ParseResult.Invalid, null);
|
||||
AssertDecimalTryParse(".1E23", ParseResult.Invalid, null);
|
||||
AssertDecimalTryParse("1..1E23", ParseResult.Invalid, null);
|
||||
AssertDecimalTryParse("1.E23", ParseResult.Invalid, null);
|
||||
AssertDecimalTryParse("1E2.3", ParseResult.Invalid, null);
|
||||
AssertDecimalTryParse("1EE-10", ParseResult.Invalid, null);
|
||||
AssertDecimalTryParse("1E-1-0", ParseResult.Invalid, null);
|
||||
AssertDecimalTryParse("1-E10", ParseResult.Invalid, null);
|
||||
AssertDecimalTryParse("", ParseResult.Invalid, null);
|
||||
AssertDecimalTryParse("5.1231231E", ParseResult.Invalid, null);
|
||||
AssertDecimalTryParse("1E+23i", ParseResult.Invalid, null);
|
||||
AssertDecimalTryParse("1EE+23", ParseResult.Invalid, null);
|
||||
AssertDecimalTryParse("1E++23", ParseResult.Invalid, null);
|
||||
AssertDecimalTryParse("1E--23", ParseResult.Invalid, null);
|
||||
AssertDecimalTryParse("E23", ParseResult.Invalid, null);
|
||||
AssertDecimalTryParse("00", ParseResult.Invalid, null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Int64TryParse()
|
||||
{
|
||||
|
||||
@@ -2019,10 +2019,9 @@ namespace Newtonsoft.Json
|
||||
}
|
||||
else
|
||||
{
|
||||
string number = _stringReference.ToString();
|
||||
|
||||
decimal value;
|
||||
if (decimal.TryParse(number, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out value))
|
||||
ParseResult parseResult = ConvertUtils.DecimalTryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out value);
|
||||
if (parseResult == ParseResult.Success)
|
||||
{
|
||||
numberValue = value;
|
||||
}
|
||||
@@ -2123,18 +2122,17 @@ namespace Newtonsoft.Json
|
||||
}
|
||||
else
|
||||
{
|
||||
string number = _stringReference.ToString();
|
||||
|
||||
if (_floatParseHandling == FloatParseHandling.Decimal)
|
||||
{
|
||||
decimal d;
|
||||
if (decimal.TryParse(number, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out d))
|
||||
parseResult = ConvertUtils.DecimalTryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out d);
|
||||
if (parseResult == ParseResult.Success)
|
||||
{
|
||||
numberValue = d;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw ThrowReaderError("Input string '{0}' is not a valid decimal.".FormatWith(CultureInfo.InvariantCulture, number));
|
||||
throw ThrowReaderError("Input string '{0}' is not a valid decimal.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString()));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -2147,7 +2145,7 @@ namespace Newtonsoft.Json
|
||||
}
|
||||
else
|
||||
{
|
||||
throw ThrowReaderError("Input string '{0}' is not a valid number.".FormatWith(CultureInfo.InvariantCulture, number));
|
||||
throw ThrowReaderError("Input string '{0}' is not a valid number.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1357,6 +1357,265 @@ namespace Newtonsoft.Json.Utilities
|
||||
return double.IsInfinity(value) ? ParseResult.Overflow : ParseResult.Success;
|
||||
}
|
||||
|
||||
public static ParseResult DecimalTryParse(char[] chars, int start, int length, out decimal value)
|
||||
{
|
||||
value = 0M;
|
||||
const decimal decimalMaxValueHi28 = 7922816251426433759354395033M;
|
||||
const ulong decimalMaxValueHi19 = 7922816251426433759UL;
|
||||
const ulong decimalMaxValueLo9 = 354395033UL;
|
||||
const char decimalMaxValueLo1 = '5';
|
||||
|
||||
if (length == 0)
|
||||
{
|
||||
return ParseResult.Invalid;
|
||||
}
|
||||
|
||||
bool isNegative = (chars[start] == '-');
|
||||
if (isNegative)
|
||||
{
|
||||
// text just a negative sign
|
||||
if (length == 1)
|
||||
{
|
||||
return ParseResult.Invalid;
|
||||
}
|
||||
|
||||
start++;
|
||||
length--;
|
||||
}
|
||||
|
||||
int i = start;
|
||||
int end = start + length;
|
||||
int numDecimalStart = end;
|
||||
int numDecimalEnd = end;
|
||||
int exponent = 0;
|
||||
ulong hi19 = 0UL;
|
||||
ulong lo10 = 0UL;
|
||||
int mantissaDigits = 0;
|
||||
int exponentFromMantissa = 0;
|
||||
bool? roundUp = null;
|
||||
bool? storeOnly28Digits = null;
|
||||
for (; i < end; i++)
|
||||
{
|
||||
char c = chars[i];
|
||||
switch (c)
|
||||
{
|
||||
case '.':
|
||||
if (i == start)
|
||||
{
|
||||
return ParseResult.Invalid;
|
||||
}
|
||||
if (i + 1 == end)
|
||||
{
|
||||
return ParseResult.Invalid;
|
||||
}
|
||||
|
||||
if (numDecimalStart != end)
|
||||
{
|
||||
// multiple decimal points
|
||||
return ParseResult.Invalid;
|
||||
}
|
||||
|
||||
numDecimalStart = i + 1;
|
||||
break;
|
||||
case 'e':
|
||||
case 'E':
|
||||
if (i == start)
|
||||
{
|
||||
return ParseResult.Invalid;
|
||||
}
|
||||
if (i == numDecimalStart)
|
||||
{
|
||||
// E follows decimal point
|
||||
return ParseResult.Invalid;
|
||||
}
|
||||
i++;
|
||||
if (i == end)
|
||||
{
|
||||
return ParseResult.Invalid;
|
||||
}
|
||||
|
||||
if (numDecimalStart < end)
|
||||
{
|
||||
numDecimalEnd = i - 1;
|
||||
}
|
||||
|
||||
c = chars[i];
|
||||
bool exponentNegative = false;
|
||||
switch (c)
|
||||
{
|
||||
case '-':
|
||||
exponentNegative = true;
|
||||
i++;
|
||||
break;
|
||||
case '+':
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
|
||||
// parse 3 digit
|
||||
for (; i < end; i++)
|
||||
{
|
||||
c = chars[i];
|
||||
if (c < '0' || c > '9')
|
||||
{
|
||||
return ParseResult.Invalid;
|
||||
}
|
||||
|
||||
int newExponent = (10 * exponent) + (c - '0');
|
||||
// stops updating exponent when overflowing
|
||||
if (exponent < newExponent)
|
||||
{
|
||||
exponent = newExponent;
|
||||
}
|
||||
}
|
||||
|
||||
if (exponentNegative)
|
||||
{
|
||||
exponent = -exponent;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (c < '0' || c > '9')
|
||||
{
|
||||
return ParseResult.Invalid;
|
||||
}
|
||||
|
||||
if (i == start && c == '0')
|
||||
{
|
||||
i++;
|
||||
if (i != end)
|
||||
{
|
||||
c = chars[i];
|
||||
if (c == '.')
|
||||
{
|
||||
goto case '.';
|
||||
}
|
||||
if (c == 'e' || c == 'E')
|
||||
{
|
||||
goto case 'E';
|
||||
}
|
||||
|
||||
return ParseResult.Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
if (mantissaDigits < 29 && (mantissaDigits != 28 || !(storeOnly28Digits ?? (storeOnly28Digits = (hi19 > decimalMaxValueHi19 || (hi19 == decimalMaxValueHi19 && (lo10 > decimalMaxValueLo9 || (lo10 == decimalMaxValueLo9 && c > decimalMaxValueLo1))))).GetValueOrDefault())))
|
||||
{
|
||||
if (mantissaDigits < 19)
|
||||
{
|
||||
hi19 = (hi19 * 10UL) + (ulong)(c - '0');
|
||||
}
|
||||
else
|
||||
{
|
||||
lo10 = (lo10 * 10UL) + (ulong)(c - '0');
|
||||
}
|
||||
++mantissaDigits;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!roundUp.HasValue)
|
||||
{
|
||||
roundUp = c >= '5';
|
||||
}
|
||||
++exponentFromMantissa;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
exponent += exponentFromMantissa;
|
||||
|
||||
// correct the decimal point
|
||||
exponent -= (numDecimalEnd - numDecimalStart);
|
||||
|
||||
if (mantissaDigits <= 19)
|
||||
{
|
||||
value = hi19;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = (hi19 * DecimalFactors[mantissaDigits - 20]) + lo10;
|
||||
}
|
||||
|
||||
if (exponent > 0)
|
||||
{
|
||||
mantissaDigits += exponent;
|
||||
if (mantissaDigits > 29)
|
||||
{
|
||||
return ParseResult.Overflow;
|
||||
}
|
||||
if (mantissaDigits == 29)
|
||||
{
|
||||
if (exponent > 1)
|
||||
{
|
||||
value *= DecimalFactors[exponent - 2];
|
||||
if (value > decimalMaxValueHi28)
|
||||
{
|
||||
return ParseResult.Overflow;
|
||||
}
|
||||
}
|
||||
value *= 10M;
|
||||
}
|
||||
else
|
||||
{
|
||||
value *= DecimalFactors[exponent - 1];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (roundUp == true && exponent >= -28)
|
||||
{
|
||||
++value;
|
||||
}
|
||||
if (exponent < 0)
|
||||
{
|
||||
if (mantissaDigits + exponent + 28 <= 0)
|
||||
{
|
||||
value = 0M;
|
||||
return ParseResult.Success;
|
||||
}
|
||||
if (exponent >= -28)
|
||||
{
|
||||
value /= DecimalFactors[-exponent - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
decimal[] decimalFactors = DecimalFactors;
|
||||
value /= decimalFactors[27];
|
||||
value /= decimalFactors[-exponent - 29];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isNegative)
|
||||
{
|
||||
value = -value;
|
||||
}
|
||||
|
||||
return ParseResult.Success;
|
||||
}
|
||||
|
||||
private static decimal[] _decimalFactors;
|
||||
|
||||
private static decimal[] DecimalFactors
|
||||
{
|
||||
get
|
||||
{
|
||||
decimal[] decimalFactors = _decimalFactors;
|
||||
if (decimalFactors == null)
|
||||
{
|
||||
decimalFactors = new decimal[28];
|
||||
decimal last = 1M;
|
||||
for (int i = 0; i < decimalFactors.Length; ++i)
|
||||
{
|
||||
decimalFactors[i] = last *= 10M;
|
||||
}
|
||||
_decimalFactors = decimalFactors;
|
||||
}
|
||||
return decimalFactors;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryConvertGuid(string s, out Guid g)
|
||||
{
|
||||
// GUID has to have format 00000000-0000-0000-0000-000000000000
|
||||
@@ -1414,4 +1673,4 @@ namespace Newtonsoft.Json.Utilities
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user