Skip to content

Sin/Cos/Tan near a multiple of π/2 return wrong digits and the wrong sign: Sin(PiTo(60), 25) = -3.75e-46, correct is +4.59e-60 #156

Open

Description

@matt-edmondson

What's wrong

SinCos (PreciseNumber/PreciseNumber.Trigonometry.cs ~L195–205) sizes π for the reduction as working + argumentDigits + TrigonometricGuardDigits. That budget covers the argument's integer digits only, not the leading digits that cancel when x lies close to q·π/2. The remainder x − q·π/2 carries an absolute error of about 10^-reductionDigits. When the true remainder is smaller than that, the series runs on rounding noise, and the result is reported as if it had digits correct significant digits. Cos and Tan share the path. SinPi is unaffected because it reduces exactly.

Repro

PiTo(n) below is π rounded to n significant digits. Correct values were computed with Python decimal at 120 digits and confirmed by the library itself at 100 digits.

Call Returned Correct
Sin(PiTo(60), 25) -3.7510582097494e-46 4.592307816406286208998628e-60
Sin(PiTo(40), 10) 4.97115803e-31 1.693993751e-40
Cos(PiTo(60)/2, 25) -1.8755291048747e-46 ≈ 2.3e-60
Tan(PiTo(60), 25) -2.506e-56 ≈ 4.59e-60
Cos(1.57079632679489661923132169163975144, 20) 2.0986e-36 (5 digits) 2.0985846996875529104874…e-36

Asking for enough digits gives the right answer: Sin(PiTo(60), 100) returns 4.592307816406286208998628034…e-60. So the error comes from under-budgeted reduction, not from the series.

Why it matters

Evaluating sin or cos at a rounded π, π/2 or a multiple of either is the classic precision test, and it is exactly what a high-precision library is used for. The library returns a confidently wrong value with the wrong sign and a wrong magnitude. It raises no error, although CLAUDE.md's position on RequireReducibleArgument is that "every reported digit is correct". This is not #124, #125 or #143, which are about throwing at default precision, Tan's guard digits, and requests above ~140 digits.

Suggested fix / acceptance criteria

  • After reduction, measure the cancellation: how far |remainder| sits below the magnitude implied by x. When it exceeds the guard digits, redo the reduction with π widened by that many digits.
  • If the widened reduction would exceed ConstantPrecision, throw the same ArgumentOutOfRangeException that RequireReducibleArgument uses rather than returning noise.
  • Tests:
    • Sin(PiTo(60), 25) returns 4.592307816406286208998628e-60 or throws the documented exception, and never returns a wrong value.
    • The same holds for Cos(PiTo(60)/2, 25) and Tan(PiTo(60), 25).

Activity

  1. matt-edmondson commented on Oct 6, 2026

    @matt-edmondson
    ContributorAuthor

    Triage


    Generated by Claude Code

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 working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions