From 1f0f6562a4552925ebb31ba4cb4c264c02e09df8 Mon Sep 17 00:00:00 2001 From: matt-edmondson Date: Sun, 1 Nov 2026 04:26:19 +0000 Subject: [PATCH] Round Log in a base, Log2, Log2P1 and Log10P1 to the digits asked for [patch] Each finished with Divide, which returns a terminating quotient exactly and ignores significantDigits, so Log(1E5, 1E4, 1) returned 1.25. Use DivideApproximation, as Trigonometry and Roots already do. Fixes #147 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017vfJW4sAxuwubJA58cavro --- .../PreciseNumberExponentialTests.cs | 36 +++++++++++++++++++ PreciseNumber/PreciseNumber.Exponentials.cs | 8 ++--- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/PreciseNumber.Test/PreciseNumberExponentialTests.cs b/PreciseNumber.Test/PreciseNumberExponentialTests.cs index 235d394..43645b5 100644 --- a/PreciseNumber.Test/PreciseNumberExponentialTests.cs +++ b/PreciseNumber.Test/PreciseNumberExponentialTests.cs @@ -383,6 +383,42 @@ public void TestLog2P1AndLog10P1KeepTheDigitsOfASmallArgument() "Log10P1(1E-30) is wrong"); } + [TestMethod] + public void TestLogInABaseRoundsATerminatingQuotientToTheDigitsAskedFor() + { + // ln 1e5 / ln 1e4 is exactly 1.25 once both logarithms are carried to the same working width, + // so an unrounded Divide hands back three digits for a one-digit request. + PreciseNumber oneDigit = PreciseNumber.Log(Parse("1E5"), Parse("1E4"), 1); + Assert.AreEqual(PreciseNumber.One, oneDigit, "Log(1E5, 1E4, 1) is wrong"); + Assert.AreEqual(1, oneDigit.SignificantDigits, "Log(1E5, 1E4, 1) returned more digits than asked for"); + + PreciseNumber twoDigits = PreciseNumber.Log(Parse("1E5"), Parse("1E4"), 2); + Assert.AreEqual(2, twoDigits.SignificantDigits, "Log(1E5, 1E4, 2) returned more digits than asked for"); + Assert.AreEqual(Parse("1.3"), twoDigits, "Log(1E5, 1E4, 2) is wrong"); + + Assert.AreEqual(Parse("1.25"), PreciseNumber.Log(Parse("1E5"), Parse("1E4"), 3), "Log(1E5, 1E4, 3) is wrong"); + Assert.AreEqual(Parse("1.8"), PreciseNumber.Log(Parse("1E7"), Parse("1E4"), 2), "Log(1E7, 1E4, 2) is wrong"); + } + + [TestMethod] + public void TestDividedLogarithmsNeverReturnMoreDigitsThanAskedFor() + { + // Log2, Log2P1 and Log10P1 finish with the same division as a logarithm in a chosen base, so + // they share its rule: the result is rounded to the request whether or not the quotient + // terminates. + for (int digits = 1; digits <= 4; digits++) + { + for (int i = 1; i <= 64; i++) + { + PreciseNumber x = Parse(i.ToString(CultureInfo.InvariantCulture)); + Assert.IsLessThanOrEqualTo(digits, PreciseNumber.Log2(x, digits).SignificantDigits, $"Log2({i}, {digits}) returned more digits than asked for"); + Assert.IsLessThanOrEqualTo(digits, PreciseNumber.Log2P1(x, digits).SignificantDigits, $"Log2P1({i}, {digits}) returned more digits than asked for"); + Assert.IsLessThanOrEqualTo(digits, PreciseNumber.Log10P1(x, digits).SignificantDigits, $"Log10P1({i}, {digits}) returned more digits than asked for"); + Assert.IsLessThanOrEqualTo(digits, PreciseNumber.Log(x, Parse("1E4"), digits).SignificantDigits, $"Log({i}, 1E4, {digits}) returned more digits than asked for"); + } + } + } + [TestMethod] public void TestPrecisionFollowsTheWiderOperandRatherThanDouble() { diff --git a/PreciseNumber/PreciseNumber.Exponentials.cs b/PreciseNumber/PreciseNumber.Exponentials.cs index 06d28f9..90995f9 100644 --- a/PreciseNumber/PreciseNumber.Exponentials.cs +++ b/PreciseNumber/PreciseNumber.Exponentials.cs @@ -193,7 +193,7 @@ public static PreciseNumber Log(PreciseNumber x, PreciseNumber newBase, int sign { RequireSignificantDigits(significantDigits); int working = significantDigits + ExponentialGuardDigits; - return Divide(Log(x, working), Log(newBase, working), significantDigits); + return DivideApproximation(Log(x, working), Log(newBase, working), working, significantDigits); } /// @@ -224,7 +224,7 @@ public static PreciseNumber Log2(PreciseNumber x, int significantDigits) { RequireSignificantDigits(significantDigits); int working = significantDigits + ExponentialGuardDigits; - return Divide(Log(x, working), Ln2To(working), significantDigits); + return DivideApproximation(Log(x, working), Ln2To(working), working, significantDigits); } /// @@ -354,7 +354,7 @@ public static PreciseNumber Log2P1(PreciseNumber x, int significantDigits) } int working = significantDigits + ExponentialGuardDigits; - return Divide(LogP1(x, working), Ln2To(working), significantDigits); + return DivideApproximation(LogP1(x, working), Ln2To(working), working, significantDigits); } /// @@ -386,7 +386,7 @@ public static PreciseNumber Log10P1(PreciseNumber x, int significantDigits) } int working = significantDigits + ExponentialGuardDigits; - return Divide(LogP1(x, working), Ln10To(working), significantDigits); + return DivideApproximation(LogP1(x, working), Ln10To(working), working, significantDigits); } ///