diff --git a/PreciseNumber.Test/PreciseNumberExponentialTests.cs b/PreciseNumber.Test/PreciseNumberExponentialTests.cs index e8ad495..997c3cf 100644 --- a/PreciseNumber.Test/PreciseNumberExponentialTests.cs +++ b/PreciseNumber.Test/PreciseNumberExponentialTests.cs @@ -319,6 +319,26 @@ public void TestExp2MatchesPublishedDigits() Assert.AreEqual(PreciseNumber.One, PreciseNumber.Exp2(PreciseNumber.Zero)); } + [TestMethod] + public void TestExp2RoundsTheLastDigitOfALargeFractionalPower() + { + // x·ln2 scales the error in ln2 by |x|, so a fractional power with ten integer digits needs + // ln2 widened by those digits or the digit the final rounding turns on is wrong. The values + // are too large to print, so compare the significand and exponent directly. + AssertExp2(Parse("7000000000.5"), 3, 629, 2107209967); + AssertExp2(Parse("-6000000000.5"), 5, 73383, -1806179979); + + static void AssertExp2(PreciseNumber x, int digits, int expectedSignificand, int expectedExponent) + { + PreciseNumber actual = PreciseNumber.Exp2(x, digits); + PreciseNumber reference = PreciseNumber.Exp2(x, digits + 40).ReduceSignificance(digits); + + Assert.AreEqual(new BigInteger(expectedSignificand), actual.Significand, $"Exp2({x}, {digits}) significand is wrong"); + Assert.AreEqual(expectedExponent, actual.Exponent, $"Exp2({x}, {digits}) exponent is wrong"); + Assert.AreEqual(reference.Significand, actual.Significand, $"Exp2({x}, {digits}) disagrees with the wider computation"); + } + } + [TestMethod] public void TestExp2M1AndExp10M1KeepTheDigitsOfASmallArgument() { diff --git a/PreciseNumber/PreciseNumber.Exponentials.cs b/PreciseNumber/PreciseNumber.Exponentials.cs index 767f583..b4a3d5f 100644 --- a/PreciseNumber/PreciseNumber.Exponentials.cs +++ b/PreciseNumber/PreciseNumber.Exponentials.cs @@ -528,7 +528,8 @@ public static PreciseNumber Exp2(PreciseNumber x, int significantDigits) return Two.Pow(x); } - return ExpOfProductWithConstant(x, Ln2To(significantDigits + ExponentialGuardDigits), significantDigits); + // x scales the absolute error in ln 2, so widen it by the integer digits of x as Exp2M1 does. + return ExpOfProductWithConstant(x, Ln2To(significantDigits + ExponentialGuardDigits + IntegerDigitCount(x)), significantDigits); } ///