From b6d049869ce866b6193a6ff3a854e5808c002eff Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Oct 2026 13:31:17 +0000 Subject: [PATCH] Throw OverflowException when Add/Subtract/Mod exponents are too far apart to align [patch] CommonizeSignificands subtracted the two exponents in unchecked int, so operands at opposite extremes (1e2000000000 + 1e-2000000000) wrapped the difference negative and surfaced an unrelated ArgumentOutOfRangeException from Pow10. The difference is now checked, so Add, Subtract and Mod report the condition as OverflowException, the same as Multiply and Divide. Fixes #135 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016cMuggtJH1AeNFWqHHnG2h --- PreciseNumber.Test/PreciseNumberTests.cs | 20 ++++++++++++++++++++ PreciseNumber/PreciseNumber.cs | 10 ++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) 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)