What's wrong
Tan(x, significantDigits) (PreciseNumber/PreciseNumber.Trigonometry.cs:234-240) calls the public SinCos(x, significantDigits + TrigonometricGuardDigits). That method runs RequireReducibleArgument(significantDigits, argumentDigits) (line 199) on the widened value.
The guard exists to limit only the digits the answer carries plus the integer digits the reduction consumes. The RequireReducibleArgument remarks, CLAUDE.md and PR #97 all say guard margin must not be limited ("margin is allowed to be unavailable"). Because Tan goes through the checked public SinCos, its 10 guard digits are counted against the ceiling. As a result, Tan refuses every request from ConstantPrecision - argDigits - 9 up to the real ceiling. That is 10 digits that Sin/Cos deliver correctly.
The parameterless Tan(x) is hit hardest. It throws for any argument with at least 140 significant digits, for example Tan(PreciseNumber.Pi / 4). Its docs don't mention this exception.
Reproduction
Sin(1, 145) → 145 digits
Tan(1, 145) → ArgumentOutOfRangeException: Reducing an argument of 1 integer digits to 155 significant digits needs π to 156 digits …
Tan(1, 140) → ArgumentOutOfRangeException (… to 150 significant digits …)
Tan(1, 139) → ok
Tan(0.3, 145) → ArgumentOutOfRangeException (0 integer digits to 155 …)
Tan(Pi/4) → ArgumentOutOfRangeException (… to 161 significant digits …)
The digits are actually available. var (s, c) = SinCos(x, 149); Divide(s, c, 145) matched an independent 220-digit Python decimal reference in all 145 digits for x = 1, 2 and 0.3.
Suggested fix
- In
Tan, call RequireReducibleArgument(significantDigits, IntegerDigitCount(x)) against the requested precision.
- Compute sin and cos at the widened working precision through an unchecked private core of
SinCos. To do this, split SinCos into a checked public wrapper and an unchecked body.
- Add
Tan to the existing …DeliversEveryDigitUpToTheReductionCeiling test.
Related: the default-precision refusal on the constants, filed separately. That one also affects Sin/Cos; this one is specific to Tan.
What's wrong
Tan(x, significantDigits)(PreciseNumber/PreciseNumber.Trigonometry.cs:234-240) calls the publicSinCos(x, significantDigits + TrigonometricGuardDigits). That method runsRequireReducibleArgument(significantDigits, argumentDigits)(line 199) on the widened value.The guard exists to limit only the digits the answer carries plus the integer digits the reduction consumes. The
RequireReducibleArgumentremarks, CLAUDE.md and PR #97 all say guard margin must not be limited ("margin is allowed to be unavailable"). BecauseTangoes through the checked publicSinCos, its 10 guard digits are counted against the ceiling. As a result,Tanrefuses every request fromConstantPrecision - argDigits - 9up to the real ceiling. That is 10 digits thatSin/Cosdeliver correctly.The parameterless
Tan(x)is hit hardest. It throws for any argument with at least 140 significant digits, for exampleTan(PreciseNumber.Pi / 4). Its docs don't mention this exception.Reproduction
The digits are actually available.
var (s, c) = SinCos(x, 149); Divide(s, c, 145)matched an independent 220-digit Pythondecimalreference in all 145 digits for x = 1, 2 and 0.3.Suggested fix
Tan, callRequireReducibleArgument(significantDigits, IntegerDigitCount(x))against the requested precision.SinCos. To do this, splitSinCosinto a checked public wrapper and an unchecked body.Tanto the existing…DeliversEveryDigitUpToTheReductionCeilingtest.Related: the default-precision refusal on the constants, filed separately. That one also affects
Sin/Cos; this one is specific toTan.