diff --git a/PreciseNumber.Test/PreciseNumberTests.cs b/PreciseNumber.Test/PreciseNumberTests.cs index 10ea1d1..8297aeb 100644 --- a/PreciseNumber.Test/PreciseNumberTests.cs +++ b/PreciseNumber.Test/PreciseNumberTests.cs @@ -2430,6 +2430,47 @@ public void TestParseThenFormatAtExtremeExponentsRoundTrips() Assert.ThrowsExactly(() => huge.ToString(CultureInfo.InvariantCulture)); } + [TestMethod] + public void TestMultiplyThrowsOverflowWhenTheExponentLeavesTheIntRange() + { + PreciseNumber big = PreciseNumber.Parse("1e2000000000", NumberStyles.Float, CultureInfo.InvariantCulture); + PreciseNumber small = PreciseNumber.Parse("1e-2000000000", NumberStyles.Float, CultureInfo.InvariantCulture); + + // Wrapping would give 1e-294967296 and 1e+294967296, off by billions of orders of magnitude. + Assert.ThrowsExactly(() => big * big); + Assert.ThrowsExactly(() => small * small); + Assert.ThrowsExactly(() => PreciseNumber.Pow(big, 2.ToPreciseNumber())); + + // Exponents that still fit are unaffected. + Assert.AreEqual(PreciseNumber.One, big * small); + } + + [TestMethod] + public void TestDivideThrowsOverflowWhenTheExponentLeavesTheIntRange() + { + PreciseNumber big = PreciseNumber.Parse("1e2000000000", NumberStyles.Float, CultureInfo.InvariantCulture); + PreciseNumber small = PreciseNumber.Parse("1e-2000000000", NumberStyles.Float, CultureInfo.InvariantCulture); + + Assert.ThrowsExactly(() => big / small); + Assert.ThrowsExactly(() => small / big); + + // Exponents that still fit are unaffected. + Assert.AreEqual(PreciseNumber.One, big / big); + } + + [TestMethod] + public void TestDivideThrowsOverflowWhenScalingTheQuotientLeavesTheIntRange() + { + PreciseNumber tiny = PreciseNumber.Parse("1E-2147483648", NumberStyles.Float, CultureInfo.InvariantCulture); + + // An exact quotient: 1e-2147483648 / 2 is 5e-2147483649, one place past int.MinValue. + Assert.ThrowsExactly(() => tiny / PreciseNumber.CreateFromComponents(0, 2)); + + // A rounded quotient: a third needs its digits below int.MinValue too. + Assert.ThrowsExactly(() => tiny / PreciseNumber.CreateFromComponents(0, 3)); + Assert.ThrowsExactly(() => PreciseNumber.Divide(tiny, PreciseNumber.CreateFromComponents(0, 3), 5)); + } + [TestMethod] public void TestRoundAtExtremeNegativeExponentGivesZero() { diff --git a/PreciseNumber/PreciseNumber.cs b/PreciseNumber/PreciseNumber.cs index fc17624..c1465da 100644 --- a/PreciseNumber/PreciseNumber.cs +++ b/PreciseNumber/PreciseNumber.cs @@ -1409,6 +1409,7 @@ public static PreciseNumber Add(PreciseNumber left, PreciseNumber right) /// The first number to multiply. /// The second number to multiply. /// The result of the multiplication. + /// Thrown when the product needs an exponent outside the range of an . public static PreciseNumber Multiply(PreciseNumber left, PreciseNumber right) { if (left.Significand.IsZero || right.Significand.IsZero) @@ -1426,7 +1427,7 @@ public static PreciseNumber Multiply(PreciseNumber left, PreciseNumber right) // (l * 10^el) * (r * 10^er) == (l * r) * 10^(el + er), so there is no need to scale the // operands to a common exponent first; doing so only inflates both significands. - return new PreciseNumber(left.Exponent + right.Exponent, left.Significand * right.Significand); + return new PreciseNumber(checked(left.Exponent + right.Exponent), left.Significand * right.Significand); } /// @@ -1436,6 +1437,7 @@ public static PreciseNumber Multiply(PreciseNumber left, PreciseNumber right) /// The number to divide by. /// The result of the division. /// Thrown when is zero. + /// Thrown when the quotient needs an exponent outside the range of an . /// /// A quotient whose decimal expansion terminates is produced exactly, however many digits that /// takes. One that repeats is produced to the precision of the wider operand, and never fewer @@ -1465,6 +1467,7 @@ public static PreciseNumber Divide(PreciseNumber left, PreciseNumber right) /// The result of the division. /// Thrown when is zero. /// Thrown when is less than one. + /// Thrown when the quotient needs an exponent outside the range of an . public static PreciseNumber Divide(PreciseNumber left, PreciseNumber right, int significantDigits) { if (significantDigits < 1) @@ -1484,7 +1487,7 @@ public static PreciseNumber Divide(PreciseNumber left, PreciseNumber right, int BigInteger numerator = left.Significand; BigInteger denominator = right.Significand; - int exponent = left.Exponent - right.Exponent; + int exponent = checked(left.Exponent - right.Exponent); // Carry the sign on the numerator so the denominator can be factorized as a positive value. if (denominator.Sign < 0) @@ -1543,7 +1546,7 @@ private static bool TryDivideExactly(BigInteger numerator, BigInteger denominato int scale = Math.Max(twos, fives); BigInteger significand = numerator * BigInteger.Pow(2, scale - twos) * BigInteger.Pow(5, scale - fives); - result = new PreciseNumber(exponent - scale, significand); + result = new PreciseNumber(checked(exponent - scale), significand); return true; } @@ -1562,7 +1565,7 @@ private static PreciseNumber DivideToPrecision(BigInteger numerator, BigInteger // rounding decision is made on. int scale = significantDigits + 1 - CountDigits(numerator) + CountDigits(denominator); BigInteger scaled = scale > 0 ? numerator * Pow10(scale) : numerator; - int scaledExponent = exponent - Math.Max(scale, 0); + int scaledExponent = checked(exponent - Math.Max(scale, 0)); BigInteger quotient = scaled / denominator; int excess = CountDigits(quotient) - significantDigits; @@ -1583,7 +1586,7 @@ private static PreciseNumber DivideToPrecision(BigInteger numerator, BigInteger kept += quotient.Sign; } - return new PreciseNumber(scaledExponent + excess, kept); + return new PreciseNumber(checked(scaledExponent + excess), kept); } ///