Severity: MED. Found by the 2026-08-21 defensive review (SV3), probe-verified against the shipped library.
src/negative_binomial.cpp:~320 computes the bisection upper bound as a double and casts it to int unguarded. For small p or large r the bound exceeds INT_MAX: on x86 the cast yields INT_MIN, the search range collapses and every quantile returns 0 — Geometric(1e-9).getQuantile(0.5) returns 0 where the answer is 6.93e8 (the CDF at the returned value is 1e-9, not 0.5); on AArch64 the cast saturates to 2.1e9 and the answer is capped. The one member of the double→int cast family (S6) that produces a wrong answer rather than a formally-UB-but-correct one.
Fix: widen the search bound to std::int64_t (or keep it in double and bisect on doubles), clamp before any narrowing; test: CDF(quantile(q)) ≈ q for Geometric(1e-9) at q ∈ {0.5, 0.99} and NegativeBinomial(1e10, 0.5); fails today.
Severity: MED. Found by the 2026-08-21 defensive review (SV3), probe-verified against the shipped library.
src/negative_binomial.cpp:~320computes the bisection upper bound as adoubleand casts it tointunguarded. For smallpor largerthe bound exceedsINT_MAX: on x86 the cast yieldsINT_MIN, the search range collapses and every quantile returns 0 —Geometric(1e-9).getQuantile(0.5)returns 0 where the answer is 6.93e8 (the CDF at the returned value is 1e-9, not 0.5); on AArch64 the cast saturates to 2.1e9 and the answer is capped. The one member of the double→int cast family (S6) that produces a wrong answer rather than a formally-UB-but-correct one.Fix: widen the search bound to
std::int64_t(or keep it indoubleand bisect on doubles), clamp before any narrowing; test:CDF(quantile(q)) ≈ qforGeometric(1e-9)at q ∈ {0.5, 0.99} andNegativeBinomial(1e10, 0.5); fails today.