What's wrong
PiTo, Ln2To and Ln10To return at most ConstantPrecision (150) digits. That cap is deliberate and is pinned by TestConstantAccessorsReturnTheWholeConstantWhenAskedForMore. #96 reported that callers assume they get the full width they asked for.
PR #97 (6f79d8c) fixed #96 for SinCos only, by adding RequireReducibleArgument. Its commit message ruled out the rest of the API: "Log is unaffected … correct to 130 digits at 180 total working digits. Exp tops out around ConstantPrecision - 1 … so it is documented rather than guarded."
That measurement never asked for more than 150 result digits. When significantDigits is above about 140, the capped constant limits the answer to roughly 148 correct digits, yet the function still reports every digit that was requested. Neither the XML docs nor the README mention the limit.
Affected call sites
These read a constant wider than it can be served, with no guard:
PreciseNumber/PreciseNumber.Exponentials.cs
:163: Log, via Ln10To(working + exponentDigits), whenever the decimal exponent is non-zero
:227, :273, :357, :389: Log2, Log10, Log2P1, Log10P1
:442, :448: Exp, and through it Pow (fractional), Sinh, Cosh and ExpM1 for |x| > 0.5
:532, :563, :608, :641: Exp2, Exp2M1, Exp10, Exp10M1
PreciseNumber/PreciseNumber.Trigonometry.cs
:337: Acos
:539: SinPi, CosPi, TanPi
:445: Atan2 with x < 0
:602, :639, :667, :695, :723: the …Pi inverses and the degree/radian conversions
Reproduction
Run against da99f93 and checked against Python decimal at 260 digits:
| Call |
Correct digits in the result |
Log(12345, 200) |
148 (ends …988588036379…, correct is …988587987647…) |
Exp(5, 200) |
about 147 |
Log2(3, 200) |
148 |
Exp10(0.5, 200) |
148 |
Sinh(3, 200) |
148 |
Acos(0.5, 200) |
148 |
SinPi(0.25, 200) |
150 |
Controls:
Log(2, 200) reads no constant because its exponent is zero, and it is correct to all 200 digits.
Log(12345, 145) is correct.
Sin(0.5, 200) throws ArgumentOutOfRangeException naming the 150-digit ceiling.
So one function clearly refuses a request that its neighbours answer with silently wrong digits.
The one-argument overloads are affected too. They default to the argument's own SignificantDigits, so Log(x) or Exp(x) on any value wider than about 140 digits hits this, for example a parsed 200-digit literal.
Suggested fix
Apply one rule to every function that reads a constant. Either:
- (a) Refuse. Throw when
significantDigits plus the digits the argument consumes exceeds ConstantPrecision, as RequireReducibleArgument does.
- (b) Report only correct digits. Round the result to
min(significantDigits, ConstantPrecision - consumedDigits - 1).
(b) matches how the constant accessors already behave, and it would also settle #124 for the default-precision overloads. Whichever is chosen, update the Log/SinPi paragraph in CLAUDE.md, which currently says those functions have no ceiling.
Acceptance criteria
- Each of
Log(12345, 200), Exp(5, 200), Log2(3, 200), Exp10(0.5, 200), Sinh(3, 200), Acos(0.5, 200) and SinPi(0.25, 200) either throws a documented exception or returns a result whose reported digits all match an independent reference.
Log(2, 200), which reads no constant, stays exact to 200 digits.
- The chosen ceiling is documented on the affected methods.
This follows up #96 / #97.
What's wrong
PiTo,Ln2ToandLn10Toreturn at mostConstantPrecision(150) digits. That cap is deliberate and is pinned byTestConstantAccessorsReturnTheWholeConstantWhenAskedForMore. #96 reported that callers assume they get the full width they asked for.PR #97 (6f79d8c) fixed #96 for
SinCosonly, by addingRequireReducibleArgument. Its commit message ruled out the rest of the API: "Log is unaffected … correct to 130 digits at 180 total working digits. Exp tops out around ConstantPrecision - 1 … so it is documented rather than guarded."That measurement never asked for more than 150 result digits. When
significantDigitsis above about 140, the capped constant limits the answer to roughly 148 correct digits, yet the function still reports every digit that was requested. Neither the XML docs nor the README mention the limit.Affected call sites
These read a constant wider than it can be served, with no guard:
PreciseNumber/PreciseNumber.Exponentials.cs:163:Log, viaLn10To(working + exponentDigits), whenever the decimal exponent is non-zero:227,:273,:357,:389:Log2,Log10,Log2P1,Log10P1:442,:448:Exp, and through itPow(fractional),Sinh,CoshandExpM1for |x| > 0.5:532,:563,:608,:641:Exp2,Exp2M1,Exp10,Exp10M1PreciseNumber/PreciseNumber.Trigonometry.cs:337:Acos:539:SinPi,CosPi,TanPi:445:Atan2with x < 0:602,:639,:667,:695,:723: the…Piinverses and the degree/radian conversionsReproduction
Run against da99f93 and checked against Python
decimalat 260 digits:Log(12345, 200)…988588036379…, correct is…988587987647…)Exp(5, 200)Log2(3, 200)Exp10(0.5, 200)Sinh(3, 200)Acos(0.5, 200)SinPi(0.25, 200)Controls:
Log(2, 200)reads no constant because its exponent is zero, and it is correct to all 200 digits.Log(12345, 145)is correct.Sin(0.5, 200)throwsArgumentOutOfRangeExceptionnaming the 150-digit ceiling.So one function clearly refuses a request that its neighbours answer with silently wrong digits.
The one-argument overloads are affected too. They default to the argument's own
SignificantDigits, soLog(x)orExp(x)on any value wider than about 140 digits hits this, for example a parsed 200-digit literal.Suggested fix
Apply one rule to every function that reads a constant. Either:
significantDigitsplus the digits the argument consumes exceedsConstantPrecision, asRequireReducibleArgumentdoes.min(significantDigits, ConstantPrecision - consumedDigits - 1).(b) matches how the constant accessors already behave, and it would also settle #124 for the default-precision overloads. Whichever is chosen, update the
Log/SinPiparagraph in CLAUDE.md, which currently says those functions have no ceiling.Acceptance criteria
Log(12345, 200),Exp(5, 200),Log2(3, 200),Exp10(0.5, 200),Sinh(3, 200),Acos(0.5, 200)andSinPi(0.25, 200)either throws a documented exception or returns a result whose reported digits all match an independent reference.Log(2, 200), which reads no constant, stays exact to 200 digits.This follows up #96 / #97.