Skip to content

chore: clean-up - #15426

Merged
kgryte merged 2 commits into
developfrom
philipp/fix-propagation-2026-09-22
Sep 23, 2026
Merged

kgryte merged 2 commits into
developfrom
philipp/fix-propagation-2026-09-22

Conversation

@Planeshifter

@Planeshifter Planeshifter commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

Description

What is the purpose of this pull request?

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, matching src/main.c. n*K is 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; becomes sigma2 = variance( k ); sigma = sqrt( sigma2 );, matching src/main.c. The old form round-tripped variance through sqrt and a squaring to recover a value already held exactly, and since chi variance tends to 0.5 as k grows, the 1.0 - ( 2.0*sigma2 ) cancellation amplifies that perturbation: max error over 100k samples of k drops 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/mean from 1 to 0, matching the bound test/test.native.js already 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/skewness tolerance is deliberately left alone. Its fixture margin is unchanged by this PR (21.0% → 21.1% of a 1e-11 relative budget) because the residual error there is dominated by the gamma ratio in chi/mean, not by the arrangement corrected here; tightening it would clamp down on an error source this PR does not touch.

Source commit: ef08b9f5a6f457b574aa57b11b52d07bb70f0d79

Target packages:

  • @stdlib/stats/base/dists/hypergeometric/mean
  • @stdlib/stats/base/dists/chi/skewness

Related Issues

Does this pull request have any related issues?

No.

Questions

Any questions for reviewers of this pull request?

Three sibling packages matched the same search signature and were deliberately excluded; flagging them in case you want them handled separately:

  • lognormal/pdf and triangular/logpdf — lib/main.js and lib/factory.js are currently bit-identical in both packages, and factory.js structurally cannot adopt the C arrangement, since A/denom are hoisted precisely because sigma/the range are fixed while x is not. Aligning main.js alone would trade a holding invariant for a sub-ULP gain, and on triangular/logpdf it burns the medium_range fixture margin from 61% to 89% of a 60-EPS budget.
  • truncated-normal/pdf — the C is the side that is wrong here. Its s2x2 = 2.0*abs2(sigma) formulation underflows below sigma ≈ 1.5e-154 and abs2(x-mu) overflows above |x-mu| ≈ 1.3e154, so mirroring it into JS returns NaN where the current code returns correct finite values. Worth a separate issue against src/main.c.

Other

Any other information relevant to this pull request? This may include screenshots, references, and/or implementation notes.

Also noted while searching, not addressed here: lib/factory.js in lognormal/pdf, triangular/logpdf and truncated-normal/pdf each use a hoisted arrangement that matches neither lib/main.js nor src/main.c. Separately, the lognormal/pdf fixture tolerances (800/1489/305 ULP) are far looser than the measured maximum error of 4 ULP.

Checklist

Please ensure the following tasks are completed before submitting this pull request.

AI Assistance

When authoring the changes proposed in this PR, did you use any kind of AI assistance?

  • Yes
  • No

If you answered "yes" above, how did you use AI assistance?

  • Code generation (e.g., when writing an implementation or fixing a bug)
  • Test/benchmark generation
  • Documentation (including examples)
  • Research and understanding

Disclosure

If you answered "yes" to using AI assistance, please provide a short disclosure indicating how you used AI assistance. This helps reviewers determine how much scrutiny to apply when reviewing your contribution. Example disclosures: "This PR was written primarily by Claude Code." or "I consulted ChatGPT to understand the codebase, but the proposed changes were fully authored manually by myself.".

This PR was authored by Claude Code as part of an automated routine that reviews fixes merged to develop and propagates them to sibling packages with the same defect. Candidate sites were found by comparing each package's lib/main.js against its own src/main.c across all 474 stats/base/dists packages 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

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
@stdlib-bot stdlib-bot added the Statistics Issue or pull request related to statistical functionality. label Sep 22, 2026
@stdlib-bot

stdlib-bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Coverage Report

Package Statements Branches Functions Lines
stats/base/dists/chi/skewness $\\color{green}172/172$
$\\color{green}+0.00\\%$
$\\color{green}8/8$
$\\color{green}+0.00\\%$
$\\color{green}2/2$
$\\color{green}+0.00\\%$
$\\color{green}172/172$
$\\color{green}+0.00\\%$
stats/base/dists/hypergeometric/mean $\\color{green}194/194$
$\\color{green}+0.00\\%$
$\\color{green}13/13$
$\\color{green}+0.00\\%$
$\\color{green}2/2$
$\\color{green}+0.00\\%$
$\\color{green}194/194$
$\\color{green}+0.00\\%$

The above coverage report was generated for the changes in this PR.

@Planeshifter
Planeshifter marked this pull request as ready for review September 22, 2026 15:40
@Planeshifter
Planeshifter requested a review from a team September 22, 2026 15:40
@stdlib-bot stdlib-bot added Needs Review A pull request which needs code review. labels Sep 22, 2026
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
@Planeshifter
Planeshifter requested a review from kgryte September 22, 2026 15:45
return NaN;
}
return n * ( K / N );
return ( n * K ) / N;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@kgryte

kgryte commented Sep 23, 2026

Copy link
Copy Markdown
Member

@anandkaranubc Would you be interested in following up on the truncated-normal question discussed in the OP above?

@kgryte kgryte changed the title refactor: align JS and C operation order in stats/base/dists/* chore: clean-up Sep 23, 2026
@kgryte
kgryte merged commit fed47c5 into develop Sep 23, 2026
13 checks passed
@kgryte
kgryte deleted the philipp/fix-propagation-2026-09-22 branch September 23, 2026 06:15
@stdlib-bot stdlib-bot removed the Needs Review A pull request which needs code review. label Sep 23, 2026
@kgryte kgryte added the Needs Follow-up Pull request requiring one or more follow-up actions. label Sep 23, 2026
kgryte pushed a commit that referenced this pull request Sep 27, 2026
kgryte pushed a commit that referenced this pull request Sep 27, 2026
…l/pdf`

PR-URL: #15576
Reviewed-by: Athan Reines <kgryte@gmail.com>
Closes: stdlib-js/metr-issue-tracker#1376
Ref: #14446
Ref: #15426
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Needs Follow-up Pull request requiring one or more follow-up actions. Statistics Issue or pull request related to statistical functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants