From f0720fe71067db0bf2620a9a8534d8229b536ccc Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 26 Sep 2026 21:25:13 +0000 Subject: [PATCH 1/2] Throw DivideByZeroException for zero to a negative power [patch] Pow returned zero for a zero base before looking at the sign of the power, so Pow(0, -1) and Pow(0, -0.5) quietly returned 0. Zero to a negative power is one over zero, which One / Zero and RootN(0, -n) already reject with DivideByZeroException, and generic math over PreciseNumber got a finite, wrong answer with no error. The zero-base branch now throws for a negative power and still returns zero for a positive one. Both Pow overloads document the exception. Fixes #110 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QK96a24CjVhUK9u1Ss2YNN --- .../PreciseNumberExponentialTests.cs | 15 +++++++++++++++ PreciseNumber/PreciseNumber.Exponentials.cs | 1 + PreciseNumber/PreciseNumber.cs | 6 +++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/PreciseNumber.Test/PreciseNumberExponentialTests.cs b/PreciseNumber.Test/PreciseNumberExponentialTests.cs index bdc6e13..e8ad495 100644 --- a/PreciseNumber.Test/PreciseNumberExponentialTests.cs +++ b/PreciseNumber.Test/PreciseNumberExponentialTests.cs @@ -223,6 +223,21 @@ public void TestPowRejectsAFractionalPowerOfANegativeValue() Assert.ThrowsExactly(() => Parse("-2").Pow(Parse("0.5"))); } + [TestMethod] + public void TestPowOfZeroToANegativePowerThrowsLikeDivision() + { + // Zero to a negative power is one over zero, which One / Zero and RootN(0, -n) already reject. + Assert.ThrowsExactly(() => PreciseNumber.Zero.Pow(PreciseNumber.NegativeOne)); + Assert.ThrowsExactly(() => PreciseNumber.Zero.Pow(Parse("-0.5"))); + Assert.ThrowsExactly(() => PreciseNumber.Pow(PreciseNumber.Zero, Parse("-2"))); + Assert.ThrowsExactly(() => PreciseNumber.Pow(PreciseNumber.Zero, Parse("-0.5"))); + + // A positive power of zero is still zero, and the zero power of anything is still one. + Assert.AreEqual(PreciseNumber.Zero, PreciseNumber.Pow(PreciseNumber.Zero, 2.ToPreciseNumber())); + Assert.AreEqual(PreciseNumber.Zero, PreciseNumber.Pow(PreciseNumber.Zero, Parse("0.5"))); + Assert.AreEqual(PreciseNumber.One, PreciseNumber.Pow(PreciseNumber.Zero, PreciseNumber.Zero)); + } + [TestMethod] public void TestExpM1KeepsTheDigitsOfASmallArgument() { diff --git a/PreciseNumber/PreciseNumber.Exponentials.cs b/PreciseNumber/PreciseNumber.Exponentials.cs index 0a3cdf6..767f583 100644 --- a/PreciseNumber/PreciseNumber.Exponentials.cs +++ b/PreciseNumber/PreciseNumber.Exponentials.cs @@ -647,6 +647,7 @@ public static PreciseNumber Exp10M1(PreciseNumber x, int significantDigits) /// The exponent. /// x^y. /// Thrown when is negative and is not an integer. + /// Thrown when is zero and is negative. /// Thrown when the result needs an exponent outside the range of an . /// /// An integer exponent is exact, by repeated squaring. Anything else is diff --git a/PreciseNumber/PreciseNumber.cs b/PreciseNumber/PreciseNumber.cs index fc17624..8a3a60e 100644 --- a/PreciseNumber/PreciseNumber.cs +++ b/PreciseNumber/PreciseNumber.cs @@ -1740,6 +1740,7 @@ public static PreciseNumber Round(PreciseNumber value, int decimalDigits) => /// The power to raise the number to. /// A new instance of that is the result of raising the current instance to the specified power. /// Thrown when the current instance is negative and is not an integer. + /// Thrown when the current instance is zero and is negative. /// Thrown when the result needs an exponent outside the range of an . /// /// An integer power is exact, by repeated squaring. A fractional power is @@ -1754,7 +1755,10 @@ public PreciseNumber Pow(PreciseNumber power) } else if (Significand.IsZero) { - return Zero; + // Zero to a negative power is one over zero, which has no value here, as for One / Zero. + return power.Significand.Sign < 0 + ? throw new DivideByZeroException() + : Zero; } else if (IsUnit) { From 610d7b0d7decd0d0192b1bb1da8ac0138e35a4cb Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 26 Sep 2026 21:35:14 +0000 Subject: [PATCH 2/2] Move zero-to-a-power into its own helper to keep Pow's complexity down [patch] SonarCloud S3776 counted Pow at a cognitive complexity of 17 against a limit of 15 once the zero-base branch gained its sign check. The branch now calls PowOfZero, which holds that check. Behaviour is unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QK96a24CjVhUK9u1Ss2YNN --- PreciseNumber/PreciseNumber.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/PreciseNumber/PreciseNumber.cs b/PreciseNumber/PreciseNumber.cs index 8a3a60e..373a2a9 100644 --- a/PreciseNumber/PreciseNumber.cs +++ b/PreciseNumber/PreciseNumber.cs @@ -1755,10 +1755,7 @@ public PreciseNumber Pow(PreciseNumber power) } else if (Significand.IsZero) { - // Zero to a negative power is one over zero, which has no value here, as for One / Zero. - return power.Significand.Sign < 0 - ? throw new DivideByZeroException() - : Zero; + return PowOfZero(power); } else if (IsUnit) { @@ -1801,6 +1798,17 @@ public PreciseNumber Pow(PreciseNumber power) return FractionalPow(this, power, significantDigits); } + /// + /// Returns zero raised to a non-zero power. + /// + /// The non-zero power. + /// Zero, for a positive . + /// Thrown when is negative, since that is one over zero, as for One / Zero. + private static PreciseNumber PowOfZero(PreciseNumber power) => + power.Significand.Sign < 0 + ? throw new DivideByZeroException() + : Zero; + /// public static PreciseNumber operator -(PreciseNumber value) => Negate(value);