/// <summary>
/// Parses a number and returns the corresponding double-precision value.
/// </summary>
/// <param name="reader"> The reader to read characters from. </param>
/// <param name="firstChar"> The first character of the number. Must be 0-9 or a period. </param>
/// <param name="status"> Upon returning, contains the type of error if one occurred. </param>
/// <param name="allowHex"> </param>
/// <param name="allowOctal"> </param>
/// <returns> The numeric value, or <c>NaN</c> if the number is invalid. </returns>
internal static double ParseCore(TextReader reader, char firstChar, out ParseCoreStatus status, bool allowHex = true, bool allowOctal = true)
{
double result;
// A count of the number of integral and fractional digits of the input number.
int totalDigits = 0;
// Assume success.
status = ParseCoreStatus.Success;
// If the number starts with '0' then the number is a hex literal or a octal literal.
if (firstChar == '0' && (allowHex == true || allowOctal == true))
{
// Read the next char - should be 'x' or 'X' if this is a hex number (could be just '0').
int c = reader.Peek();
if ((c == 'x' || c == 'X') && allowHex == true)
{
// Hex number.
reader.Read();
result = ParseHex(reader);
if (double.IsNaN(result) == true)
{
status = ParseCoreStatus.InvalidHexLiteral;
return double.NaN;
}
status = ParseCoreStatus.HexLiteral;
return result;
}
else if (c >= '0' && c <= '9' && allowOctal == true)
{
// Octal number.
result = ParseOctal(reader);
if (double.IsNaN(result) == true)
{
status = ParseCoreStatus.InvalidOctalLiteral;
return double.NaN;
}
status = ParseCoreStatus.OctalLiteral;
return result;
}
}
// desired1-3 hold the integral and fractional digits of the input number.
// desired1 holds the first set of nine digits, desired2 holds the second set of nine
// digits, desired3 holds the rest.
int desired1 = 0;
int desired2 = 0;
var desired3 = BigInteger.Zero;
// Indicates the base-10 scale factor of the output e.g. the result is
// desired x 10^exponentBase10.
int exponentBase10 = 0;
// Read the integer component.
if (firstChar >= '0' && firstChar <= '9')
{
desired1 = firstChar - '0';
totalDigits = 1;
while (true)
{
int c = reader.Peek();
if (c < '0' || c > '9')
break;
reader.Read();
if (totalDigits < 9)
desired1 = desired1 * 10 + (c - '0');
else if (totalDigits < 18)
desired2 = desired2 * 10 + (c - '0');
else
desired3 = BigInteger.MultiplyAdd(desired3, 10, c - '0');
totalDigits++;
}
}
if (firstChar == '.' || reader.Peek() == '.')
{
// Skip past the period.
if (firstChar != '.')
reader.Read();
// Read the fractional component.
int fractionalDigits = 0;
while (true)
{
int c = reader.Peek();
if (c < '0' || c > '9')
break;
reader.Read();
if (totalDigits < 9)
desired1 = desired1 * 10 + (c - '0');
else if (totalDigits < 18)
desired2 = desired2 * 10 + (c - '0');
else
desired3 = BigInteger.MultiplyAdd(desired3, 10, c - '0');
totalDigits++;
fractionalDigits++;
exponentBase10--;
}
// Check if the number consists solely of a period.
if (totalDigits == 0)
{
status = ParseCoreStatus.NoDigits;
return double.NaN;
}
// Check if the number has a period but no digits afterwards.
if (fractionalDigits == 0)
status = ParseCoreStatus.NoFraction;
}
if (reader.Peek() == 'e' || reader.Peek() == 'E')
{
// Skip past the 'e'.
reader.Read();
// Read the sign of the exponent.
bool exponentNegative = false;
int c = reader.Peek();
if (c == '+')
reader.Read();
else if (c == '-')
{
reader.Read();
exponentNegative = true;
}
// Read the first character of the exponent.
int firstExponentChar = reader.Read();
// Check there is a number after the 'e'.
int exponent = 0;
if (firstExponentChar < '0' || firstExponentChar > '9')
{
status = ParseCoreStatus.NoExponent;
}
else
{
// Read the rest of the exponent.
exponent = firstExponentChar - '0';
int exponentDigits = 1;
while (true)
{
c = reader.Peek();
if (c < '0' || c > '9')
break;
reader.Read();
exponent = Math.Min(exponent * 10 + (c - '0'), 9999);
exponentDigits++;
}
// JSON does not allow a leading zero in front of the exponent.
if (firstExponentChar == '0' && exponentDigits > 1 && status == ParseCoreStatus.Success)
status = ParseCoreStatus.ExponentHasLeadingZero;
}
// Keep track of the overall base-10 exponent.
exponentBase10 += exponentNegative ? -exponent : exponent;
}
// Calculate the integral and fractional portion of the number, scaled to an integer.
if (totalDigits < 16)
{
// Combine desired1 and desired2 to produce an integer representing the final
// result.
result = (double)((long)desired1 * integerPowersOfTen[Math.Max(totalDigits - 9, 0)] + desired2);
}
else
{
// Combine desired1, desired2 and desired3 to produce an integer representing the
// final result.
var temp = desired3;
desired3 = new BigInteger((long)desired1 * integerPowersOfTen[Math.Min(totalDigits - 9, 9)] + desired2);
if (totalDigits > 18)
{
desired3 = BigInteger.Multiply(desired3, BigInteger.Pow(10, totalDigits - 18));
desired3 = BigInteger.Add(desired3, temp);
}
result = desired3.ToDouble();
}
// Apply the base-10 exponent.
if (exponentBase10 > 0)
result *= Math.Pow(10, exponentBase10);
else if (exponentBase10 < 0 && exponentBase10 >= -308)
result /= Math.Pow(10, -exponentBase10);
else if (exponentBase10 < -308)
{
// Note: 10^308 is the largest representable power of ten.
result /= Math.Pow(10, 308);
result /= Math.Pow(10, -exponentBase10 - 308);
}
// Numbers with 16 or more digits require the use of arbitrary precision arithmetic to
// determine the correct answer.
if (totalDigits >= 16)
return RefineEstimate(result, exponentBase10, desired3);
return result;
}