From 5b3bbd6063355ff85ed0776a4a39797c9bbec7f9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 22:25:37 +0000 Subject: [PATCH] Reject a precision below one in ReduceSignificance [patch] ReduceSignificance did not validate its argument, so 5 reduced to zero significant digits returned 10, and to minus one returned 0, with no error. It now throws ArgumentOutOfRangeException below one, as Divide, the roots, Sin and the constant accessors already do. The exponent addition is now checked too, following the exponent-overflow fix in #120, so a result beyond int's exponent range throws OverflowException instead of wrapping. Fixes #126 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LasidbyRZfyxgW8Z9wQu9M --- PreciseNumber.Test/PreciseNumberTests.cs | 20 ++++++++++++++++++++ PreciseNumber/PreciseNumber.cs | 11 ++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/PreciseNumber.Test/PreciseNumberTests.cs b/PreciseNumber.Test/PreciseNumberTests.cs index ae3c204..29f8236 100644 --- a/PreciseNumber.Test/PreciseNumberTests.cs +++ b/PreciseNumber.Test/PreciseNumberTests.cs @@ -1056,6 +1056,26 @@ public void TestReduceSignificance() Assert.AreEqual(number, number.ReduceSignificance(5)); } + // A precision below one has no meaning, and used to return a wrong number rather than fail: + // 5 reduced to zero digits came back as 10, and to minus one digit as 0. + + [TestMethod] + [DataRow(0)] + [DataRow(-1)] + [DataRow(int.MinValue)] + public void TestReduceSignificanceRejectsAPrecisionBelowOne(int significantDigits) + { + Assert.ThrowsExactly(() => 5.ToPreciseNumber().ReduceSignificance(significantDigits)); + Assert.ThrowsExactly(() => PreciseNumber.Pi.ReduceSignificance(significantDigits)); + } + + [TestMethod] + public void TestReduceSignificanceThrowsWhenTheExponentLeavesTheIntRange() + { + PreciseNumber number = PreciseNumber.CreateFromComponents(int.MaxValue - 1, 12345); + Assert.ThrowsExactly(() => number.ReduceSignificance(3)); + } + [TestMethod] public void TestReduceSignificanceRoundsHalfAwayFromZeroOnTheDroppedDigits() { diff --git a/PreciseNumber/PreciseNumber.cs b/PreciseNumber/PreciseNumber.cs index 68184fb..520f46c 100644 --- a/PreciseNumber/PreciseNumber.cs +++ b/PreciseNumber/PreciseNumber.cs @@ -857,8 +857,15 @@ internal long CountDecimalDigits() => /// The number of significant digits to reduce to. /// A new instance of reduced to the specified number of significant digits. /// Rounds half away from zero, so 123.5 becomes 124 and 123.456 becomes 123 at three digits. + /// Thrown when is less than one. + /// Thrown when the result needs an exponent outside the range of an . public PreciseNumber ReduceSignificance(int significantDigits) { + if (significantDigits < 1) + { + throw new ArgumentOutOfRangeException(nameof(significantDigits), significantDigits, "At least one significant digit is required."); + } + int significantDifference = significantDigits < SignificantDigits ? SignificantDigits - significantDigits : 0; @@ -868,9 +875,7 @@ public PreciseNumber ReduceSignificance(int significantDigits) return this; } - int newExponent = Exponent == 0 - ? significantDifference - : Exponent + significantDifference; + int newExponent = checked(Exponent + significantDifference); BigInteger newSignificand = DropDigitsRoundingHalfAwayFromZero(Significand, significantDifference); return new(newExponent, newSignificand); }