Repository navigation
Conversation
0PrashantYadav0
left a comment
There was a problem hiding this comment.
The three failing checks were cancelled before a runner picked them up, so CI hasn't actually run yet and needs a re-run.
| s2 = pow( sigma, 2.0 ); | ||
| s2i = 1.0 / s2; | ||
| return ln( s2i * x ) - ( pow( x, 2.0 ) / ( 2.0 * s2 ) ); | ||
| x2 = pow( x, 2.0 ); | ||
| q = s2i * x; | ||
| if ( | ||
| s2 >= FLOAT64_SMALLEST_NORMAL && | ||
| s2 < PINF && | ||
| x2 < PINF && | ||
| q >= FLOAT64_SMALLEST_NORMAL | ||
| ) { | ||
| return ln( q ) - ( x2 / ( 2.0 * s2 ) ); | ||
| } | ||
| // For `sigma` or `x` far from `1`, forming `sigma^2` or `x^2` overflows or underflows, so evaluate via the ratio `x/sigma` instead. `ln( x/sigma^2 )` is taken directly while that ratio is normal, as subtracting two large logarithms cancels when `x` is close to `sigma^2`... | ||
| r = x / sigma; | ||
| q = r / sigma; | ||
| if ( q >= FLOAT64_SMALLEST_NORMAL && q < PINF ) { | ||
| lq = ln( q ); | ||
| } else if ( r >= FLOAT64_SMALLEST_NORMAL ) { | ||
| lq = ln( r ) - ln( sigma ); | ||
| } else { | ||
| lq = ( ln( x ) - ln( sigma ) ) - ln( sigma ); | ||
| } | ||
| return lq - ( 0.5 * r * r ); |
There was a problem hiding this comment.
| s2 = pow( sigma, 2.0 ); | |
| s2i = 1.0 / s2; | |
| return ln( s2i * x ) - ( pow( x, 2.0 ) / ( 2.0 * s2 ) ); | |
| x2 = pow( x, 2.0 ); | |
| q = s2i * x; | |
| if ( | |
| s2 >= FLOAT64_SMALLEST_NORMAL && | |
| s2 < PINF && | |
| x2 < PINF && | |
| q >= FLOAT64_SMALLEST_NORMAL | |
| ) { | |
| return ln( q ) - ( x2 / ( 2.0 * s2 ) ); | |
| } | |
| // For `sigma` or `x` far from `1`, forming `sigma^2` or `x^2` overflows or underflows, so evaluate via the ratio `x/sigma` instead. `ln( x/sigma^2 )` is taken directly while that ratio is normal, as subtracting two large logarithms cancels when `x` is close to `sigma^2`... | |
| r = x / sigma; | |
| q = r / sigma; | |
| if ( q >= FLOAT64_SMALLEST_NORMAL && q < PINF ) { | |
| lq = ln( q ); | |
| } else if ( r >= FLOAT64_SMALLEST_NORMAL ) { | |
| lq = ln( r ) - ln( sigma ); | |
| } else { | |
| lq = ( ln( x ) - ln( sigma ) ) - ln( sigma ); | |
| } | |
| return lq - ( 0.5 * r * r ); | |
| r = x / sigma; | |
| if ( r === PINF ) { | |
| return NINF; | |
| } | |
| // Use `ln( x/sigma^2 )` while it is normal, as `ln( r ) - ln( sigma )` cancels when `x` is close to `sigma^2`: | |
| q = r / sigma; | |
| if ( q >= FLOAT64_SMALLEST_NORMAL && q < PINF ) { | |
| lq = ln( q ); | |
| } else if ( r >= FLOAT64_SMALLEST_NORMAL ) { | |
| lq = ln( r ) - ln( sigma ); | |
| } else { | |
| lq = ( ln( x ) - ln( sigma ) ) - ln( sigma ); | |
| } | |
| return lq - ( 0.5 * r * r ); |
2.0*s2 overflows near sigma = 1e154: logpdf( 1e154, 1e154 ) returns -354.598 (exact -355.098), and the fast path isn't more accurate (the old small_scale value it keeps is itself 43 ULP off). The r === PINF guard fixes logpdf( 1e300, 1e-10 ) returning NaN. Same x/sigma idea as #15798 (comment).
Same in lib/factory.js:88-103 and src/main.c:58-80; then drop s2, s2i, x2, normal and pow (also from manifest.json).
| z = setprecision( BigFloat, 2048 ) do | ||
| X = BigFloat.( x ); | ||
| S = BigFloat.( sigma ); | ||
| Float64.( log.( X ./ ( S .^ 2 ) ) .- ( ( X .^ 2 ) ./ ( 2 .* ( S .^ 2 ) ) ) ); |
There was a problem hiding this comment.
small_scale.json, medium_scale.json and large_scale.json still hold the old Distributions.jl values, so they don't match this runner.jl. small_scale is up to 43 ULP off. Can you regenerate all five? With regenerated fixtures and the change above, every existing tolerance passes. Distributions can also be removed from REQUIRE.
…e/dists/rayleigh/logpdf` For `sigma` outside about [1.5e-154, 1.3e154], `sigma^2` overflows or underflows, and so does `x^2` for large `x`, so the log-PDF came out NaN or a spurious -Infinity where it is finite: `logpdf( 1e200, 1e200 )` is about -461.017 and returned NaN. Keep the existing expression wherever it is safe — it is unchanged, bit for bit, on every point of the existing fixtures — and otherwise evaluate via the ratio `x/sigma`, taking `ln( x/sigma^2 )` directly while that ratio is normal so the two logarithms don't cancel. New `tiny_scale` and `huge_scale` fixtures cover both sides; develop fails all 2,000 of their points.
593df45 to
8251feb
Compare
|
@0PrashantYadav0 thanks — I can't re-run upstream workflows from a fork, so I rebased onto current |
Coverage Report
The above coverage report was generated for the changes in this PR. |
|
Thanks for the rebase, CI is green now. The two inline comments are still open, though: |
…se/dists/rayleigh/logpdf` At `sigma = 1e154`, `sigma^2` is finite but `2*sigma^2` overflows, so the `x^2` term vanished: `logpdf( 1e154, 1e154 )` gave -354.598 (exact -355.098). Also regenerates the small, medium and large scale fixtures with the extended-precision `gen`.
|
@0PrashantYadav0 sorry, I'd missed those two. Both are in 2a7b5aa:
|
| return ln( q ) - ( x2 / ( 2.0 * s2 ) ); | ||
| } | ||
| // For `sigma` or `x` far from `1`, forming `sigma^2` or `x^2` overflows or underflows, so evaluate via the ratio `x/sigma` instead. `ln( x/sigma^2 )` is taken directly while that ratio is normal, as subtracting two large logarithms cancels when `x` is close to `sigma^2`... | ||
| r = x / sigma; |
There was a problem hiding this comment.
| r = x / sigma; | |
| r = x / sigma; | |
| if ( r === PINF ) { | |
| return NINF; | |
| } |
Thanks, logpdf( 1e154, 1e154 ) is right now. The r === PINF guard from the earlier suggestion is still missing, though: logpdf( 1e300, 1e-10 ) returns NaN (Infinity - Infinity) instead of -Infinity. Same in lib/factory.js:94 and src/main.c:71. Could you add a test for it too? Also, Distributions can be dropped from test/fixtures/julia/REQUIRE now.
Description
This pull request:
1instats/base/dists/rayleigh/logpdf(JS, factory and C).For
sigmaoutside about[1.5e-154, 1.3e154],sigma^2overflows or underflows (andx^2does for largex), so the log-PDF comes outNaNor a spurious-Infinitywhere it is finite:This is the same class of problem @0PrashantYadav0 flagged in
rayleigh/cdf(#15772) andrayleigh/logcdf(#15798), in the sibling function.Why it's a hybrid rather than a plain switch to
x/sigma. I first rewrote it entirely in terms of the ratio. That fixed the extremes, but it regressed one point in the existingsmall_scalefixture (x = 0.536,sigma = 0.260). Therelogpdf ≈ -0.057is a small difference of two terms near2.1. Both formulas cancel at that point; the old one just happened to round inside the tolerance. So the existing expression is kept verbatim wherever it is safe —sigma^2normal and finite,x^2finite,x/sigma^2normal — and the function only falls through to the ratio form otherwise. On every one of the 3,000 points in the existing fixtures the result is bit-for-bit identical todevelop.In the ratio branch,
ln( x/sigma^2 )is taken directly while that ratio is normal, falling back toln( r ) - ln( sigma )only when it isn't: subtracting two large logarithms cancels whenxis close tosigma^2. Infactory.js,ln( sigma )and the fast-path test are computed once persigma.Measured against
mpmathat 80 digits on 5,876 points, withsigmalog-uniform in[1e-300, 1e300]andx/sigmain[1e-3, 10](plus 15% scaled down by1e-100):±InfinitydevelopThe five points over 3 ULP and the 71-ULP maximum are the same points with the same values in both rows: the old expression's own cancellation in the normal range, which this PR doesn't touch.
Tests.
geninrunner.jlnow evaluates the textbook formula in 2048-bitBigFloat, matching the pattern from #15774 and #15772. Distributions.jl'slogpdfworks inFloat64and overflows exactly where the bug is, so it can't produce these fixtures. Two new fixtures, both withx/sigmain[1e-3, 10]:tiny_scale.json:sigmain[1e-200, 1e-155], wheresigma^2underflows.huge_scale.json:sigmain[1e155, 1e200], wheresigma^2overflows.They're restricted to those ranges on purpose. An earlier draft sampled the whole
[1e-200, 1e200]and picked up two ordinary-scale points from the unchanged branch that sit at 3 and 5 ULP ondeveloptoo.test.logpdf.js,test.factory.jsandtest.native.jseach gain a block per fixture, at the existing tolerances (3*EPSintest.logpdf.js, 2 ULP viaisAlmostSameValuein the other two). Ondevelop, all 2,000 new assertions fail in bothtest.logpdf.jsandtest.factory.js. With this PR, all three files pass 5,014 assertions. The other three fixtures weren't regenerated.Related Issues
None. Related to #15772 (
rayleigh/cdf) and #15798 (rayleigh/logcdf).Questions
The
pdfsibling has the same overflow (pdf( 1e200, 1e200 )isNaN). I left it out of this PR to keep one package per PR, and will follow up if this approach looks right to you.Other
No.
Checklist
AI Assistance
If you answered "yes" above, how did you use AI assistance?
Disclosure
This PR was written primarily by Claude Code. I found the bug by looking for siblings of the
sigma^2issue in the review of #15772.@stdlib-js/reviewers