Skip to content

Commit

Permalink
Fix BigInteger.Parse returning incorrect values for exponents above 1…
Browse files Browse the repository at this point in the history
…000 (#73643)

* Fixing problem with invalid parse scientific form of numbers. (#17296)

* Preventing OverflowException when parsing scientific form of numbers. (#17296)

* Suggestions from review

* Added bigger test cases

* Adjusted cutoff to 9 digits

* Accidental commit

Co-authored-by: Maksim Golev <[email protected]>
  • Loading branch information
dakersnar and Maximys authored Aug 12, 2022
1 parent d2afae4 commit 9553069
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -515,17 +515,26 @@ private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles
int exp = 0;
do
{
exp = exp * 10 + (ch - '0');
ch = ++p < strEnd ? *p : '\0';
if (exp > 1000)
// Check if we are about to overflow past our limit of 9 digits
if (exp >= 100_000_000)
{
exp = 9999;
// Set exp to Int.MaxValue to signify the requested exponent is too large. This will lead to an OverflowException later.
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';
}
break;
}

exp = exp * 10 + (ch - '0');
ch = ++p < strEnd ? *p : '\0';

} while (char.IsAsciiDigit(ch));

if (negExp)
{
exp = -exp;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,7 @@ public static bool TryParse([NotNullWhen(true)] string? value, out BigInteger re

public static bool TryParse([NotNullWhen(true)] string? value, NumberStyles style, IFormatProvider? provider, out BigInteger result)
{
return BigNumber.TryParseBigInteger(value, style, NumberFormatInfo.GetInstance(provider), out result);
return BigNumber.TryParseBigInteger(value, style, NumberFormatInfo.GetInstance(provider), out result) == BigNumber.ParsingStatus.OK;
}

public static BigInteger Parse(ReadOnlySpan<char> value, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null)
Expand All @@ -699,7 +699,7 @@ public static bool TryParse(ReadOnlySpan<char> value, out BigInteger result)

public static bool TryParse(ReadOnlySpan<char> value, NumberStyles style, IFormatProvider? provider, out BigInteger result)
{
return BigNumber.TryParseBigInteger(value, style, NumberFormatInfo.GetInstance(provider), out result);
return BigNumber.TryParseBigInteger(value, style, NumberFormatInfo.GetInstance(provider), out result) == BigNumber.ParsingStatus.OK;
}

public static int Compare(BigInteger left, BigInteger right)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,22 @@ internal static class BigNumber
| NumberStyles.AllowCurrencySymbol | NumberStyles.AllowHexSpecifier);

private static readonly uint[] s_uint32PowersOfTen = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
internal enum ParsingStatus
{
OK,
Failed,
Overflow
}

[DoesNotReturn]
internal static void ThrowOverflowOrFormatException(ParsingStatus status) => throw GetException(status);

private static Exception GetException(ParsingStatus status)
{
return status == ParsingStatus.Failed
? new FormatException(SR.Overflow_ParseBigInteger)
: new OverflowException(SR.Overflow_ParseBigInteger);
}

private struct BigNumberBuffer
{
Expand Down Expand Up @@ -325,18 +341,18 @@ internal static bool TryValidateParseStyleInteger(NumberStyles style, [NotNullWh
return true;
}

internal static bool TryParseBigInteger(string? value, NumberStyles style, NumberFormatInfo info, out BigInteger result)
internal static ParsingStatus TryParseBigInteger(string? value, NumberStyles style, NumberFormatInfo info, out BigInteger result)
{
if (value == null)
{
result = default;
return false;
return ParsingStatus.Failed;
}

return TryParseBigInteger(value.AsSpan(), style, info, out result);
}

internal static bool TryParseBigInteger(ReadOnlySpan<char> value, NumberStyles style, NumberFormatInfo info, out BigInteger result)
internal static ParsingStatus TryParseBigInteger(ReadOnlySpan<char> value, NumberStyles style, NumberFormatInfo info, out BigInteger result)
{
if (!TryValidateParseStyleInteger(style, out ArgumentException? e))
{
Expand All @@ -347,7 +363,7 @@ internal static bool TryParseBigInteger(ReadOnlySpan<char> value, NumberStyles s
if (!FormatProvider.TryStringToBigInteger(value, style, info, bigNumber.digits, out bigNumber.precision, out bigNumber.scale, out bigNumber.sign))
{
result = default;
return false;
return ParsingStatus.Failed;
}

if ((style & NumberStyles.AllowHexSpecifier) != 0)
Expand All @@ -373,19 +389,22 @@ internal static BigInteger ParseBigInteger(ReadOnlySpan<char> value, NumberStyle
{
throw e;
}
if (!TryParseBigInteger(value, style, info, out BigInteger result))

ParsingStatus status = TryParseBigInteger(value, style, info, out BigInteger result);
if (status != ParsingStatus.OK)
{
throw new FormatException(SR.Overflow_ParseBigInteger);
ThrowOverflowOrFormatException(status);
}

return result;
}

private static bool HexNumberToBigInteger(ref BigNumberBuffer number, out BigInteger result)
private static ParsingStatus HexNumberToBigInteger(ref BigNumberBuffer number, out BigInteger result)
{
if (number.digits == null || number.digits.Length == 0)
{
result = default;
return false;
return ParsingStatus.Failed;
}

const int DigitsPerBlock = 8;
Expand Down Expand Up @@ -480,7 +499,7 @@ private static bool HexNumberToBigInteger(ref BigNumberBuffer number, out BigInt
}

result = new BigInteger(sign, bits);
return true;
return ParsingStatus.OK;
}
finally
{
Expand All @@ -499,7 +518,7 @@ private static bool HexNumberToBigInteger(ref BigNumberBuffer number, out BigInt
// a divide-and-conquer algorithm with a running time of O(NlogN).
//
private static int s_naiveThreshold = 20000;
private static bool NumberToBigInteger(ref BigNumberBuffer number, out BigInteger result)
private static ParsingStatus NumberToBigInteger(ref BigNumberBuffer number, out BigInteger result)
{
int currentBufferSize = 0;

Expand All @@ -510,10 +529,17 @@ private static bool NumberToBigInteger(ref BigNumberBuffer number, out BigIntege
const uint TenPowMaxPartial = 1000000000;

int[]? arrayFromPoolForResultBuffer = null;

if (numberScale == int.MaxValue)
{
result = default;
return ParsingStatus.Overflow;
}

if (numberScale < 0)
{
result = default;
return false;
return ParsingStatus.Failed;
}

try
Expand All @@ -535,7 +561,7 @@ private static bool NumberToBigInteger(ref BigNumberBuffer number, out BigIntege
}
}

bool Naive(ref BigNumberBuffer number, out BigInteger result)
ParsingStatus Naive(ref BigNumberBuffer number, out BigInteger result)
{
Span<uint> stackBuffer = stackalloc uint[BigIntegerCalculator.StackAllocThreshold];
Span<uint> currentBuffer = stackBuffer;
Expand All @@ -547,7 +573,7 @@ bool Naive(ref BigNumberBuffer number, out BigInteger result)
if (!ProcessChunk(digitsChunk.Span, ref currentBuffer))
{
result = default;
return false;
return ParsingStatus.Failed;
}
}

Expand All @@ -557,7 +583,7 @@ bool Naive(ref BigNumberBuffer number, out BigInteger result)
}

result = NumberBufferToBigInteger(currentBuffer, number.sign);
return true;
return ParsingStatus.OK;

bool ProcessChunk(ReadOnlySpan<char> chunkDigits, ref Span<uint> currentBuffer)
{
Expand Down Expand Up @@ -619,7 +645,7 @@ bool ProcessChunk(ReadOnlySpan<char> chunkDigits, ref Span<uint> currentBuffer)
}
}

bool DivideAndConquer(ref BigNumberBuffer number, out BigInteger result)
ParsingStatus DivideAndConquer(ref BigNumberBuffer number, out BigInteger result)
{
Span<uint> currentBuffer;
int[]? arrayFromPoolForMultiplier = null;
Expand Down Expand Up @@ -674,7 +700,7 @@ bool DivideAndConquer(ref BigNumberBuffer number, out BigInteger result)
if (digitChar != '0')
{
result = default;
return false;
return ParsingStatus.Failed;
}
}
}
Expand Down Expand Up @@ -776,7 +802,7 @@ bool DivideAndConquer(ref BigNumberBuffer number, out BigInteger result)
ArrayPool<int>.Shared.Return(arrayFromPoolForMultiplier);
}
}
return true;
return ParsingStatus.OK;
}

BigInteger NumberBufferToBigInteger(Span<uint> currentBuffer, bool signa)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,42 @@ public static void CustomFormatPerMille()
RunCustomFormatToStringTests(s_random, "#\u2030000000", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 6, PerMilleSymbolFormatter);
}

public static IEnumerable<object[]> RunFormatScientificNotationToBigIntegerAndViceVersaData()
{
yield return new object[] { "1E+1000", "1E+1000" };
yield return new object[] { "1E+1001", "1E+1001" };
yield return new object[] { "1E+10001", "1E+10001" };
yield return new object[] { "1E+100001", "1E+100001" };
yield return new object[] { "1E+99999", "1E+99999" };
}

[Theory]
[MemberData(nameof(RunFormatScientificNotationToBigIntegerAndViceVersaData))]
public static void RunFormatScientificNotationToBigIntegerAndViceVersa(string testingValue, string expectedResult)
{
BigInteger parsedValue;
string actualResult;

parsedValue = BigInteger.Parse(testingValue, NumberStyles.AllowExponent);
actualResult = parsedValue.ToString("E0");

Assert.Equal(expectedResult, actualResult);
}

public static IEnumerable<object[]> RunFormatScientificNotationToBigIntegerThrowsExceptionData()
{
yield return new object[] { "1E+1000000000" };
yield return new object[] { "1E+2147483647" };
yield return new object[] { "1E+21474836492" };
}

[Theory]
[MemberData(nameof(RunFormatScientificNotationToBigIntegerThrowsExceptionData))]
public static void RunFormatScientificNotationToBigIntegerThrowsException(string testingValue)
{
Assert.Throws<OverflowException>(() => BigInteger.Parse(testingValue, NumberStyles.AllowExponent));
}

[Fact]
public static void ToString_InvalidFormat_ThrowsFormatException()
{
Expand Down

0 comments on commit 9553069

Please sign in to comment.