#![cfg(feature = "ball")]
use puremp::{Ball, Float, Int, Rational, RoundingMode, bisect_root};
const M: RoundingMode = RoundingMode::Nearest;
const DOWN: RoundingMode = RoundingMode::TowardNegative;
const UP: RoundingMode = RoundingMode::TowardPositive;
const P: u64 = 100;
const REF: u64 = 260;
fn fr(f: &Float) -> Rational {
f.to_rational().unwrap()
}
fn int_ball(n: i64, prec: u64) -> Ball {
Ball::from_int(&Int::from_i64(n), prec)
}
fn ball_encloses_bracket(b: &Ball, lo: &Float, hi: &Float) {
assert!(
fr(&b.lower()) <= fr(lo),
"ball lower {} not ≤ bracket lower {}",
b.lower(),
lo
);
assert!(
fr(hi) <= fr(&b.upper()),
"ball upper {} not ≥ bracket upper {}",
b.upper(),
hi
);
}
#[test]
fn bisect_encloses_sqrt2() {
let two = int_ball(2, P);
let f = |x: &Ball| x.mul(x).sub(&two);
let root = bisect_root(
f,
&Float::from_f64(1.0, P, M),
&Float::from_f64(2.0, P, M),
P,
200,
)
.expect("certified sign change on [1,2]");
let lo = fr(&root.lower());
let hi = fr(&root.upper());
let two_q = Rational::from_integer(Int::from_i64(2));
assert!(&lo * &lo <= two_q, "lower² = {} > 2", &lo * &lo);
assert!(two_q <= &hi * &hi, "upper² = {} < 2", &hi * &hi);
assert!(
fr(&root.upper()) - fr(&root.lower()) < Rational::new(Int::ONE, Int::from_i64(1_000_000))
);
}
#[test]
fn bisect_encloses_ln2() {
let two = int_ball(2, P);
let f = |x: &Ball| x.exp().sub(&two);
let root = bisect_root(
f,
&Float::from_f64(0.0, P, M),
&Float::from_f64(1.0, P, M),
P,
200,
)
.expect("certified sign change on [0,1]");
let ln2_lo = Float::ln2(REF, DOWN);
let ln2_hi = Float::ln2(REF, UP);
ball_encloses_bracket(&root, &ln2_lo, &ln2_hi);
}
#[test]
fn exp_ln_enclose_true_values() {
for (mid, rad) in [(0.5_f64, 0.01_f64), (1.5, 0.05), (2.25, 0.1), (0.1, 0.001)] {
let b = Ball::new(Float::from_f64(mid, P, M), Float::from_f64(rad, P, M));
let eb = b.exp();
for x in [b.lower(), b.midpoint().clone(), b.upper()] {
let lo = x.exp(REF, DOWN);
let hi = x.exp(REF, UP);
ball_encloses_bracket(&eb, &lo, &hi);
}
let lb = b.ln();
for x in [b.lower(), b.midpoint().clone(), b.upper()] {
let lo = x.ln(REF, DOWN);
let hi = x.ln(REF, UP);
ball_encloses_bracket(&lb, &lo, &hi);
}
}
}
#[test]
fn exp_ln_round_trip() {
for mid in [0.5_f64, 1.5, 2.25, 3.0] {
let b = Ball::new(Float::from_f64(mid, P, M), Float::from_f64(0.01, P, M));
let rt = b.exp().ln();
let m = fr(b.midpoint());
assert!(
fr(&rt.lower()) <= m && m <= fr(&rt.upper()),
"round-trip ball [{}, {}] does not contain midpoint {}",
rt.lower(),
rt.upper(),
b.midpoint()
);
}
}
#[test]
fn ln_nonpositive_is_indeterminate() {
let b = Ball::new(Float::from_f64(0.0, P, M), Float::from_f64(0.5, P, M));
let r = b.ln();
assert!(!r.is_finite());
assert!(r.midpoint().is_nan());
}
#[test]
fn no_sign_change_returns_none() {
let one = int_ball(1, P);
let f = |x: &Ball| x.mul(x).add(&one);
let out = bisect_root(
f,
&Float::from_f64(-1.0, P, M),
&Float::from_f64(1.0, P, M),
P,
50,
);
assert!(out.is_none());
}