use std::cmp::Ordering;
use num_bigint::{BigInt, Sign};
use num_rational::BigRational;
use num_traits::{One, Signed, ToPrimitive, Zero};
pub type Rational = BigRational;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Ball {
pub lo: Rational,
pub hi: Rational,
}
impl Ball {
pub fn new(lo: Rational, hi: Rational) -> Self {
if lo <= hi {
Ball { lo, hi }
} else {
Ball { lo: hi, hi: lo }
}
}
pub fn point(r: &Rational) -> Self {
Ball { lo: r.clone(), hi: r.clone() }
}
pub fn from_i64(n: i64) -> Self {
Self::point(&BigRational::from_integer(BigInt::from(n)))
}
pub fn sign(&self) -> Option<Sign> {
if self.lo.is_positive() {
Some(Sign::Plus)
} else if self.hi.is_negative() {
Some(Sign::Minus)
} else if self.lo.is_zero() && self.hi.is_zero() {
Some(Sign::NoSign)
} else {
None
}
}
pub fn cmp(&self, other: &Ball) -> Option<Ordering> {
if self.hi < other.lo {
Some(Ordering::Less)
} else if self.lo > other.hi {
Some(Ordering::Greater)
} else if self.lo == self.hi && other.lo == other.hi && self.lo == other.lo {
Some(Ordering::Equal)
} else {
None
}
}
pub fn contains_zero(&self) -> bool {
!self.lo.is_positive() && !self.hi.is_negative()
}
pub fn contains(&self, v: &Rational) -> bool {
self.lo <= *v && *v <= self.hi
}
pub fn width(&self) -> Rational {
&self.hi - &self.lo
}
pub fn midpoint(&self) -> Rational {
(&self.lo + &self.hi) / BigRational::from_integer(BigInt::from(2))
}
pub fn to_f64(&self) -> f64 {
rational_to_f64(&self.midpoint())
}
pub fn add(&self, b: &Ball) -> Ball {
Ball::new(&self.lo + &b.lo, &self.hi + &b.hi)
}
pub fn neg(&self) -> Ball {
Ball::new(-&self.hi, -&self.lo)
}
pub fn sub(&self, b: &Ball) -> Ball {
Ball::new(&self.lo - &b.hi, &self.hi - &b.lo)
}
pub fn mul(&self, b: &Ball) -> Ball {
let products = [
&self.lo * &b.lo,
&self.lo * &b.hi,
&self.hi * &b.lo,
&self.hi * &b.hi,
];
let mut min = products[0].clone();
let mut max = products[0].clone();
for p in &products[1..] {
if *p < min {
min = p.clone();
}
if *p > max {
max = p.clone();
}
}
Ball::new(min, max)
}
pub fn recip(&self) -> Option<Ball> {
if self.contains_zero() {
return None;
}
let one = BigRational::one();
Some(Ball::new(&one / &self.hi, &one / &self.lo))
}
}
pub fn rational_to_f64(r: &Rational) -> f64 {
let p = r.numer();
let q = r.denom();
if q.is_zero() {
return f64::NAN;
}
if p.is_zero() {
return 0.0;
}
let negative = (p.sign() == Sign::Minus) ^ (q.sign() == Sign::Minus);
let mut pm = p.magnitude().clone();
let mut qm = q.magnitude().clone();
let pb = pm.bits() as i64;
let qb = qm.bits() as i64;
let shift = 60 + qb - pb;
if shift > 0 {
pm <<= shift as u64;
} else {
qm <<= (-shift) as u64;
}
let quo = &pm / &qm;
let mag = quo.to_f64().unwrap_or(f64::INFINITY);
let value = mag * 2f64.powi(-(shift as i32));
if negative {
-value
} else {
value
}
}
pub fn rat(p: i64, q: i64) -> Rational {
BigRational::new(BigInt::from(p), BigInt::from(q))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sign_and_straddle() {
assert_eq!(Ball::new(rat(1, 2), rat(3, 2)).sign(), Some(Sign::Plus));
assert_eq!(Ball::new(rat(-3, 2), rat(-1, 2)).sign(), Some(Sign::Minus));
assert_eq!(Ball::new(rat(-1, 1), rat(1, 1)).sign(), None);
assert_eq!(Ball::from_i64(0).sign(), Some(Sign::NoSign));
}
#[test]
fn ordering() {
let a = Ball::from_i64(1);
let b = Ball::from_i64(2);
assert_eq!(a.cmp(&b), Some(Ordering::Less));
assert_eq!(b.cmp(&a), Some(Ordering::Greater));
assert_eq!(Ball::new(rat(0, 1), rat(2, 1)).cmp(&Ball::new(rat(1, 1), rat(3, 1))), None);
}
#[test]
fn interval_arith() {
let a = Ball::new(rat(1, 1), rat(2, 1));
let b = Ball::new(rat(3, 1), rat(4, 1));
assert_eq!(a.add(&b), Ball::new(rat(4, 1), rat(6, 1)));
assert_eq!(a.mul(&b), Ball::new(rat(3, 1), rat(8, 1)));
assert!(a.recip().is_some());
assert!(Ball::new(rat(-1, 1), rat(1, 1)).recip().is_none());
}
#[test]
fn to_float() {
assert!((Ball::new(rat(1, 3), rat(1, 3)).to_f64() - (1.0 / 3.0)).abs() < 1e-15);
}
}