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
31 changes: 31 additions & 0 deletions PreciseNumber.Test/PreciseNumberTrigonometryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,37 @@
AssertAgreesTo(Parse(SinOneMillionDigits), sine, 120, "Sin at the reduction ceiling did not match its wide reference");
}

[TestMethod]
public void TestTanDeliversEveryDigitUpToTheReductionCeiling()
{
// Tan widens by its guard digits before computing sin and cos. Those are margin, so they must
// not be counted against the ceiling: Tan has to reach the same precision Sin and Cos do.
int ceiling = PreciseNumber.ConstantPrecision - 7;
PreciseNumber million = Parse("1000000");
PreciseNumber tangent = PreciseNumber.Tan(million, ceiling);
(PreciseNumber sin, PreciseNumber cos) = PreciseNumber.SinCos(million, ceiling);

Assert.AreEqual(ceiling, tangent.SignificantDigits);
AssertAgreesTo(PreciseNumber.Divide(sin, cos, ceiling), tangent, ceiling - 2, "Tan at the reduction ceiling was not sin/cos");

// One digit past the ceiling is still refused, as it is for Sin.
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => PreciseNumber.Tan(million, ceiling + 1));
}

[TestMethod]
public void TestTanAcceptsThePrecisionsSinAndCosAccept()
{
// The issue's reproduction: each of these is within the ceiling for Sin and Cos, and Tan
// refused them because its guard digits were checked as if they were part of the answer.
PreciseNumber one = PreciseNumber.One;
PreciseNumber tangent = PreciseNumber.Tan(one, 145);

Assert.AreEqual(145, tangent.SignificantDigits);
StringAssert.StartsWith(Digits(tangent), Tan1Digits, StringComparison.Ordinal, "Tan(1, 145) is wrong");

Check warning on line 180 in PreciseNumber.Test/PreciseNumberTrigonometryTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.StartsWith' instead of 'StringAssert.StartsWith'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_PreciseNumber&issues=AaETX5yTp1EBACCUliFM&open=AaETX5yTp1EBACCUliFM&pullRequest=160
Assert.AreEqual(145, PreciseNumber.Tan(Parse("0.3"), 145).SignificantDigits);
Assert.AreEqual(PreciseNumber.ConstantPrecision, PreciseNumber.Tan(Parse("0.3"), PreciseNumber.ConstantPrecision).SignificantDigits);
}

[TestMethod]
public void TestSinRefusesAnArgumentTooLargeToReduceAtAll()
{
Expand Down
31 changes: 27 additions & 4 deletions PreciseNumber/PreciseNumber.Trigonometry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,25 @@ public static (PreciseNumber Sin, PreciseNumber Cos) SinCos(PreciseNumber x) =>
public static (PreciseNumber Sin, PreciseNumber Cos) SinCos(PreciseNumber x, int significantDigits)
{
RequireSignificantDigits(significantDigits);
RequireReducibleArgument(significantDigits, IntegerDigitCount(x));
return SinCosCore(x, significantDigits);
}

/// <summary>
/// Computes the sine and cosine of an angle in radians without checking the precision against
/// the reduction ceiling.
/// </summary>
/// <param name="x">The angle, in radians.</param>
/// <param name="significantDigits">The number of significant digits to produce, at least one.</param>
/// <returns>A tuple of the sine and cosine of <paramref name="x"/>.</returns>
/// <remarks>
/// The body of <see cref="SinCos(PreciseNumber, int)"/>, for a caller that has already checked
/// the digits it will report and wants these computed wider as guard margin. Margin is allowed to
/// be unavailable (see <see cref="RequireReducibleArgument(int, int)"/>), so checking the widened
/// precision would refuse requests whose every reported digit is correct.
/// </remarks>
private static (PreciseNumber Sin, PreciseNumber Cos) SinCosCore(PreciseNumber x, int significantDigits)
{
if (x.Significand.IsZero)
{
return (Zero, One);
Expand All @@ -196,8 +214,6 @@ public static (PreciseNumber Sin, PreciseNumber Cos) SinCos(PreciseNumber x, int
int argumentDigits = IntegerDigitCount(x);
int reductionDigits = working + argumentDigits + TrigonometricGuardDigits;

RequireReducibleArgument(significantDigits, argumentDigits);

PreciseNumber piOverTwo = Divide(PiTo(reductionDigits), Two, reductionDigits);
BigInteger quadrant = RoundToNearestInteger(Divide(x, piOverTwo, reductionDigits));
PreciseNumber remainder = Subtract(x, Multiply(new(0, quadrant), piOverTwo))
Expand Down Expand Up @@ -226,16 +242,23 @@ public static PreciseNumber Tan(PreciseNumber x) =>
/// <param name="x">The angle, in radians.</param>
/// <param name="significantDigits">The number of significant digits to produce.</param>
/// <returns>The tangent of <paramref name="x"/>.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="significantDigits"/> is less than one.</exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when <paramref name="significantDigits"/> is less than one, or when it and the integer
/// digits of <paramref name="x"/> together exceed <see cref="ConstantPrecision"/>, exactly as for
/// <see cref="SinCos(PreciseNumber, int)"/>.
/// </exception>
/// <exception cref="DivideByZeroException">Thrown when the cosine of <paramref name="x"/> is zero.</exception>
/// <remarks>
/// <c>sin / cos</c> from one reduction and a single division, rather than two independent series.
/// The ceiling is checked against the requested digits only; the sine and cosine are then
/// computed wider as guard margin, which is not counted against it.
/// </remarks>
public static PreciseNumber Tan(PreciseNumber x, int significantDigits)
{
RequireSignificantDigits(significantDigits);
RequireReducibleArgument(significantDigits, IntegerDigitCount(x));
int working = significantDigits + TrigonometricGuardDigits;
(PreciseNumber sin, PreciseNumber cos) = SinCos(x, working);
(PreciseNumber sin, PreciseNumber cos) = SinCosCore(x, working);
return DivideApproximation(sin, cos, working, significantDigits);
}

Expand Down
Loading