diff --git a/PreciseNumber.Test/PreciseNumberTrigonometryTests.cs b/PreciseNumber.Test/PreciseNumberTrigonometryTests.cs index ecc8df1..5b43a6b 100644 --- a/PreciseNumber.Test/PreciseNumberTrigonometryTests.cs +++ b/PreciseNumber.Test/PreciseNumberTrigonometryTests.cs @@ -164,6 +164,39 @@ public void TestSinRefusesAnArgumentTooLargeToReduceAtAll() Assert.ThrowsExactly(() => PreciseNumber.Tan(enormous, 10)); } + [TestMethod] + public void TestDefaultPrecisionOverloadsAcceptTheLibrarysOwnConstants() + { + // The constants carry ConstantPrecision digits, so the default used to ask for every one of + // them and then refuse, because an argument with an integer digit needs π one digit wider. + // The overloads that choose their own precision must choose one they can deliver. + int ceiling = PreciseNumber.ConstantPrecision - 1; + + PreciseNumber cosPi = PreciseNumber.Cos(PreciseNumber.Pi); + AssertAgreesTo(PreciseNumber.NegativeOne, cosPi, ceiling, "Cos(Pi) was not -1"); + AssertAgreesTo(PreciseNumber.One, PreciseNumber.Cos(PreciseNumber.Tau), ceiling, "Cos(Tau) was not 1"); + + (PreciseNumber sin, PreciseNumber cos) = PreciseNumber.SinCos(PreciseNumber.Pi); + Assert.AreEqual(PreciseNumber.Sin(PreciseNumber.Pi), sin, "SinCos(Pi).Sin disagreed with Sin(Pi)"); + Assert.AreEqual(cosPi, cos, "SinCos(Pi).Cos disagreed with Cos(Pi)"); + + // E is not a multiple of π/2, so the sine has digits of its own to compare against the + // explicit overload at the same ceiling. + Assert.AreEqual(PreciseNumber.Sin(PreciseNumber.E, ceiling), PreciseNumber.Sin(PreciseNumber.E)); + } + + [TestMethod] + public void TestDefaultPrecisionOverloadsStillRefuseAnArgumentTooLargeToReduce() + { + // Clamping the default must not hide the one case with no answer at any precision. + PreciseNumber enormous = Parse("1E200"); + + Assert.ThrowsExactly(() => PreciseNumber.Sin(enormous)); + Assert.ThrowsExactly(() => PreciseNumber.Cos(enormous)); + Assert.ThrowsExactly(() => PreciseNumber.SinCos(enormous)); + Assert.ThrowsExactly(() => PreciseNumber.Tan(enormous)); + } + [TestMethod] public void TestOrdinaryAnglesAreUnaffectedByTheReductionCeiling() { diff --git a/PreciseNumber/PreciseNumber.Trigonometry.cs b/PreciseNumber/PreciseNumber.Trigonometry.cs index 3c6e87a..589d09e 100644 --- a/PreciseNumber/PreciseNumber.Trigonometry.cs +++ b/PreciseNumber/PreciseNumber.Trigonometry.cs @@ -95,11 +95,12 @@ public readonly partial record struct PreciseNumber /// The sine of . /// /// Produced to the significant digits of , and never fewer than - /// . Use to choose - /// that precision. + /// , but no more than the reduction can deliver at the + /// magnitude of ; see . + /// Use to choose that precision. /// public static PreciseNumber Sin(PreciseNumber x) => - Sin(x, DefaultTrigonometricPrecision(x)); + Sin(x, DefaultReducedPrecision(x)); /// /// Returns the sine of an angle in radians, to a chosen number of significant digits. @@ -123,11 +124,12 @@ public static PreciseNumber Sin(PreciseNumber x, int significantDigits) => /// The cosine of . /// /// Produced to the significant digits of , and never fewer than - /// . Use to choose - /// that precision. + /// , but no more than the reduction can deliver at the + /// magnitude of ; see . + /// Use to choose that precision. /// public static PreciseNumber Cos(PreciseNumber x) => - Cos(x, DefaultTrigonometricPrecision(x)); + Cos(x, DefaultReducedPrecision(x)); /// /// Returns the cosine of an angle in radians, to a chosen number of significant digits. @@ -152,10 +154,10 @@ public static PreciseNumber Cos(PreciseNumber x, int significantDigits) => /// /// Both come from one argument reduction and one kernel, which is the reason to prefer this over a /// separate and when both are - /// wanted. + /// wanted. The precision follows . /// public static (PreciseNumber Sin, PreciseNumber Cos) SinCos(PreciseNumber x) => - SinCos(x, DefaultTrigonometricPrecision(x)); + SinCos(x, DefaultReducedPrecision(x)); /// /// Returns the sine and cosine of an angle in radians, to a chosen number of significant digits. @@ -214,11 +216,12 @@ public static (PreciseNumber Sin, PreciseNumber Cos) SinCos(PreciseNumber x, int /// The tangent of . /// /// Produced to the significant digits of , and never fewer than - /// . Use to choose - /// that precision. + /// , but no more than the reduction can deliver at the + /// magnitude of ; see . + /// Use to choose that precision. /// public static PreciseNumber Tan(PreciseNumber x) => - Tan(x, DefaultTrigonometricPrecision(x)); + Tan(x, DefaultReducedPrecision(x)); /// /// Returns the tangent of an angle in radians, to a chosen number of significant digits. @@ -736,6 +739,26 @@ public static PreciseNumber RadiansToDegrees(PreciseNumber radians, int signific private static int DefaultTrigonometricPrecision(PreciseNumber value) => Math.Max(value.SignificantDigits, MinimumDivisionPrecision); + /// + /// Gets the significant digits the circular functions produce when the caller does not choose. + /// + /// The angle being reduced. + /// + /// , capped at what reducing + /// against digits of π can deliver, and + /// never less than one. + /// + /// + /// The built-in constants carry digits, so the uncapped default + /// asks for exactly as many digits as π holds, and a constant with an integer digit then needs + /// one more than that. rightly refuses such a + /// request when a caller makes it, but an overload that picks its own precision must not pick one + /// it will refuse, or Sin(Pi) throws. Only an argument too large to reduce at any + /// precision, where even one digit is out of reach, still throws. + /// + private static int DefaultReducedPrecision(PreciseNumber value) => + Math.Max(1, Math.Min(DefaultTrigonometricPrecision(value), ConstantPrecision - IntegerDigitCount(value))); + /// /// Selects the sine and cosine of an angle from those of its reduced remainder and the quadrant /// the reduction removed.