Skip to content

Asin(±1), Atan2(y, 0), DegreesToRadians and RootN(x, -n) ignore significantDigits and return wrong trailing digits #121

Description

@matt-edmondson

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingreadyFully specified; implement as written

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions