What's wrong
Several methods finish with Divide(approx, k, significantDigits), where approx has already been rounded to a wider working precision, for example PiTo(working) or a rounded root. When that quotient happens to terminate, Divide takes its exact path and never rounds to significantDigits. The caller gets the working-precision approximation divided exactly. That result has more digits than it asked for, and the extra digits are wrong.
Affected call sites:
PreciseNumber/PreciseNumber.Trigonometry.cs: Asin, the magnitude == One branch: Divide(PiTo(working), Two, significantDigits)
PreciseNumber/PreciseNumber.Trigonometry.cs: Atan2, the x == 0 branch: same expression
PreciseNumber/PreciseNumber.Trigonometry.cs: DegreesToRadians: Divide(Multiply(degrees, PiTo(working)), OneEighty, significantDigits)
PreciseNumber/PreciseNumber.Roots.cs: RootN with n < 0: Divide(One, RootN(x, -n, significantDigits), significantDigits)
Reproduction (actual → expected)
| Call |
Actual |
Expected |
Asin(1, 5) |
1.570796326794895 (16 digits; the last two are wrong, since π/2 = 1.5707963267948966…) |
1.5708 |
Atan2(1, 0, 5) |
1.570796326794895 |
1.5708 |
DegreesToRadians(180, 5) |
3.14159265358979 |
3.1416 |
RootN(1.0486, -2, 4) |
0.9765625 (exactly 1/1.024; the true value is 0.976551…) |
0.9766 |
DegreesToRadians(30, 5) returns 0.5236 correctly, because 30/180 doesn't terminate. Acos(-1, 5) returns 3.1416 correctly, because it ends with ReduceSignificance. So the library currently gives different answers for π depending on which function computed it.
Why it matters
significantDigits is the contract of every one of these APIs. Here the result is both longer than requested and wrong in its trailing digits. The RootN case is wrong from the 5th significant digit even though 4 were requested. Code that compares or hashes results at a fixed precision will see spurious differences.
Suggested fix
End each of these paths with .ReduceSignificance(significantDigits) and do the division at working precision. For example:
PreciseNumber halfPi = Divide(PiTo(working), Two, working).ReduceSignificance(significantDigits);
For RootN(x, -n), compute the inner root at significantDigits + guard, divide at the same working precision, then reduce. A more general option is a small helper that always rounds to significantDigits, even when the quotient is exact, for any caller whose operand is already an approximation. Tan/TanPi use a similar pattern and should be audited too.
Acceptance: each row in the table above returns the expected value, with tests added.
What's wrong
Several methods finish with
Divide(approx, k, significantDigits), whereapproxhas already been rounded to a wider working precision, for examplePiTo(working)or a rounded root. When that quotient happens to terminate,Dividetakes its exact path and never rounds tosignificantDigits. The caller gets the working-precision approximation divided exactly. That result has more digits than it asked for, and the extra digits are wrong.Affected call sites:
PreciseNumber/PreciseNumber.Trigonometry.cs:Asin, themagnitude == Onebranch:Divide(PiTo(working), Two, significantDigits)PreciseNumber/PreciseNumber.Trigonometry.cs:Atan2, thex == 0branch: same expressionPreciseNumber/PreciseNumber.Trigonometry.cs:DegreesToRadians:Divide(Multiply(degrees, PiTo(working)), OneEighty, significantDigits)PreciseNumber/PreciseNumber.Roots.cs:RootNwithn < 0:Divide(One, RootN(x, -n, significantDigits), significantDigits)Reproduction (actual → expected)
Asin(1, 5)1.570796326794895(16 digits; the last two are wrong, since π/2 = 1.5707963267948966…)1.5708Atan2(1, 0, 5)1.5707963267948951.5708DegreesToRadians(180, 5)3.141592653589793.1416RootN(1.0486, -2, 4)0.9765625(exactly 1/1.024; the true value is 0.976551…)0.9766DegreesToRadians(30, 5)returns0.5236correctly, because 30/180 doesn't terminate.Acos(-1, 5)returns3.1416correctly, because it ends withReduceSignificance. So the library currently gives different answers for π depending on which function computed it.Why it matters
significantDigitsis the contract of every one of these APIs. Here the result is both longer than requested and wrong in its trailing digits. The RootN case is wrong from the 5th significant digit even though 4 were requested. Code that compares or hashes results at a fixed precision will see spurious differences.Suggested fix
End each of these paths with
.ReduceSignificance(significantDigits)and do the division atworkingprecision. For example:For
RootN(x, -n), compute the inner root atsignificantDigits + guard, divide at the same working precision, then reduce. A more general option is a small helper that always rounds tosignificantDigits, even when the quotient is exact, for any caller whose operand is already an approximation.Tan/TanPiuse a similar pattern and should be audited too.Acceptance: each row in the table above returns the expected value, with tests added.