Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions PreciseNumber.Test/PreciseNumberTrigonometryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,39 @@ public void TestSinRefusesAnArgumentTooLargeToReduceAtAll()
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => 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<ArgumentOutOfRangeException>(() => PreciseNumber.Sin(enormous));
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => PreciseNumber.Cos(enormous));
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => PreciseNumber.SinCos(enormous));
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => PreciseNumber.Tan(enormous));
}

[TestMethod]
public void TestOrdinaryAnglesAreUnaffectedByTheReductionCeiling()
{
Expand Down
45 changes: 34 additions & 11 deletions PreciseNumber/PreciseNumber.Trigonometry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,12 @@ public readonly partial record struct PreciseNumber
/// <returns>The sine of <paramref name="x"/>.</returns>
/// <remarks>
/// Produced to the significant digits of <paramref name="x"/>, and never fewer than
/// <see cref="MinimumDivisionPrecision"/>. Use <see cref="Sin(PreciseNumber, int)"/> to choose
/// that precision.
/// <see cref="MinimumDivisionPrecision"/>, but no more than the reduction can deliver at the
/// magnitude of <paramref name="x"/>; see <see cref="DefaultReducedPrecision(PreciseNumber)"/>.
/// Use <see cref="Sin(PreciseNumber, int)"/> to choose that precision.
/// </remarks>
public static PreciseNumber Sin(PreciseNumber x) =>
Sin(x, DefaultTrigonometricPrecision(x));
Sin(x, DefaultReducedPrecision(x));

/// <summary>
/// Returns the sine of an angle in radians, to a chosen number of significant digits.
Expand All @@ -123,11 +124,12 @@ public static PreciseNumber Sin(PreciseNumber x, int significantDigits) =>
/// <returns>The cosine of <paramref name="x"/>.</returns>
/// <remarks>
/// Produced to the significant digits of <paramref name="x"/>, and never fewer than
/// <see cref="MinimumDivisionPrecision"/>. Use <see cref="Cos(PreciseNumber, int)"/> to choose
/// that precision.
/// <see cref="MinimumDivisionPrecision"/>, but no more than the reduction can deliver at the
/// magnitude of <paramref name="x"/>; see <see cref="DefaultReducedPrecision(PreciseNumber)"/>.
/// Use <see cref="Cos(PreciseNumber, int)"/> to choose that precision.
/// </remarks>
public static PreciseNumber Cos(PreciseNumber x) =>
Cos(x, DefaultTrigonometricPrecision(x));
Cos(x, DefaultReducedPrecision(x));

/// <summary>
/// Returns the cosine of an angle in radians, to a chosen number of significant digits.
Expand All @@ -152,10 +154,10 @@ public static PreciseNumber Cos(PreciseNumber x, int significantDigits) =>
/// <remarks>
/// Both come from one argument reduction and one kernel, which is the reason to prefer this over a
/// separate <see cref="Sin(PreciseNumber)"/> and <see cref="Cos(PreciseNumber)"/> when both are
/// wanted.
/// wanted. The precision follows <see cref="DefaultReducedPrecision(PreciseNumber)"/>.
/// </remarks>
public static (PreciseNumber Sin, PreciseNumber Cos) SinCos(PreciseNumber x) =>
SinCos(x, DefaultTrigonometricPrecision(x));
SinCos(x, DefaultReducedPrecision(x));

/// <summary>
/// Returns the sine and cosine of an angle in radians, to a chosen number of significant digits.
Expand Down Expand Up @@ -214,11 +216,12 @@ public static (PreciseNumber Sin, PreciseNumber Cos) SinCos(PreciseNumber x, int
/// <returns>The tangent of <paramref name="x"/>.</returns>
/// <remarks>
/// Produced to the significant digits of <paramref name="x"/>, and never fewer than
/// <see cref="MinimumDivisionPrecision"/>. Use <see cref="Tan(PreciseNumber, int)"/> to choose
/// that precision.
/// <see cref="MinimumDivisionPrecision"/>, but no more than the reduction can deliver at the
/// magnitude of <paramref name="x"/>; see <see cref="DefaultReducedPrecision(PreciseNumber)"/>.
/// Use <see cref="Tan(PreciseNumber, int)"/> to choose that precision.
/// </remarks>
public static PreciseNumber Tan(PreciseNumber x) =>
Tan(x, DefaultTrigonometricPrecision(x));
Tan(x, DefaultReducedPrecision(x));

/// <summary>
/// Returns the tangent of an angle in radians, to a chosen number of significant digits.
Expand Down Expand Up @@ -736,6 +739,26 @@ public static PreciseNumber RadiansToDegrees(PreciseNumber radians, int signific
private static int DefaultTrigonometricPrecision(PreciseNumber value) =>
Math.Max(value.SignificantDigits, MinimumDivisionPrecision);

/// <summary>
/// Gets the significant digits the circular functions produce when the caller does not choose.
/// </summary>
/// <param name="value">The angle being reduced.</param>
/// <returns>
/// <see cref="DefaultTrigonometricPrecision(PreciseNumber)"/>, capped at what reducing
/// <paramref name="value"/> against <see cref="ConstantPrecision"/> digits of 蟺 can deliver, and
/// never less than one.
/// </returns>
/// <remarks>
/// The built-in constants carry <see cref="ConstantPrecision"/> 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. <see cref="RequireReducibleArgument(int, int)"/> 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 <c>Sin(Pi)</c> throws. Only an argument too large to reduce at any
/// precision, where even one digit is out of reach, still throws.
/// </remarks>
private static int DefaultReducedPrecision(PreciseNumber value) =>
Math.Max(1, Math.Min(DefaultTrigonometricPrecision(value), ConstantPrecision - IntegerDigitCount(value)));

/// <summary>
/// Selects the sine and cosine of an angle from those of its reduced remainder and the quadrant
/// the reduction removed.
Expand Down
Loading