Skip to content

Commit 3f4c95d

Browse files
Merge pull request #120 from ktsu-dev/fix/exponent-overflow
2 parents 6fafe87 + 85c110c commit 3f4c95d

2 files changed

Lines changed: 49 additions & 5 deletions

File tree

‎PreciseNumber.Test/PreciseNumberTests.cs‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2545,6 +2545,47 @@ public void TestParseThenFormatAtExtremeExponentsRoundTrips()
25452545
Assert.ThrowsExactly<OverflowException>(() => huge.ToString(CultureInfo.InvariantCulture));
25462546
}
25472547

2548+
[TestMethod]
2549+
public void TestMultiplyThrowsOverflowWhenTheExponentLeavesTheIntRange()
2550+
{
2551+
PreciseNumber big = PreciseNumber.Parse("1e2000000000", NumberStyles.Float, CultureInfo.InvariantCulture);
2552+
PreciseNumber small = PreciseNumber.Parse("1e-2000000000", NumberStyles.Float, CultureInfo.InvariantCulture);
2553+
2554+
// Wrapping would give 1e-294967296 and 1e+294967296, off by billions of orders of magnitude.
2555+
Assert.ThrowsExactly<OverflowException>(() => big * big);
2556+
Assert.ThrowsExactly<OverflowException>(() => small * small);
2557+
Assert.ThrowsExactly<OverflowException>(() => PreciseNumber.Pow(big, 2.ToPreciseNumber()));
2558+
2559+
// Exponents that still fit are unaffected.
2560+
Assert.AreEqual(PreciseNumber.One, big * small);
2561+
}
2562+
2563+
[TestMethod]
2564+
public void TestDivideThrowsOverflowWhenTheExponentLeavesTheIntRange()
2565+
{
2566+
PreciseNumber big = PreciseNumber.Parse("1e2000000000", NumberStyles.Float, CultureInfo.InvariantCulture);
2567+
PreciseNumber small = PreciseNumber.Parse("1e-2000000000", NumberStyles.Float, CultureInfo.InvariantCulture);
2568+
2569+
Assert.ThrowsExactly<OverflowException>(() => big / small);
2570+
Assert.ThrowsExactly<OverflowException>(() => small / big);
2571+
2572+
// Exponents that still fit are unaffected.
2573+
Assert.AreEqual(PreciseNumber.One, big / big);
2574+
}
2575+
2576+
[TestMethod]
2577+
public void TestDivideThrowsOverflowWhenScalingTheQuotientLeavesTheIntRange()
2578+
{
2579+
PreciseNumber tiny = PreciseNumber.Parse("1E-2147483648", NumberStyles.Float, CultureInfo.InvariantCulture);
2580+
2581+
// An exact quotient: 1e-2147483648 / 2 is 5e-2147483649, one place past int.MinValue.
2582+
Assert.ThrowsExactly<OverflowException>(() => tiny / PreciseNumber.CreateFromComponents(0, 2));
2583+
2584+
// A rounded quotient: a third needs its digits below int.MinValue too.
2585+
Assert.ThrowsExactly<OverflowException>(() => tiny / PreciseNumber.CreateFromComponents(0, 3));
2586+
Assert.ThrowsExactly<OverflowException>(() => PreciseNumber.Divide(tiny, PreciseNumber.CreateFromComponents(0, 3), 5));
2587+
}
2588+
25482589
[TestMethod]
25492590
public void TestRoundAtExtremeNegativeExponentGivesZero()
25502591
{

‎PreciseNumber/PreciseNumber.cs‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,6 +1452,7 @@ public static PreciseNumber Add(PreciseNumber left, PreciseNumber right)
14521452
/// <param name="left">The first number to multiply.</param>
14531453
/// <param name="right">The second number to multiply.</param>
14541454
/// <returns>The result of the multiplication.</returns>
1455+
/// <exception cref="OverflowException">Thrown when the product needs an exponent outside the range of an <see cref="int"/>.</exception>
14551456
public static PreciseNumber Multiply(PreciseNumber left, PreciseNumber right)
14561457
{
14571458
if (left.Significand.IsZero || right.Significand.IsZero)
@@ -1469,7 +1470,7 @@ public static PreciseNumber Multiply(PreciseNumber left, PreciseNumber right)
14691470

14701471
// (l * 10^el) * (r * 10^er) == (l * r) * 10^(el + er), so there is no need to scale the
14711472
// operands to a common exponent first; doing so only inflates both significands.
1472-
return new PreciseNumber(left.Exponent + right.Exponent, left.Significand * right.Significand);
1473+
return new PreciseNumber(checked(left.Exponent + right.Exponent), left.Significand * right.Significand);
14731474
}
14741475

14751476
/// <summary>
@@ -1479,6 +1480,7 @@ public static PreciseNumber Multiply(PreciseNumber left, PreciseNumber right)
14791480
/// <param name="right">The number to divide by.</param>
14801481
/// <returns>The result of the division.</returns>
14811482
/// <exception cref="DivideByZeroException">Thrown when <paramref name="right"/> is zero.</exception>
1483+
/// <exception cref="OverflowException">Thrown when the quotient needs an exponent outside the range of an <see cref="int"/>.</exception>
14821484
/// <remarks>
14831485
/// A quotient whose decimal expansion terminates is produced exactly, however many digits that
14841486
/// takes. One that repeats is produced to the precision of the wider operand, and never fewer
@@ -1508,6 +1510,7 @@ public static PreciseNumber Divide(PreciseNumber left, PreciseNumber right)
15081510
/// <returns>The result of the division.</returns>
15091511
/// <exception cref="DivideByZeroException">Thrown when <paramref name="right"/> is zero.</exception>
15101512
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="significantDigits"/> is less than one.</exception>
1513+
/// <exception cref="OverflowException">Thrown when the quotient needs an exponent outside the range of an <see cref="int"/>.</exception>
15111514
public static PreciseNumber Divide(PreciseNumber left, PreciseNumber right, int significantDigits)
15121515
{
15131516
if (significantDigits < 1)
@@ -1527,7 +1530,7 @@ public static PreciseNumber Divide(PreciseNumber left, PreciseNumber right, int
15271530

15281531
BigInteger numerator = left.Significand;
15291532
BigInteger denominator = right.Significand;
1530-
int exponent = left.Exponent - right.Exponent;
1533+
int exponent = checked(left.Exponent - right.Exponent);
15311534

15321535
// Carry the sign on the numerator so the denominator can be factorized as a positive value.
15331536
if (denominator.Sign < 0)
@@ -1586,7 +1589,7 @@ private static bool TryDivideExactly(BigInteger numerator, BigInteger denominato
15861589
int scale = Math.Max(twos, fives);
15871590
BigInteger significand = numerator * BigInteger.Pow(2, scale - twos) * BigInteger.Pow(5, scale - fives);
15881591

1589-
result = new PreciseNumber(exponent - scale, significand);
1592+
result = new PreciseNumber(checked(exponent - scale), significand);
15901593
return true;
15911594
}
15921595

@@ -1605,7 +1608,7 @@ private static PreciseNumber DivideToPrecision(BigInteger numerator, BigInteger
16051608
// rounding decision is made on.
16061609
int scale = significantDigits + 1 - CountDigits(numerator) + CountDigits(denominator);
16071610
BigInteger scaled = scale > 0 ? numerator * Pow10(scale) : numerator;
1608-
int scaledExponent = exponent - Math.Max(scale, 0);
1611+
int scaledExponent = checked(exponent - Math.Max(scale, 0));
16091612

16101613
BigInteger quotient = scaled / denominator;
16111614
int excess = CountDigits(quotient) - significantDigits;
@@ -1626,7 +1629,7 @@ private static PreciseNumber DivideToPrecision(BigInteger numerator, BigInteger
16261629
kept += quotient.Sign;
16271630
}
16281631

1629-
return new PreciseNumber(scaledExponent + excess, kept);
1632+
return new PreciseNumber(checked(scaledExponent + excess), kept);
16301633
}
16311634

16321635
/// <summary>

0 commit comments

Comments
 (0)