Make StorageMath public, and add Cbrt, RootN and Hypot - #247
Merged
Merged
Conversation
StorageMath.Sqrt is the piece of this library that System.Numerics does not offer: a root at the precision of an arbitrary INumber<T>, with no IRootFunctions<T> constraint, which decimal could not have satisfied anyway. It was internal, so an application doing its own vector math over quantities — a norm the generator does not emit — had to reimplement it. The class and Sqrt are now public, with what they guarantee written down rather than left to be discovered: the 256-step cap and the ArithmeticException on a root that does not settle, the double round trip for the primitives, the floor for integers, and OverflowException rather than NaN for a negative value on a type with no NaN. The seeding, the double round trip and the Newton loop stay private. Cbrt, RootN and Hypot ship alongside, on the same seeding and the same loop, since they are what a caller asks for next. Sqrt itself is untouched, so every existing result is unchanged. Cbrt and RootN take the double route only for the binary floating point primitives and refine every integer type in integer arithmetic, so their floor is exact rather than whatever Math.Pow rounded to; each power is taken checked, so a narrow type reports the overflow instead of wrapping into an estimate that goes nowhere. Hypot computes a fractional type from the ratio of its legs, so a pair whose squares leave the type still has its hypotenuse. StorageMathTests pins both halves of the surface — the four methods public, the workings not — so the reason this is public does not erode. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A9Ke7x6EeGyKHfPHncbNrP
RootByNewton carried both loops in one method, switching on whether the type's division floors, which Sonar reads as cognitive complexity 19 against a limit of 15 — and it is right that the two have little to say to each other. RootByDescent stops on the first step that does not descend and answers with the estimate before it; RootBySettling stops when the estimate stops changing, or takes the smaller of a pair it alternates between. Each now says only its own rule. No behaviour changes: same seed, same step, same checked powers, and the suite still runs 1274 tests green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A9Ke7x6EeGyKHfPHncbNrP
|
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 #239
StorageMath.Sqrtis the piece of this library thatSystem.Numericsdoes not offer: a root at the precision of an arbitraryINumber<T>, with noIRootFunctions<T>constraint — which is the whole trick, sincedecimalcould not have satisfied that constraint anyway. It wasinternal, and the generated quantities were its only callers, so an application computing a norm the generator does not emit had to reimplement it.What changed
The class and
Sqrtare public, with what they guarantee written into the XML docs rather than left to be discovered, since this is the point at which the shape freezes:MaximumIterations = 256, and theArithmeticExceptionon a root that does not settledoubleround trip for the binary floating point and integer primitives —Sqrt<double>isMath.Sqrt, not a refinement of itOverflowExceptionrather thanNaNfor a negative value on a type that has noNaNIsRoundedThroughDouble,SeedandTryRootThroughDoublestay private, as the issue asks. This takes option 1 from the issue's "one thing to agree before merging" — apublic static classwith static methods, the smallest diff and the shape that is already there. Option 3, anIStorageMath<T>a caller could substitute, is deliberately not taken here: it is worth having only once there is a higher-precision root to inject (ktsu-dev/PreciseNumber#80), and it can be added later over these methods without them becoming a second way to do the same thing. Say the word if you would rather have it now, before this ships.Cbrt,RootNandHypotship alongside, on the same seeding and the same loop, since they are what a caller asks for next:RootNrefines byx = (((n - 1) * x) + (value / x^(n - 1))) / n; a value outside the range ofdoubleis scaled by powers of2^ninto[1, 2^n)for its seed, the same trickSqrtplays with powers of four.CbrtandRootNtake thedoubleroute only for the binary floating point primitives, and refine every integer type in integer arithmetic. So their floor is exact rather than whateverMath.Powhappened to round to —Cbrt(27) == 3, not 2.estimate^(n-1)it cannot, and an estimate that wrapped would send the steps somewhere arbitrary. A degree so high that the type cannot hold2^nis answered before any of that: no value the type holds has a root of two or more.Hypotcomputes a fractional type aslarger * Sqrt(1 + (smaller / larger)²), so a pair whose squares leave the range of the type still has its hypotenuse —Hypot(1e20m, 1e20m)answers where1e20m * 1e20mthrows. An integer type squares and sums directly, since the ratio of two integers is not a ratio.Exp,Logand trig stay out, as the issue scopes them: they need a series and belong to the storage type.Sqrtitself is untouched — visibility and documentation only — so every existing quantity result is unchanged.Tests
StorageMathTestsgrows 13 test methods, and the class is now 43 tests:TheRootsArePublicAndTheirWorkingsAreNotasserts the surface in both directions — the four methods public, the nine private helpers not. Making the classinternalagain fails it; the rest of the new tests do not compile at all without the change.Math.Cbrtanddouble.Hypotdirectly, so a change to their results fails heredecimalis asserted at its own precision (cubing the refined root lands nearer 2 than cubing thedoubleone), and on exact cubes including negative onesBigIntegerwell beyond the range ofdouble:Cbrt(10^300),RootN(10^500, 5), and the neighbours either side that pin the floorRootN(100, 20),RootN(int.MaxValue, 40), and a degree below one throwingVerification
dotnet buildover the solution: clean, no warnings (this repo builds warnings as errors, acrossnet8.0–net10.0)StorageMath.csand rebuilding fails the new tests, as aboveCLAUDE.md,docs/physics-generator.mdand the package README record the new surface.🤖 Generated with Claude Code
https://claude.ai/code/session_01A9Ke7x6EeGyKHfPHncbNrP
Generated by Claude Code