Repository navigation
Conversation
`log1p( -exp( -lambda*x ) )` cancels for small `lambda*x` and is `-Infinity` for `lambda*x < 2^-53`.
Coverage Report
The above coverage report was generated for the changes in this PR. |
| return NINF; | ||
| } | ||
| return log1p( -exp( -lambda * x ) ); | ||
| return log1mexp( lambda * x ); |
There was a problem hiding this comment.
| return log1mexp( lambda * x ); | |
| t = lambda * x; | |
| if ( t < FLOAT64_SMALLEST_NORMAL ) { | |
| return ln( x ) + ln( lambda ); | |
| } | |
| return log1mexp( t ); |
lambda * x can underflow: logcdf( 1e-200, 1e-200 ) returns -Infinity (exact ≈ -921.034). Same fix as #15797 (comment). Same in lib/factory.js:71; needs var t;, ln and FLOAT64_SMALLEST_NORMAL.
| return STDLIB_CONSTANT_FLOAT64_NINF; | ||
| } | ||
| return stdlib_base_log1p( -stdlib_base_exp( -lambda * x ) ); | ||
| return stdlib_base_log1mexp( lambda * x ); |
There was a problem hiding this comment.
| return stdlib_base_log1mexp( lambda * x ); | |
| t = lambda * x; | |
| if ( t < STDLIB_CONSTANT_FLOAT64_SMALLEST_NORMAL ) { | |
| return stdlib_base_ln( x ) + stdlib_base_ln( lambda ); | |
| } | |
| return stdlib_base_log1mexp( t ); |
Same as lib/main.js:74; also needs double t;, the ln.h/smallest_normal.h includes and both deps in manifest.json.
|
|
||
| # Small `x`: | ||
| x = exp10.( range( -300.0, stop = -1.0, length = 1000 ) ); | ||
| lambda = ( rand( 1000 ) .* 10.0 ) .+ 0.1; |
There was a problem hiding this comment.
| lambda = ( rand( 1000 ) .* 10.0 ) .+ 0.1; | |
| lambda = exp10.( ( rand( 1000 ) .* 41.0 ) .- 40.0 ); |
With lambda in [0.1, 10.1], lambda*x never gets subnormal, so the case above isn't tested (with this range the current code fails 33/1000). Same as #15797 (comment).
…ogcdf` `lambda * x` underflows for small arguments, so `logcdf( 1e-200, 1e-200 )` returned -Infinity instead of about -921.034. Below the smallest normal, `ln(1 - exp(-t))` is `ln(t)` to within rounding, so evaluate it as `ln(x) + ln(lambda)` there, as `weibull/logcdf` does. The small-`x` fixture now draws `lambda` from [1e-40, 1e1] so it reaches that branch; the previous code fails 36 of its points.
|
@0PrashantYadav0 thanks — applied in 21b42d6:
|
Description
This pull request:
stats/base/dists/exponential/logcdfaslog1mexp( lambda*x )instead oflog1p( -exp( -lambda*x ) )(JS, factory and C).For small
lambda*x,exp(-lambda*x)is close to1, andlog1pof minus that loses the digits oflambda*x. Forlambda*x < 2^-53the result is-Infinity:log1mexpusesln( -expm1( -x ) )forx <= ln(2)andlog1p( -exp( -x ) )above it, so largelambda*xtakes the same path as before.Tests: there's a new
test/fixtures/julia/small_x.json(xlog-spaced over[1e-300, 0.1],lambdain[0.1, 10.1]).runner.jlevaluatesln(1 - exp(-lambda*x))in 2048-bitBigFloat. The new tests intest.logcdf.js,test.factory.jsandtest.native.jspass at 1 ULP. Ondevelop, 996 of those assertions fail.The R fixture keeps its 511-ULP tolerance. Its worst cases have
lambda*x ≈ 267, where the result is-exp(-267), and the half-ULP rounding of the productlambda*xalone is multiplied bylambda*x. That's the conditioning of the function, not this formula.Related Issues
None. Companion to #15770 (
exponential/cdf).Questions
No.
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 scanning
stats/base/distsfor1 - exp(...).@stdlib-js/reviewers