use core::cmp::Ordering;
use core::fmt;
use crate::float::{Float, RoundingMode};
use crate::int::{Int, Sign};
use crate::interval::Interval;
use crate::rational::Rational;
const DOWN: RoundingMode = RoundingMode::TowardNegative;
const UP: RoundingMode = RoundingMode::TowardPositive;
const NEAR: RoundingMode = RoundingMode::Nearest;
const RAD_PREC: u64 = 30;
#[derive(Clone)]
pub struct Ball {
mid: Float,
rad: Float, }
fn rad_zero() -> Float {
Float::from_int(&Int::ZERO, RAD_PREC, NEAR)
}
fn round_err(mid: &Float, ord: Ordering) -> Float {
if ord == Ordering::Equal {
rad_zero()
} else {
half_ulp(mid)
}
}
fn half_ulp(f: &Float) -> Float {
if f.is_zero() {
return rad_zero();
}
match f.exponent() {
Some(e) => i32::try_from(e - 1)
.map(|k| Float::from_rational(&Rational::power_of_two(k), RAD_PREC, UP))
.unwrap_or_else(|_| Float::infinity(RAD_PREC)),
None => Float::infinity(RAD_PREC), }
}
impl Ball {
pub fn new(mid: Float, rad: Float) -> Ball {
let rad = rad.abs().round(RAD_PREC, UP);
Ball { mid, rad }
}
pub fn point(x: Float) -> Ball {
Ball {
mid: x,
rad: rad_zero(),
}
}
pub fn from_int(n: &Int, precision: u64) -> Ball {
Ball {
mid: Float::from_int(n, precision, NEAR),
rad: rad_zero(),
}
}
pub fn from_rational(r: &Rational, precision: u64) -> Ball {
let mid = Float::from_rational(r, precision, NEAR);
let rad = match mid.to_rational() {
Some(m) if m == *r => rad_zero(), _ => half_ulp(&mid),
};
Ball { mid, rad }
}
pub fn from_f64(x: f64, precision: u64) -> Ball {
Ball {
mid: Float::from_f64(x, precision, NEAR),
rad: rad_zero(),
}
}
#[inline]
pub fn midpoint(&self) -> &Float {
&self.mid
}
#[inline]
pub fn radius(&self) -> &Float {
&self.rad
}
#[inline]
pub fn precision(&self) -> u64 {
self.mid.precision()
}
pub fn lower(&self) -> Float {
self.mid.sub(&self.rad, self.precision(), DOWN)
}
pub fn upper(&self) -> Float {
self.mid.add(&self.rad, self.precision(), UP)
}
pub fn contains(&self, x: &Float) -> bool {
&self.lower() <= x && x <= &self.upper()
}
pub fn contains_zero(&self) -> bool {
self.lower().sign() != Sign::Positive && self.upper().sign() != Sign::Negative
}
pub fn is_finite(&self) -> bool {
self.mid.is_finite() && self.rad.is_finite()
}
fn work_precision(&self, rhs: &Ball) -> u64 {
self.precision().max(rhs.precision())
}
pub fn add(&self, rhs: &Ball) -> Ball {
let p = self.work_precision(rhs);
let (mid, ord) = self.mid.add_ternary(&rhs.mid, p, NEAR);
let rad = sum_up(&[&self.rad, &rhs.rad, &round_err(&mid, ord)]);
Ball { mid, rad }
}
pub fn sub(&self, rhs: &Ball) -> Ball {
let p = self.work_precision(rhs);
let (mid, ord) = self.mid.sub_ternary(&rhs.mid, p, NEAR);
let rad = sum_up(&[&self.rad, &rhs.rad, &round_err(&mid, ord)]);
Ball { mid, rad }
}
pub fn neg(&self) -> Ball {
Ball {
mid: self.mid.neg(),
rad: self.rad.clone(),
}
}
pub fn mul(&self, rhs: &Ball) -> Ball {
let p = self.work_precision(rhs);
let (mid, ord) = self.mid.mul_ternary(&rhs.mid, p, NEAR);
let t1 = self.mid.abs().mul(&rhs.rad, RAD_PREC, UP);
let t2 = rhs.mid.abs().mul(&self.rad, RAD_PREC, UP);
let t3 = self.rad.mul(&rhs.rad, RAD_PREC, UP);
let rad = sum_up(&[&t1, &t2, &t3, &round_err(&mid, ord)]);
Ball { mid, rad }
}
pub fn div(&self, rhs: &Ball) -> Ball {
let p = self.work_precision(rhs);
Ball::from_interval(&self.to_interval().div(&rhs.to_interval()), p)
}
pub fn sqrt(&self) -> Ball {
Ball::from_interval(&self.to_interval().sqrt(), self.precision())
}
pub fn exp(&self) -> Ball {
let p = self.precision();
let lo = self.lower().exp(p, DOWN);
let hi = self.upper().exp(p, UP);
Ball::from_interval(&Interval::new(lo, hi, p), p)
}
pub fn ln(&self) -> Ball {
let p = self.precision();
if self.lower().sign() != Sign::Positive {
return Ball::new(Float::nan(p), Float::infinity(RAD_PREC));
}
let lo = self.lower().ln(p, DOWN);
let hi = self.upper().ln(p, UP);
Ball::from_interval(&Interval::new(lo, hi, p), p)
}
pub fn to_interval(&self) -> Interval {
Interval::new(self.lower(), self.upper(), self.precision())
}
pub fn from_interval(iv: &Interval, precision: u64) -> Ball {
let mid = iv.midpoint().round(precision, NEAR);
let lo_gap = mid.sub(iv.lower(), RAD_PREC, UP);
let hi_gap = iv.upper().sub(&mid, RAD_PREC, UP);
let rad = if lo_gap >= hi_gap { lo_gap } else { hi_gap };
Ball {
mid,
rad: rad.abs().round(RAD_PREC, UP),
}
}
}
fn sum_up(terms: &[&Float]) -> Float {
let mut acc = rad_zero();
for t in terms {
acc = acc.add(t, RAD_PREC, UP);
}
acc
}
impl fmt::Display for Ball {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ± {}", self.mid, self.rad)
}
}
impl fmt::Debug for Ball {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Ball({} ± {})", self.mid, self.rad)
}
}
macro_rules! ball_binop {
($tr:ident, $m:ident, $atr:ident, $am:ident) => {
impl core::ops::$tr<Ball> for Ball {
type Output = Ball;
#[inline]
fn $m(self, rhs: Ball) -> Ball {
Ball::$m(&self, &rhs)
}
}
impl core::ops::$tr<&Ball> for Ball {
type Output = Ball;
#[inline]
fn $m(self, rhs: &Ball) -> Ball {
Ball::$m(&self, rhs)
}
}
impl core::ops::$tr<Ball> for &Ball {
type Output = Ball;
#[inline]
fn $m(self, rhs: Ball) -> Ball {
Ball::$m(self, &rhs)
}
}
impl core::ops::$tr<&Ball> for &Ball {
type Output = Ball;
#[inline]
fn $m(self, rhs: &Ball) -> Ball {
Ball::$m(self, rhs)
}
}
impl core::ops::$atr<Ball> for Ball {
#[inline]
fn $am(&mut self, rhs: Ball) {
*self = Ball::$m(self, &rhs);
}
}
impl core::ops::$atr<&Ball> for Ball {
#[inline]
fn $am(&mut self, rhs: &Ball) {
*self = Ball::$m(self, rhs);
}
}
};
}
ball_binop!(Add, add, AddAssign, add_assign);
ball_binop!(Sub, sub, SubAssign, sub_assign);
ball_binop!(Mul, mul, MulAssign, mul_assign);
ball_binop!(Div, div, DivAssign, div_assign);
impl core::ops::Neg for Ball {
type Output = Ball;
#[inline]
fn neg(self) -> Ball {
Ball::neg(&self)
}
}
impl core::ops::Neg for &Ball {
type Output = Ball;
#[inline]
fn neg(self) -> Ball {
Ball::neg(self)
}
}