Open
Conversation
Implements arbitrary-precision nth root with the full RoundingMode contract. Odd degrees accept negative inputs. - Add Calculator::nthRoot() with Newton-Raphson fallback and GmpCalculator override via gmp_root() - Add BigInteger::nthRoot(int $n, RoundingMode = Unnecessary) - Add BigDecimal::nthRoot(int $n, int $scale, RoundingMode = Unnecessary) - Add named exception constructors for the new failure modes - Extend random-tests.php to fuzz nthRoot across all three backends - Update CHANGELOG
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #113 +/- ##
============================================
+ Coverage 98.86% 98.98% +0.12%
- Complexity 709 755 +46
============================================
Files 20 20
Lines 1759 1870 +111
============================================
+ Hits 1739 1851 +112
+ Misses 20 19 -1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
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.
Add
nthRoot()toBigIntegerandBigDecimalSummary
This PR adds
nthRoot()methods toBigIntegerandBigDecimal, providingarbitrary-precision nth root computation with the full
RoundingModecontractused everywhere else in the library. Odd degrees accept negative inputs.
Motivation
brick/mathalready exposessqrt()on bothBigIntegerandBigDecimal,but callers who need a cube root, fourth root, or any higher degree root have
to reach for ad-hoc Newton iterations, or drop down to native
floatarithmetic — losing the arbitrary precision that the library exists to
provide.
Real-world cases where this matters:
(
(final/initial)^(1/n) − 1), and volatility calculations commonly requirenon-square roots at precisions beyond IEEE-754
double.perfect kth power (e.g., Miller–Rabin preconditions, AKS primality) needs a
floor integer nth root.
inversions, bezier parameterizations, and physics-engine mass
distributions.
x^(1/n)wherexhasmore than 15 significant digits.
Today every one of these callers either rolls their own loop — a
well-known source of off-by-one bugs in the floor/ceiling boundary — or
accepts silent
floatrounding. Both are avoidable.API
BigInteger::nthRoot()Returns the integer nth root of
$this, rounded according to$roundingMode.$nmust be≥ 1.$thismay be negative only when$nis odd.RoundingMode::UnnecessarythrowsRoundingNecessaryExceptionif$thisis not a perfect nth power — matching the library-wide contractthat lossy operations require explicit opt-in.
BigDecimal::nthRoot()Returns the nth root rounded to
$scaledecimal places.$nas above.$scalemust be non-negative (as inBigDecimal::sqrt()anddividedBy()).RoundingNecessaryExceptionwith distinct messages for "notexact" vs "exact but scale too small", mirroring
sqrt().Signature parity with
sqrt()sqrt()nthRoot()BigIntegersqrt(RoundingMode = Unnecessary)nthRoot(int $n, RoundingMode = Unnecessary)BigDecimalsqrt(int $scale, RoundingMode = Unnecessary)nthRoot(int $n, int $scale, RoundingMode = Unnecessary)$nRoundingModesImplementation
Two-layer split (matches
sqrt())Calculator layer
Calculator::nthRoot()returns the truncated-toward-zero integer nth root.It has one backend-specific override:
GMP →
gmp_root(), with sign re-applied manually to freezetruncation-toward-zero semantics across PHP/GMP versions.
BCMath & Native → shared Newton-Raphson fallback:
Starting strictly above the true root guarantees monotone convergence
from above; the iterate stabilises when it can no longer decrease.
BigInteger::nthRoot()— roundingAfter obtaining the truncated root
rand computingr^n, the methodbranches on
$roundingMode:Down/Floor/Ceiling/Up— direct choice betweenrandthe next step one unit further from zero.
Half*— would normally require a midpoint comparison. But forconsecutive integers
randr±1, the sumr^n + (r±1)^nis alwaysodd, while
2·|value|is always even, so a midpoint tie isprovably impossible. All five
Half*modes therefore collapse to asingle comparison:
increment ⇔ 2·|value| > r^n + (r±1)^n.BigDecimal::nthRoot()— scale handlingGiven input scale
sand target scaleS:s ≡ 0 (mod n)(append up ton−1zeros).intermediateScale = max(S, s/n) + 1(one guard digit).n·intermediateScale − szeros.Up/Ceiling/Flooraway-from-zero direction, and — since the true value is irrational —
collapse all
Half*modes toHalfUp(no midpoint tie is possible).SviaDecimalHelper::scale().All scale arithmetic goes through
Safe::{add, sub, mul}so overflowsurfaces as
IntegerOverflowExceptionrather than silentfloatcorruption.
Testing
covering: identity cases (
n=1), zero and unit inputs at all degrees,perfect powers for
n ∈ {2, 3, 4, 5, 7, 100}, non-exact boundarycases exercising every
RoundingMode, negative inputs with oddn,fractional inputs (
inputScale % n ≠ 0), large scales (30), and everyexception path.
automatically fans out to the modes that are mathematically
equivalent for the given sign (e.g., positive-value
Up ≡ Ceiling),so no branch is left unexercised.
random-tests.phpnow fuzzesnthRootfork ∈ {2, 3, 4, 5, 7}on both positive and negativeoperands against all three backends, exiting on the first
mismatch.
Test plan
CALCULATOR=Native(13,469 tests pass)CALCULATOR=BCMath(13,469 tests pass)CALCULATOR=BCMathwithBCMATH_DEFAULT_SCALE=8vendor/bin/phpstan --no-progress— no errors at level 10Calculator::nthRootCALCULATOR=GMPon PHP 8.2–8.5 (64-bit and 32-bit)random-tests.phpfor ≥ several million iterations across all three backendstools/ecscoding-standard checkBackward compatibility
Purely additive: no existing API surface changes. Matches the
0.x.ybump convention documented inREADME.mdfor non-breakingadditions.