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
8 changes: 8 additions & 0 deletions PreciseNumber.Test/PreciseNumberRootTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -299,4 +299,12 @@ public void TestRootsOfLongSignificandsStayCorrect()
Assert.AreEqual(200, root.SignificantDigits, "A 200 digit input produced a shorter root");
AssertAgreesTo(value, root.Squared(), 195, "The root of a 200 digit value does not square back");
}

[TestMethod]
public void TestNegativeDegreeRootRoundsTheReciprocalOfAnUnroundedRoot()
{
// The square root of 1.0486 is 1.02401…, which rounds to 1.024 at four digits, and 1/1.024 is
// exactly 0.9765625. The true reciprocal is 0.976551…, so it must come from a wider root.
Assert.AreEqual(Parse("0.9766"), PreciseNumber.RootN(Parse("1.0486"), -2, 4), "RootN(1.0486, -2) is wrong");
}
}
22 changes: 22 additions & 0 deletions PreciseNumber.Test/PreciseNumberTrigonometryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -352,4 +352,26 @@ public void TestInverseHalfTurnFamilyReturnsHalfTurns()
Assert.AreEqual(PreciseNumber.One, PreciseNumber.AcosPi(PreciseNumber.NegativeOne, 50));
AssertAgreesTo(Parse("0.25"), PreciseNumber.AtanPi(PreciseNumber.One, 50), 49, "AtanPi(1) is a quarter turn");
}

[TestMethod]
public void TestExactQuotientsOfApproximatePiStillRoundToTheRequestedDigits()
{
// π at working precision halves, or divides by 180, without a remainder. That exact quotient
// must still be rounded to the digits asked for, not handed back with the guard digits on it.
Assert.AreEqual(Parse("1.5708"), PreciseNumber.Asin(PreciseNumber.One, 5), "Asin(1) is wrong");
Assert.AreEqual(Parse("-1.5708"), PreciseNumber.Asin(-PreciseNumber.One, 5), "Asin(-1) is wrong");
Assert.AreEqual(Parse("1.5708"), PreciseNumber.Atan2(PreciseNumber.One, PreciseNumber.Zero, 5), "Atan2(1, 0) is wrong");
Assert.AreEqual(Parse("-1.5708"), PreciseNumber.Atan2(-PreciseNumber.One, PreciseNumber.Zero, 5), "Atan2(-1, 0) is wrong");
Assert.AreEqual(Parse("3.1416"), PreciseNumber.DegreesToRadians(Parse("180"), 5), "DegreesToRadians(180) is wrong");
Assert.AreEqual(Parse("1.5708"), PreciseNumber.DegreesToRadians(Parse("90"), 5), "DegreesToRadians(90) is wrong");
}

[TestMethod]
public void TestHalfTurnFunctionsRoundToTheRequestedDigits()
{
// asin(1/2) = π/6 and acos(1/2) = π/3, so in half turns they are 1/6 and 1/3. tan(π/8) = √2 − 1.
Assert.AreEqual(Parse("0.1666666667"), PreciseNumber.AsinPi(Parse("0.5"), 10), "AsinPi(0.5) is wrong");
Assert.AreEqual(Parse("0.3333333333"), PreciseNumber.AcosPi(Parse("0.5"), 10), "AcosPi(0.5) is wrong");
Assert.AreEqual(Parse("0.4142135624"), PreciseNumber.TanPi(Parse("0.125"), 10), "TanPi(0.125) is wrong");
}
}
2 changes: 1 addition & 1 deletion PreciseNumber/PreciseNumber.Roots.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ public static PreciseNumber RootN(PreciseNumber x, int n, int significantDigits)
{
return x.Significand.IsZero
? throw new DivideByZeroException()
: Divide(One, RootN(x, -n, significantDigits), significantDigits);
: DivideApproximation(One, RootN(x, -n, significantDigits + RootGuardDigits), significantDigits + RootGuardDigits, significantDigits);
}

if (x.Significand.IsZero || n == 1)
Expand Down
35 changes: 26 additions & 9 deletions PreciseNumber/PreciseNumber.Trigonometry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ public static PreciseNumber Tan(PreciseNumber x, int significantDigits)
RequireSignificantDigits(significantDigits);
int working = significantDigits + TrigonometricGuardDigits;
(PreciseNumber sin, PreciseNumber cos) = SinCos(x, working);
return Divide(sin, cos, significantDigits);
return DivideApproximation(sin, cos, working, significantDigits);
}

/// <summary>
Expand Down Expand Up @@ -281,7 +281,7 @@ public static PreciseNumber Asin(PreciseNumber x, int significantDigits)

