diff --git a/crates/functions/src/stat.rs b/crates/functions/src/stat.rs index 9d8642e24..06a0ac790 100644 --- a/crates/functions/src/stat.rs +++ b/crates/functions/src/stat.rs @@ -1243,6 +1243,10 @@ fn binom_inv(a: &[Arg], _: C) -> R { } while hi - lo > 1.0 { let mid = ((lo + hi) / 2.0).floor(); + // Above 2^53 adjacent f64 values are more than 1 apart, so the gap can stop shrinking. + if mid <= lo || mid >= hi { + break; + } if binom_cdf(mid, n, p) >= alpha { hi = mid; } else { @@ -2624,6 +2628,24 @@ mod tests { is_err(ev("F.DIST", vec![n(1.0), n(0.0), n(4.0), b(true)]), CellError::Num); } + #[test] + fn binom_inv_huge_trials_returns() { + // Above 2^53 neighbouring f64 values are more than 1 apart; the bisection must still end. + let cases: [(&str, f64, f64, f64); 3] = [("BINOM.INV", 1e17, 0.5, 0.5), ("CRITBINOM", 1e300, 1.0, 0.5), ("BINOM.INV", 1e300, 0.5, 0.5)]; + for (name, trials, p, alpha) in cases { + let (tx, rx) = std::sync::mpsc::channel(); + std::thread::spawn(move || { + let r = ev(name, vec![n(trials), n(p), n(alpha)]); + let _ = tx.send(r); + }); + match rx.recv_timeout(std::time::Duration::from_secs(5)) { + Ok(Value::Number(_)) | Ok(Value::Error(CellError::Num)) => {} + Ok(other) => panic!("{name}({trials},{p},{alpha}) returned {other:?}"), + Err(_) => panic!("{name}({trials},{p},{alpha}) did not return within 5 s"), + } + } + } + #[test] fn discrete() { close_tol(ev("BINOM.DIST", vec![n(6.0), n(10.0), n(0.5), b(false)]), 0.205078125, 1e-12);