Repository navigation
chore: clean-up - #15426
chore: clean-up#15426
Conversation
Propagates fix from ef08b9f ("refactor: align JS implementation and migrate `hypergeometric/variance` to ULP-based assertions") to sibling packages whose JavaScript implementations evaluate the same formula in a different floating-point operation order than their own C implementations, causing the JS and native builds to disagree in the last bits. In `hypergeometric/mean`, `n * ( K / N )` rounds twice, while the C computes `( n * K ) / N`, a single correctly-rounded division; the product `n * K` is exact below 2^53. The JS differs from the C on 24% of the integer domain by 1 ULP, and the patched form now reproduces the Julia fixture exactly. In `chi/skewness`, the JS round-tripped the variance through `sqrt` and a squaring to recover `sigma2`, perturbing a value it already held exactly. Because the chi variance tends to 0.5, the subsequent `1.0 - ( 2.0*sigma2 )` cancellation amplifies that perturbation, giving a measured maximum error of 838 ULP against 3 ULP for the C ordering. Neither change requires test or fixture updates, and documented values are unaffected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YRk2WJxnRNdX8txWkmyLtb
Coverage Report
The above coverage report was generated for the changes in this PR. |
The JS test asserted a 1 ULP bound while `test.native.js` asserted 0. That slack existed only to accommodate the JS implementation's operation order, which rounded twice where the C performed a single correctly rounded division. With the two implementations aligned in 0d3105d, the JS build reproduces the Julia fixture exactly, so the bound is tightened to 0 to match the native test and lock in the alignment. Verified as a real guard: at 0 ULP the previous arrangement fails 20 of 100 fixture cases and the current one passes all 100. The bound is deterministic across platforms; the computation is an integer product and a single IEEE-754 division, with no transcendental functions involved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YRk2WJxnRNdX8txWkmyLtb
| return NaN; | ||
| } | ||
| return n * ( K / N ); | ||
| return ( n * K ) / N; |
There was a problem hiding this comment.
In principle, this computation has a greater risk of overflow, including just to the realm beyond safe integers. However, practically speaking, that is not a common use case, so I am fine having this match the C implementation.
There was a problem hiding this comment.
Confirmed, and worth pinning down the boundary: the product overflows once n*K exceeds sqrt(MAX_VALUE) ≈ 1.34e154, so mean( 1e160, 1e160, 1e160 ) now returns Infinity where the previous arrangement returned 1e160.
The asymmetry is JS-only. src/main.c takes int32_t, so the C cannot reach that regime at all, whereas the JS guard is isNonNegativeInteger, which accepts 1e300. So this is a case where matching the C narrows the JS domain rather than just reordering within it.
Leaving as-is per your call. Noting the threshold here so it's on the record if anyone later hits it.
Generated by Claude Code
|
@anandkaranubc Would you be interested in following up on the truncated-normal question discussed in the OP above? |
stats/base/dists/*PR-URL: #15577 Reviewed-by: Athan Reines <kgryte@gmail.com> Closes: stdlib-js/metr-issue-tracker#1377 Ref: #14446 Ref: #15426
…l/pdf` PR-URL: #15576 Reviewed-by: Athan Reines <kgryte@gmail.com> Closes: stdlib-js/metr-issue-tracker#1376 Ref: #14446 Ref: #15426
Description
This pull request propagates the operation-ordering fix from
ef08b9f5(align JS floating-point evaluation order with each package's C implementation) to two sibling distributions.For
@stdlib/stats/base/dists/hypergeometric/mean,n * ( K / N )becomes( n * K ) / N, matchingsrc/main.c.n*Kis exact below 2^53, so the C form is a single correctly-rounded division while the old form rounds twice; the two forms disagree by 1 ULP on 24% of the integer domain, and the package's own test files already asserted 1 ULP for JS versus 0 ULP for native — in-repo evidence the divergence was known. Post-fix, the implementation reproduces the package's Julia fixture bit-for-bit, versus 20/100 mismatched cases before.For
@stdlib/stats/base/dists/chi/skewness,sigma = sqrt( variance( k ) ); sigma2 = sigma * sigma;becomessigma2 = variance( k ); sigma = sqrt( sigma2 );, matchingsrc/main.c. The old form round-trippedvariancethroughsqrtand a squaring to recover a value already held exactly, and since chi variance tends to 0.5 askgrows, the1.0 - ( 2.0*sigma2 )cancellation amplifies that perturbation: max error over 100k samples ofkdrops from 838 ULP to 3 ULP with the corrected ordering.Documented example values are unchanged in both packages.
A follow-up commit tightens the JS ULP bound in
hypergeometric/meanfrom 1 to 0, matching the boundtest/test.native.jsalready asserts. The 1 ULP slack existed only to accommodate the old operation order; with the implementations aligned the JS build reproduces the fixture exactly. Verified as a real guard rather than a cosmetic change: at 0 ULP the previous arrangement fails 20 of 100 fixture cases and the current one passes all 100. The bound is deterministic across platforms, since the computation is an integer product and a single IEEE-754 division with no transcendental functions involved.The
chi/skewnesstolerance is deliberately left alone. Its fixture margin is unchanged by this PR (21.0% → 21.1% of a1e-11relative budget) because the residual error there is dominated by the gamma ratio inchi/mean, not by the arrangement corrected here; tightening it would clamp down on an error source this PR does not touch.Source commit:
ef08b9f5a6f457b574aa57b11b52d07bb70f0d79Target packages:
@stdlib/stats/base/dists/hypergeometric/mean@stdlib/stats/base/dists/chi/skewnessRelated Issues
No.
Questions
Three sibling packages matched the same search signature and were deliberately excluded; flagging them in case you want them handled separately:
lognormal/pdfandtriangular/logpdf—lib/main.jsandlib/factory.jsare currently bit-identical in both packages, andfactory.jsstructurally cannot adopt the C arrangement, sinceA/denomare hoisted precisely becausesigma/the range are fixed whilexis not. Aligningmain.jsalone would trade a holding invariant for a sub-ULP gain, and ontriangular/logpdfit burns themedium_rangefixture margin from 61% to 89% of a 60-EPS budget.truncated-normal/pdf— the C is the side that is wrong here. Itss2x2 = 2.0*abs2(sigma)formulation underflows belowsigma ≈ 1.5e-154andabs2(x-mu)overflows above|x-mu| ≈ 1.3e154, so mirroring it into JS returnsNaNwhere the current code returns correct finite values. Worth a separate issue againstsrc/main.c.Other
Also noted while searching, not addressed here:
lib/factory.jsinlognormal/pdf,triangular/logpdfandtruncated-normal/pdfeach use a hoisted arrangement that matches neitherlib/main.jsnorsrc/main.c. Separately, thelognormal/pdffixture tolerances (800/1489/305 ULP) are far looser than the measured maximum error of 4 ULP.Checklist
AI Assistance
If you answered "yes" above, how did you use AI assistance?
Disclosure
This PR was authored by Claude Code as part of an automated routine that reviews fixes merged to
developand propagates them to sibling packages with the same defect. Candidate sites were found by comparing each package'slib/main.jsagainst its ownsrc/main.cacross all 474stats/base/distspackages that ship both, then validated by independent agents that measured the divergence empirically and compared both arrangements against exact-rational and arbitrary-precision references. Three of the five candidate sites were rejected on those measurements and are described above. The numbers quoted in this description were reproduced against the packages' own fixtures before committing.🤖 Generated with Claude Code
https://claude.ai/code/session_01YRk2WJxnRNdX8txWkmyLtb