fix: correct Geometric::inverse_cdf against its definition - #431
fix: correct Geometric::inverse_cdf against its definition#431teddytennant wants to merge 2 commits into
Conversation
The closed form ceil(ln1p(-x)/ln1p(-p)) is only approximately integral at step boundaries, so plain ceil often returns the wrong side of the true quantile and breaks inverse_cdf(cdf(k)) == k (statrs-dev#342). Correct the candidate with a short cdf check (and bisection near x ~ 1 where 1-x loses precision). Add round-trip and definition regression tests.
cdf(1) is mathematically p, but evaluating via expm1/ln1p can undershoot by a ulp on MSVC, so the definition check in inverse_cdf returned 2 for x=p (Windows CI: Expected 1, got 2 for p=0.2). Special-case cdf(1)/sf(1) and short-circuit inverse_cdf for x <= p.
|
The Coverage (nightly) failure here is unrelated to this change. Recent nightly (1.99.0-nightly, 2026-08-06) started firing the It reproduces on a clean checkout of main (02fdab6) with nothing applied: 90 errors. This branch also produces exactly 90 — it adds no new instances. Without
Happy to send a separate PR swapping those paths over to the associated constants (or dropping the |
Summary
Geometric::inverse_cdfused the closed formceil(ln1p(-x) / ln1p(-p))alone. That quotient is only approximately integral at the step boundaries, soceiloften lands on the wrong side of the true quantile and breaks the discrete inverse definition (leastkwithcdf(k) >= x).Over a sweep of 200 values of
pand 400 ofk, thousands of pairs failedinverse_cdf(cdf(k)) == keven thoughcdfseparated adjacent integers.Root cause
Floating-point evaluation of the closed form is not exact at bucket edges. Being an override, this path was also unreachable by later improvements to the default discrete
inverse_cdf.Fix
Keep the closed form as a fast candidate, then correct it against the definition:
candidate,candidate - 1, andcandidate + 1with two or threecdfevaluations (the usual path);xis within a few ulp of 1,1 - xloses significant bits and the closed form can be off without bound (cdf plateaus about1/pwide), so fall back to exact integer bisection.Test plan
cargo test --lib geometric::cargo test --lib(775 passed, 2 ignored pre-existing pathological cases still marked#[ignore])test_inverse_cdf_round_trips_and_matches_definitiontest_inverse_cdf_is_least_k_with_cdf_at_least_xtest_inverse_cdf_long_plateauFixes #342