if (magnitude == One)
{
PreciseNumber halfPi = Divide(PiTo(working), Two, significantDigits);
PreciseNumber halfPi = DivideApproximation(PiTo(working), Two, working, significantDigits);
return x.Significand.Sign > 0 ? halfPi : -halfPi;
}

Expand Down Expand Up @@ -431,7 +431,7 @@ public static PreciseNumber Atan2(PreciseNumber y, PreciseNumber x, int signific
return Zero;
}

PreciseNumber halfPi = Divide(PiTo(working), Two, significantDigits);
PreciseNumber halfPi = DivideApproximation(PiTo(working), Two, working, significantDigits);
return y.Significand.Sign > 0 ? halfPi : -halfPi;
}

Expand Down Expand Up @@ -562,7 +562,7 @@ public static PreciseNumber TanPi(PreciseNumber x, int significantDigits)
RequireSignificantDigits(significantDigits);
int working = significantDigits + TrigonometricGuardDigits;
(PreciseNumber sin, PreciseNumber cos) = SinCosPi(x, working);
return Divide(sin, cos, significantDigits);
return DivideApproximation(sin, cos, working, significantDigits);
}

/// <summary>
Expand Down Expand Up @@ -599,7 +599,7 @@ public static PreciseNumber AsinPi(PreciseNumber x, int significantDigits)
}

int working = significantDigits + TrigonometricGuardDigits;
return Divide(Asin(x, working), PiTo(working), significantDigits);
return DivideApproximation(Asin(x, working), PiTo(working), working, significantDigits);
}

/// <summary>
Expand Down Expand Up @@ -636,7 +636,7 @@ public static PreciseNumber AcosPi(PreciseNumber x, int significantDigits)
}

int working = significantDigits + TrigonometricGuardDigits;
return Divide(Acos(x, working), PiTo(working), significantDigits);
return DivideApproximation(Acos(x, working), PiTo(working), working, significantDigits);
}

/// <summary>
Expand Down Expand Up @@ -664,7 +664,7 @@ public static PreciseNumber AtanPi(PreciseNumber x, int significantDigits)
}

int working = significantDigits + TrigonometricGuardDigits;
return Divide(Atan(x, working), PiTo(working), significantDigits);
return DivideApproximation(Atan(x, working), PiTo(working), working, significantDigits);
}

/// <summary>
Expand Down Expand Up @@ -692,7 +692,7 @@ public static PreciseNumber DegreesToRadians(PreciseNumber degrees, int signific
{
RequireSignificantDigits(significantDigits);
int working = significantDigits + TrigonometricGuardDigits;
return Divide(Multiply(degrees, PiTo(working)), OneEighty, significantDigits);
return DivideApproximation(Multiply(degrees, PiTo(working)), OneEighty, working, significantDigits);
}

/// <summary>
Expand Down Expand Up @@ -720,7 +720,7 @@ public static PreciseNumber RadiansToDegrees(PreciseNumber radians, int signific
{
RequireSignificantDigits(significantDigits);
int working = significantDigits + TrigonometricGuardDigits;
return Divide(Multiply(radians, OneEighty), PiTo(working), significantDigits);
return DivideApproximation(Multiply(radians, OneEighty), PiTo(working), working, significantDigits);
}

/// <summary>
Expand Down Expand Up @@ -853,6 +853,23 @@ private static (PreciseNumber Sin, PreciseNumber Cos) SinCosSeries(PreciseNumber
$"The trigonometric series did not converge to {workingDigits.ToString(InvariantCulture)} significant digits.");
}

/// <summary>
/// Divides a value that is already an approximation, then rounds to the digits the caller asked for.
/// </summary>
/// <param name="numerator">The dividend, carried at <paramref name="workingDigits"/>.</param>
/// <param name="denominator">The divisor.</param>
/// <param name="workingDigits">The significant digits the operands were computed to.</param>
/// <param name="significantDigits">The number of significant digits to produce.</param>
/// <returns>The quotient, rounded to <paramref name="significantDigits"/>.</returns>
/// <remarks>
/// <see cref="Divide(PreciseNumber, PreciseNumber, int)"/> returns a terminating quotient exactly,
/// without rounding it. When an operand is a rounded approximation, such as π at working precision,
/// that exact quotient still carries the operand's guard digits, and they are wrong. Rounding
/// afterwards keeps the result to the digits requested whether or not the quotient terminates.
/// </remarks>
private static PreciseNumber DivideApproximation(PreciseNumber numerator, PreciseNumber denominator, int workingDigits, int significantDigits) =>
Divide(numerator, denominator, workingDigits).ReduceSignificance(significantDigits);

/// <summary>
/// Sums the arc tangent series for a small argument.
/// </summary>
Expand Down
Loading