You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
RootN with a negative degree misrounds the last digit: RootN(3, -3, 10) returns 0.6933612743, correct is 0.6933612744 #142
RootGuardDigits is 2 (:27), and the value is rounded three times:
The inner root is rounded to d+2 digits.
The reciprocal is rounded to d+2 digits.
ReduceSignificance(d) rounds again.
Two guard digits are not enough to absorb two roundings before the final one. When the true value lies close to a rounding boundary at d digits, the result rounds the wrong way.
Failure scenario
The true value of 3^(-1/3) is 0.69336127435063…, which rounds to 0.6933612744 at 10 digits.
RootN(3, 3, 12) gives 1.44224957031, rounded up.
Its reciprocal at 12 digits is 0.693361274349.
Rounding that to 10 digits gives 0.6933612743, which is wrong.
A differential sweep against HEAD compared RootN(k, n, d) with RootN(k, n, d + 40).ReduceSignificance(d), for k = 2..300, n ∈ {-2, -3, -5} and d ∈ {5, 10, 20, 50}. It found 27 of 3,588 cases misrounded, including some at the default 50 digits:
Call
Returned
Correct
RootN(3, -3, 10)
0.6933612743
0.6933612744
RootN(20, -2, 10)
0.2236067978
0.2236067977 (true 0.22360679774997…)
RootN(47, -2, 5)
0.14587
0.14586
RootN(41, -3, 5)
0.29001
0.29
RootN(0.5, -3, 8)
1.2599211
1.2599210
RootN(1.5, -3, 6)
0.873581
0.873580
RootN(7, -5, 50)
…70498
…70499
Positive degrees are unaffected. That path floors an integer root and then rounds once.
This is a sibling of #121. Its fix (310faf9) rewrote this line to handle quotients that terminate, but it kept the 2 guard digits, so ordinary misrounding remains. #127 (double rounding in FractionalPow) is scoped to Pow and doesn't cover this path.
Suggested fix
Preferred (exact): take the root of the reciprocal directly in integers, as the positive path does.
For significand s, compute q = floor(10^K / s), with K chosen so that q has about n·(d+2) digits and the resulting exponent is divisible by n.
Take IntegerRootN(q, n). Since floor((floor y)^(1/n)) = floor(y^(1/n)), the truncated digits are exact, and a single ReduceSignificance(d) rounds correctly.
Treat the result as exact only when 10^K % s == 0 and root^n == q.
Cheap mitigation: widen the guard on this path to about 10 digits, like ExponentialGuardDigits and TrigonometricGuardDigits. This makes misrounding much rarer but does not eliminate it.
Priority: Medium. A precision library returning the wrong last digit is a correctness defect, and it also happens at the default 50 digits. The error is at most 1 ulp, only negative degrees are affected, and there's no crash.
Activity
matt-edmondson commentedon Sep 30, 2026
Triage
PreciseNumber.Roots.cs, the negative-degreeRootNpath. Whoever owns the rounding and guard-digit work (Asin(±1), Atan2(y, 0), DegreesToRadians and RootN(x, -n) ignore significantDigits and return wrong trailing digits #121, Pow with a fractional exponent misrounds the last digit: Pow(19, 1.5) ends in …417 instead of …416 (double rounding without guard digits) #127).FractionalPowdouble rounding, scoped toPow). Not a duplicate.Generated by Claude Code