pub fn bisect(lo: f64, hi: f64, mut below: impl FnMut(f64) -> bool) -> f64 {
let (mut lo, mut hi) = (lo, hi);
for _ in 0..2100 {
let mid = 0.5 * (lo + hi);
if mid <= lo || mid >= hi {
break;
}
if below(mid) {
lo = mid;
} else {
hi = mid;
}
}
0.5 * (lo + hi)
}
pub fn bisect_log(lo: f64, hi: f64, mut below: impl FnMut(f64) -> bool) -> f64 {
let (mut lo, mut hi) = (lo, hi);
for _ in 0..2100 {
let mid = lo.sqrt() * hi.sqrt();
if mid <= lo || mid >= hi {
break;
}
if below(mid) {
lo = mid;
} else {
hi = mid;
}
}
lo.sqrt() * hi.sqrt()
}
pub fn illinois(x0: f64, x1: f64, f0: f64, f1: f64, mut f: impl FnMut(f64) -> f64) -> f64 {
let (mut x0, mut x1, mut f0, mut f1) = (x0, x1, f0, f1);
let positive_left = f0 > 0.0;
let mut side = 0;
for _ in 0..200 {
let x = (x0 * f1 - x1 * f0) / (f1 - f0);
if !(x > x0.min(x1) && x < x0.max(x1)) || (x1 - x0).abs() <= 1e-15 * x1.abs().max(1.0) {
break;
}
let fx = f(x);
if fx == 0.0 {
return x;
}
if (fx > 0.0) == positive_left {
x0 = x;
f0 = fx;
if side == -1 {
f1 *= 0.5;
}
side = -1;
} else {
x1 = x;
f1 = fx;
if side == 1 {
f0 *= 0.5;
}
side = 1;
}
}
0.5 * (x0 + x1)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bisect_reaches_full_precision() {
let r = bisect(1.0, 2.0, |x| x * x * x < 3.0);
let next = f64::from_bits(r.to_bits() + 1);
let prev = f64::from_bits(r.to_bits() - 1);
assert!(prev * prev * prev < 3.0 || next * next * next >= 3.0);
assert!((r - 3f64.cbrt()).abs() <= 2.0 * f64::EPSILON);
}
#[test]
fn bisect_handles_tiny_roots_in_wide_brackets() {
let r = bisect(1e-12, 1e3, |x| x < 3e-10);
assert!((r / 3e-10 - 1.0).abs() < 1e-14);
}
#[test]
fn bisect_log_handles_huge_brackets() {
let r = bisect_log(1e-300, 1e300, |x| x < 7e200);
assert!((r / 7e200 - 1.0).abs() < 1e-14);
}
#[test]
fn illinois_accepts_either_sign_order() {
let f = |x: f64| 2.0 - x * x;
let r = illinois(0.0, 3.0, f(0.0), f(3.0), f);
assert!((r - 2f64.sqrt()).abs() < 1e-14);
let g = |x: f64| x * x - 2.0;
let r = illinois(0.0, 3.0, g(0.0), g(3.0), g);
assert!((r - 2f64.sqrt()).abs() < 1e-14);
}
#[test]
fn illinois_is_fast_on_smooth_functions() {
let mut calls = 0;
let f = |x: f64| x.powi(5) - 7.0;
let r = illinois(0.0, 3.0, f(0.0), f(3.0), |x| {
calls += 1;
f(x)
});
assert!((r - 7f64.powf(0.2)).abs() < 1e-14);
assert!(calls < 30, "{calls} calls");
}
}