Implement IRootFunctions<PreciseNumber> with Sqrt, Cbrt, RootN and Hypot - #86
Merged
Merged
Conversation
…pot [minor] Every root scales the significand by a power of ten until the degree divides the exponent, then takes an integer Newton root of what is left. Started above the root that iteration is strictly decreasing and stops on the floor of the root exactly, so there is no tolerance to choose and no working precision to carry: a value whose root is exact gets it exactly, however few digits were asked for, the way Divide is exact when a quotient terminates. Nothing routes through double, so 1e400 and 1e-400 root as accurately as 2 does. Precision follows Divide's rule, never below the operand's digits and never below MinimumDivisionPrecision, so rooting a 150 digit constant is not capped at fifty. There is no NaN to return, so Sqrt of a negative value throws rather than answering wrongly. An odd root of a negative value is real and is returned. Fixes #80 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSvvb3UfRbL65zGQy2fip5
|
This was referenced Sep 16, 2026
This was referenced Sep 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Fixes #80
What this adds
PreciseNumbernow satisfiesIRootFunctions<PreciseNumber>:Sqrt,Cbrt,RootNandHypot, each with an overload taking the significant digits to produce.IFloatingPointConstantscame for free —E,PiandTauwere already there.How it works, and where it diverges from the issue's design
The issue proposed Newton over
PreciseNumberwith aMath.Sqrtseed and aReduceSignificancetruncation per step. This does Newton on the significand as an integer instead, which turns out to be simpler and strictly better:v = s · 10^e, so scalingsby10^kuntil the degree dividese - ksplits the problem in two. The exponent is rooted by division alone, and the significand is rooted by integer Newton —x ← ((n-1)·x + v / x^(n-1)) / n, seeded at2^ceil(bits/n), which is above the root and within a factor of two of it. Started above the root the iteration is strictly decreasing and stops on the floor of the root exactly.That buys three things the proposed version would have had to work for:
Sqrttoo, so there is one loop rather than two.BigInteger.Pow(root, n) == scaleddecides whether to round at all, so a value whose root is exact keeps it however few digits were asked for — the same contractDividehas for a terminating quotient.Sqrt(144)is12with two significant digits, andSqrt(x.Squared())isxeven atsignificantDigits: 1.doubleanywhere. The issue flagged the naiveMath.Sqrtseed as the case that gets10^400wrong. There is no seed to get wrong here — the bit length is exact at any magnitude.The two contract decisions in the issue are taken as proposed, and documented on the methods:
SqrtthrowsArgumentOutOfRangeException. There is no NaN, so the alternative is returning something wrong. This is a real divergence fromdoubleand is called out in the XML docs and in the README's Limitations section, because an algorithm ported fromdoublethat leans on NaN has to test the sign itself now.ArithmeticException, matchingStorageMath.Sqrt<T>'s precedent. It is unreachable by construction — the descent is strictly decreasing from a bounded seed — so it is a backstop, not a path. The allowance is64 + n, because the first phase closes a1/nfraction of the gap per step; measured over degrees up to 2000 the worst case needs four iterations more than the degree.One judgement call beyond the issue: the default precision is
max(operand digits, MinimumDivisionPrecision)rather than a flatMinimumDivisionPrecision. That is the ruleDivideactually follows, and a flat fifty would capSqrt(Pi)at fifty digits — exactly the cliff theConstantPrecisionremark was written to remove. Say the word if you'd rather have the flat fifty.Tests
PreciseNumberRootTests.cs, 27 tests, covering the issue's list:Sqrt(x)²agrees withxacross a sweep spanning1e-400to1e400, andRootN(x, n)^nlikewise for n of 2, 3, 4, 5, 9 and 17Sqrt(144) == 12,Cbrt(343) == 7,Cbrt(0.008) == 0.2, and an exact root survivingsignificantDigits: 12e400,2e-400and the odd-exponent1e401— the cases adoubleseed gets wrong — with both digits and exponent assertedCbrt(-8) == -2, the fifth root of -243 being -3, and an even root of a negative value throwingSqrt, degree 0, degreeint.MinValue, a negative degree of zero (DivideByZeroException), and fewer than one significant digitHypoton Pythagorean triples being exact, ignoring signs, and needing no scaling at3e400/4e400where TNumber : IRootFunctions<TNumber>constraint, which is the point of implementing the interfaceProof the tests fail without the change: removing
PreciseNumber.Roots.csand rebuilding gives 104 compile errors —'PreciseNumber' does not contain a definition for 'Sqrt'/'Cbrt'/'RootN'/'Hypot', plus theIRootFunctions<PreciseNumber>constraint no longer being satisfiable. Restored, the full suite is 304 passing, 0 failing — 277 pre-existing, no regressions. The library builds clean with 0 warnings across net7.0, net8.0, net9.0 and net10.0.Benchmarks
RootBenchmarks.cs, on the repository'sDigitsaxis with allocation reported.--job short:Two rows read oddly and are explained in the class remarks rather than left to puzzle over. 8 and 30 digits cost the same because both meet the
MinimumDivisionPrecisionfloor — the digit count only starts driving the work at 200. And the perfect square is the slowest case at 200 digits, not the fastest: its operand carries twice the digits its root does, so following the operand asks for twice the precision. Exactness saves the rounding, not the iterating.No existing benchmark's numbers move; nothing on an existing path was touched.
Docs
README gains root examples, the precision rule, and the negative-input divergence under Limitations.
CLAUDE.mdgains the design note and the new test and benchmark files.🤖 Generated with Claude Code
https://claude.ai/code/session_01MSvvb3UfRbL65zGQy2fip5
Generated by Claude Code