diff --git a/PreciseNumber.Test/PreciseNumberTests.cs b/PreciseNumber.Test/PreciseNumberTests.cs index 70afcac..2d431ba 100644 --- a/PreciseNumber.Test/PreciseNumberTests.cs +++ b/PreciseNumber.Test/PreciseNumberTests.cs @@ -2674,6 +2674,26 @@ public void TestMultiplyThrowsOverflowWhenTheExponentLeavesTheIntRange() Assert.AreEqual(PreciseNumber.One, big * small); } + [TestMethod] + public void TestAddSubtractModThrowOverflowWhenTheExponentsAreTooFarApartToAlign() + { + PreciseNumber big = PreciseNumber.Parse("1e2000000000", NumberStyles.Float, CultureInfo.InvariantCulture); + PreciseNumber small = PreciseNumber.Parse("1e-2000000000", NumberStyles.Float, CultureInfo.InvariantCulture); + + // The exponents are 4e9 apart; the difference wrapped to -294967296 and reached Pow10 as an + // ArgumentOutOfRangeException, unlike the OverflowException Multiply and Divide throw. + Assert.ThrowsExactly(() => big + small); + Assert.ThrowsExactly(() => small + big); + Assert.ThrowsExactly(() => big - small); + Assert.ThrowsExactly(() => small - big); + Assert.ThrowsExactly(() => big % small); + Assert.ThrowsExactly(() => small % big); + + // Exponents that are close enough to align are unaffected. + Assert.AreEqual(PreciseNumber.Parse("2e2000000000", NumberStyles.Float, CultureInfo.InvariantCulture), big + big); + Assert.AreEqual(PreciseNumber.Zero, small - small); + } + [TestMethod] public void TestDivideThrowsOverflowWhenTheExponentLeavesTheIntRange() { diff --git a/PreciseNumber/PreciseNumber.cs b/PreciseNumber/PreciseNumber.cs index 066ea33..41fabc5 100644 --- a/PreciseNumber/PreciseNumber.cs +++ b/PreciseNumber/PreciseNumber.cs @@ -943,6 +943,7 @@ internal static (PreciseNumber, PreciseNumber, int) MakeCommonizedWithExponent(P /// The left number. /// The right number. /// The scaled significands and the exponent they share. + /// Thrown when the exponents are further apart than the range of an . private static (BigInteger Left, BigInteger Right, int Exponent) CommonizeSignificands(PreciseNumber left, PreciseNumber right) { int leftExponent = left.Exponent; @@ -953,9 +954,11 @@ private static (BigInteger Left, BigInteger Right, int Exponent) CommonizeSignif return (left.Significand, right.Significand, leftExponent); } + // Exponents at opposite extremes are more than int.MaxValue apart, and an unchecked difference + // would wrap negative and reach Pow10 as an unrelated ArgumentOutOfRangeException. return leftExponent > rightExponent - ? (left.Significand * Pow10(leftExponent - rightExponent), right.Significand, rightExponent) - : (left.Significand, right.Significand * Pow10(rightExponent - leftExponent), leftExponent); + ? (left.Significand * Pow10(checked(leftExponent - rightExponent)), right.Significand, rightExponent) + : (left.Significand, right.Significand * Pow10(checked(rightExponent - leftExponent)), leftExponent); } /// @@ -1536,6 +1539,7 @@ public static PreciseNumber Negate(PreciseNumber value) /// The number to subtract from. /// The number to subtract. /// The result of the subtraction. + /// Thrown when the exponents of the operands are further apart than the range of an . public static PreciseNumber Subtract(PreciseNumber left, PreciseNumber right) { (BigInteger commonLeft, BigInteger commonRight, int commonExponent) = CommonizeSignificands(left, right); @@ -1548,6 +1552,7 @@ public static PreciseNumber Subtract(PreciseNumber left, PreciseNumber right) /// The first number to add. /// The second number to add. /// The result of the addition. + /// Thrown when the exponents of the operands are further apart than the range of an . public static PreciseNumber Add(PreciseNumber left, PreciseNumber right) { (BigInteger commonLeft, BigInteger commonRight, int commonExponent) = CommonizeSignificands(left, right); @@ -1809,6 +1814,7 @@ private static PreciseNumber DivideToPrecision(BigInteger numerator, BigInteger /// The number to divide. /// The number to divide by. /// The modulus of the two numbers. + /// Thrown when the exponents of the operands are further apart than the range of an . public static PreciseNumber Mod(PreciseNumber left, PreciseNumber right) { if (right.Significand.IsZero)