From 05b43eb9424c5fbbe7daa99dd910c988c8332da6 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Nov 2026 17:24:17 +0000 Subject: [PATCH] Name newBase, not x, when Log is given a base outside its domain [patch] Log(x, newBase) passed the base to the single-argument Log, whose guard names x, so Log(5, -2) reported a bad x with the base's value, after working out Log(x) for nothing. Check each argument under its own name first. Fixes #196 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017vfJW3RtuaocwQ2QRF7djh --- .../PreciseNumberExponentialTests.cs | 22 +++++++++++++++++++ PreciseNumber/PreciseNumber.Exponentials.cs | 13 +++++++++++ 2 files changed, 35 insertions(+) diff --git a/PreciseNumber.Test/PreciseNumberExponentialTests.cs b/PreciseNumber.Test/PreciseNumberExponentialTests.cs index 43645b5..930071a 100644 --- a/PreciseNumber.Test/PreciseNumberExponentialTests.cs +++ b/PreciseNumber.Test/PreciseNumberExponentialTests.cs @@ -142,6 +142,28 @@ public void TestLogRejectsValuesOutsideItsDomain() Assert.ThrowsExactly(() => PreciseNumber.Log(2.ToPreciseNumber(), 0)); } + [TestMethod] + public void TestLogInABaseNamesTheArgumentOutsideTheDomain() + { + // The base is passed on to the single-argument Log, whose guard names x, so each argument has + // to be checked under its own name first. + ArgumentOutOfRangeException negativeBase = Assert.ThrowsExactly( + () => PreciseNumber.Log(5.ToPreciseNumber(), (-2).ToPreciseNumber())); + Assert.AreEqual("newBase", negativeBase.ParamName); + Assert.AreEqual((-2).ToPreciseNumber(), negativeBase.ActualValue); + + ArgumentOutOfRangeException zeroBase = Assert.ThrowsExactly( + () => PreciseNumber.Log(5.ToPreciseNumber(), PreciseNumber.Zero, 20)); + Assert.AreEqual("newBase", zeroBase.ParamName); + + ArgumentOutOfRangeException negativeValue = Assert.ThrowsExactly( + () => PreciseNumber.Log((-5).ToPreciseNumber(), 2.ToPreciseNumber())); + Assert.AreEqual("x", negativeValue.ParamName); + + // A base of one is still a division by its zero logarithm. + Assert.ThrowsExactly(() => PreciseNumber.Log(5.ToPreciseNumber(), PreciseNumber.One)); + } + [TestMethod] public void TestExpMatchesPublishedDigits() { diff --git a/PreciseNumber/PreciseNumber.Exponentials.cs b/PreciseNumber/PreciseNumber.Exponentials.cs index 90995f9..40acbfb 100644 --- a/PreciseNumber/PreciseNumber.Exponentials.cs +++ b/PreciseNumber/PreciseNumber.Exponentials.cs @@ -192,6 +192,19 @@ public static PreciseNumber Log(PreciseNumber x, PreciseNumber newBase) => public static PreciseNumber Log(PreciseNumber x, PreciseNumber newBase, int significantDigits) { RequireSignificantDigits(significantDigits); + + // Each argument is checked under its own name before either logarithm is worked out, so a bad + // base is not reported as a bad value by the single-argument Log it would otherwise reach. + if (x.Significand.Sign <= 0) + { + throw new ArgumentOutOfRangeException(nameof(x), x, NonPositiveLogarithmMessage); + } + + if (newBase.Significand.Sign <= 0) + { + throw new ArgumentOutOfRangeException(nameof(newBase), newBase, NonPositiveLogarithmMessage); + } + int working = significantDigits + ExponentialGuardDigits; return DivideApproximation(Log(x, working), Log(newBase, working), working, significantDigits); }