From 9b155c48f7001513d0f392f147196873ea43d15a Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Thu, 15 Oct 2026 03:43:57 +0000 Subject: [PATCH] Stop BINOM.INV/CRITBINOM hanging when trials exceed 2^53 Found while testing crates/functions/src/stat.rs:1244 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Opus 5.5 --- crates/functions/src/stat.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/functions/src/stat.rs b/crates/functions/src/stat.rs index 7c50abeaa..10b33880c 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 { @@ -2584,6 +2588,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);