use crate::binopdf;
pub fn binocdf(x: f64, n: f64, p: f64, upper: bool) -> f64 {
let (x, p) = if upper { (n - 1.0 - f64::floor(x), 1.0 - p) } else { (x, p) };
let mut y = 0.0;
if x >= n {
y = 1.0;
}
if x.is_nan() {
y = f64::NAN;
}
let k2 = p == 0.0 && x >= 0.0;
if k2 {
y = 1.0;
}
let k3 = p == 1.0;
if k3 {
y = if x >= n { 1.0 } else { 0.0 };
}
let k1 = n < 0.0 || p < 0.0 || p > 1.0 || n != f64::round(n);
if k1 {
y = f64::NAN;
}
let xx = f64::floor(x);
let k = xx >= 0.0 && xx < n && !k1 && !k2 && !k3;
if k {
let smallp = 1.0e-4; let bign = 1.0e5;
let np = n * p;
let t = (n < bign) & ((x <= np) | (p < smallp));
if t {
y = sumfrom0(xx, n, p);
} else if n < bign {
y = 1.0 - sumfrom0(n - xx - 1.0, n, 1.0 - p);
} else {
y = sumA2B(xx, n, p);
}
}
return y.clamp(0.0, 1.0);
}
fn sumfrom0(xx: f64, n: f64, p: f64) -> f64 {
let y: f64 = (0..=xx as usize).map(|i| binopdf(i as f64, n, p)).sum();
return y;
}
#[allow(non_snake_case)]
fn sumA2B(x: f64, N: f64, p: f64) -> f64 {
let mut y: f64 = 0.0;
let mu = N * p;
let std = f64::sqrt(N * p * (1.0 - p));
let mut t1 = 40.0;
let mut t2 = 10.0;
while binopdf(f64::floor(mu - t1 * std), N, p) > eps(0.0) {
t1 = 1.5 * t1;
}
while binopdf(f64::ceil(mu + t2 * std), N, p) > eps(1.0) {
t2 = 1.5 * t2;
}
let a = f64::max(0.0, f64::floor(mu - t1 * std));
let b = f64::ceil(mu + t2 * std);
if x < a {
y = 0.0;
}
if x > b {
y = 1.0;
}
if x >= a && x <= b {
let mut sums = (a as usize .. x as usize + 1).map(|i| binopdf(i as f64, N, p)).collect::<Vec<_>>();
for i in 1..sums.len() {
sums[i] += sums[i - 1];
}
y = sums[x as usize - a as usize];
}
return y;
}
fn eps(x: f64) -> f64 {
let next_up = x.next_up();
return next_up - x;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_binocdf_ex() {
let x = 100.0;
let n = 123456.0;
let p = 0.001;
let y = binocdf(x, n, p, false);
assert!((y - 1.695618562854513e-02).abs() < 1.0e-15);
}
#[test]
fn test_binocdf_basic() {
let tol = 1e-15;
assert!((binocdf(2.0, 4.0, 0.5, false) - 0.6875).abs() < tol);
assert!((binocdf(2.0, 4.0, 0.5, true) - 0.3125).abs() < tol);
}
#[test]
fn test_binocdf_boundaries() {
assert_eq!(binocdf(-1.0, 10.0, 0.5, false), 0.0);
assert_eq!(binocdf(-1.0, 10.0, 0.5, true), 1.0);
assert_eq!(binocdf(10.0, 10.0, 0.5, false), 1.0);
assert_eq!(binocdf(10.0, 10.0, 0.5, true), 0.0);
assert_eq!(binocdf(11.0, 10.0, 0.5, false), 1.0);
}
#[test]
fn test_binocdf_edge_p() {
assert_eq!(binocdf(0.0, 5.0, 0.0, false), 1.0);
assert_eq!(binocdf(-1.0, 5.0, 0.0, false), 0.0);
assert_eq!(binocdf(5.0, 5.0, 0.0, false), 1.0);
assert_eq!(binocdf(4.0, 5.0, 1.0, false), 0.0);
assert_eq!(binocdf(5.0, 5.0, 1.0, false), 1.0);
assert_eq!(binocdf(4.0, 5.0, 1.0, true), 1.0);
}
#[test]
fn test_binocdf_invalid() {
assert!(binocdf(2.0, -1.0, 0.5, false).is_nan());
assert!(binocdf(2.0, 4.0, -0.1, false).is_nan());
assert!(binocdf(2.0, 4.0, 1.1, false).is_nan());
assert!(binocdf(2.0, 4.5, 0.5, false).is_nan());
assert!(binocdf(f64::NAN, 4.0, 0.5, false).is_nan());
}
#[test]
fn test_binocdf_sum_switch() {
let val = binocdf(1.0, 100.0, 1e-5, false);
let p: f64 = 1e-5;
let n: f64 = 100.0;
let expected = (1.0 - p).powf(n) + n * p * (1.0 - p).powf(n - 1.0);
assert!((val - expected).abs() < 1e-14);
}
#[test]
fn test_binocdf_large_n() {
let val = binocdf(50000.0, 100000.0, 0.5, false);
assert!((val - 5.012615631070982e-01).abs() < 1e-8);
}
}