use rug::float::Round;
use rug::{Float, Rational};
use super::error::ThetaError;
#[derive(Clone, Debug)]
pub struct RealBall {
mid: Float,
rad: Float,
prec: u32,
}
impl RealBall {
pub fn exact_f64(v: f64, prec: u32) -> Self {
let p = prec.max(53);
RealBall {
mid: Float::with_val(p, v),
rad: Float::with_val(p, 0.0),
prec,
}
}
pub fn exact_i64(v: i64, prec: u32) -> Self {
let p = prec.max(64);
RealBall {
mid: Float::with_val(p, v),
rad: Float::with_val(p, 0.0),
prec,
}
}
pub fn from_mid_rad(mid: Float, rad: Float, prec: u32) -> Self {
let mut rad = rad;
rad.abs_mut();
RealBall { mid, rad, prec }
}
pub fn from_rational(r: &Rational, prec: u32) -> Self {
let p = prec.max(2);
let (mid, ord) = Float::with_val_round(p, r, Round::Nearest);
let rad = if ord == std::cmp::Ordering::Equal {
Float::with_val(p, 0.0)
} else {
match mid.get_exp() {
Some(e) => {
let mut u = Float::with_val(p, 1.0);
u <<= e - p as i32;
u
}
None => Float::with_val(p, 0.0),
}
};
RealBall { mid, rad, prec }
}
pub fn indeterminate(prec: u32) -> Self {
RealBall {
mid: Float::with_val(prec.max(53), 0.0),
rad: Float::with_val(prec.max(53), f64::INFINITY),
prec,
}
}
pub fn midpoint(&self) -> &Float {
&self.mid
}
pub fn radius(&self) -> &Float {
&self.rad
}
pub fn precision(&self) -> u32 {
self.prec
}
pub fn midpoint_f64(&self) -> f64 {
self.mid.to_f64()
}
pub fn radius_f64(&self) -> f64 {
if !self.rad.is_finite() {
return f64::INFINITY;
}
let mid_d = self.mid.to_f64();
if !mid_d.is_finite() {
return f64::INFINITY;
}
let p = self.mid.prec().max(64) + 64;
let drift = (Float::with_val(p, &self.mid) - Float::with_val(p, mid_d)).abs();
let (total, _) = Float::with_val_round(p, &self.rad + &drift, Round::Up);
let up = total.to_f64_round(Round::Up);
if up.is_finite() && up >= 0.0 {
up
} else {
f64::INFINITY
}
}
pub fn is_indeterminate(&self) -> bool {
!self.rad.is_finite() || !self.mid.is_finite()
}
pub fn is_exact(&self) -> bool {
self.rad.is_zero()
}
pub fn accuracy_bits(&self) -> i64 {
if self.is_indeterminate() {
return i64::MIN;
}
if self.rad.is_zero() {
return i64::MAX;
}
match (self.mid.get_exp(), self.rad.get_exp()) {
(Some(me), Some(re)) => i64::from(me) - i64::from(re) - 1,
_ => i64::MIN,
}
}
fn inner_endpoints(&self) -> Option<(Float, Float)> {
if !self.mid.is_finite() || !self.rad.is_finite() {
return None;
}
let p = self.mid.prec().max(self.rad.prec()).max(64) + 64;
let (lo, _) = Float::with_val_round(p, &self.mid - &self.rad, Round::Up);
let (hi, _) = Float::with_val_round(p, &self.mid + &self.rad, Round::Down);
Some((lo, hi))
}
pub fn contains_f64(&self, v: f64) -> bool {
if !self.rad.is_finite() {
return true;
}
if !self.mid.is_finite() {
return false;
}
match self.inner_endpoints() {
Some((lo, hi)) => lo <= v && v <= hi,
None => false,
}
}
pub fn overlaps(&self, other: &RealBall) -> bool {
if !self.rad.is_finite() || !other.rad.is_finite() {
return true;
}
let (Some((a_lo, a_hi)), Some((b_lo, b_hi))) =
(self.inner_endpoints(), other.inner_endpoints())
else {
return false;
};
a_lo <= b_hi && b_lo <= a_hi
}
pub fn contains_zero(&self) -> bool {
self.contains_f64(0.0)
}
pub fn interval_f64(&self) -> (f64, f64) {
let m = self.midpoint_f64();
let r = self.radius_f64();
if !m.is_finite() || !r.is_finite() {
return (f64::NEG_INFINITY, f64::INFINITY);
}
let (lo, _) = Float::with_val_round(64, Float::with_val(64, m) - r, Round::Down);
let (hi, _) = Float::with_val_round(64, Float::with_val(64, m) + r, Round::Up);
(lo.to_f64_round(Round::Down), hi.to_f64_round(Round::Up))
}
pub fn to_decimal_string(&self, digits: usize) -> String {
if !self.mid.is_finite() {
return format!("{}", self.mid);
}
format!("{:.*}", digits.max(1), self.mid)
}
}
impl std::fmt::Display for RealBall {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"[{} +/- {:.3e}]",
self.to_decimal_string(17),
self.radius_f64()
)
}
}
#[derive(Clone, Debug)]
pub struct ComplexBall {
pub(crate) re: RealBall,
pub(crate) im: RealBall,
}
impl ComplexBall {
pub fn exact_f64(re: f64, im: f64, prec: u32) -> Self {
ComplexBall {
re: RealBall::exact_f64(re, prec),
im: RealBall::exact_f64(im, prec),
}
}
pub fn exact_i64(re: i64, im: i64, prec: u32) -> Self {
ComplexBall {
re: RealBall::exact_i64(re, prec),
im: RealBall::exact_i64(im, prec),
}
}
pub fn from_rationals(re: &Rational, im: &Rational, prec: u32) -> Self {
ComplexBall {
re: RealBall::from_rational(re, prec),
im: RealBall::from_rational(im, prec),
}
}
pub fn from_parts(re: RealBall, im: RealBall) -> Self {
ComplexBall { re, im }
}
pub fn indeterminate(prec: u32) -> Self {
ComplexBall {
re: RealBall::indeterminate(prec),
im: RealBall::indeterminate(prec),
}
}
pub fn real(&self) -> &RealBall {
&self.re
}
pub fn imag(&self) -> &RealBall {
&self.im
}
pub fn midpoint_re(&self) -> &Float {
self.re.midpoint()
}
pub fn midpoint_im(&self) -> &Float {
self.im.midpoint()
}
pub fn radius_re(&self) -> &Float {
self.re.radius()
}
pub fn radius_im(&self) -> &Float {
self.im.radius()
}
pub fn precision(&self) -> u32 {
self.re.precision()
}
pub fn accuracy_bits(&self) -> i64 {
if self.is_indeterminate() {
return i64::MIN;
}
if self.re.is_exact() && self.im.is_exact() {
return i64::MAX;
}
let mid_exp = [self.re.midpoint().get_exp(), self.im.midpoint().get_exp()]
.into_iter()
.flatten()
.max();
let rad_exp = [self.re.radius().get_exp(), self.im.radius().get_exp()]
.into_iter()
.flatten()
.max();
match (mid_exp, rad_exp) {
(Some(me), Some(re)) => i64::from(me) - i64::from(re) - 1,
(Some(_), None) => i64::MAX,
_ => i64::MIN,
}
}
pub fn is_indeterminate(&self) -> bool {
self.re.is_indeterminate() || self.im.is_indeterminate()
}
pub fn is_exact(&self) -> bool {
self.re.is_exact() && self.im.is_exact()
}
pub fn contains_f64(&self, re: f64, im: f64) -> bool {
self.re.contains_f64(re) && self.im.contains_f64(im)
}
pub fn contains_zero(&self) -> bool {
self.re.contains_zero() && self.im.contains_zero()
}
pub fn overlaps(&self, other: &ComplexBall) -> bool {
self.re.overlaps(&other.re) && self.im.overlaps(&other.im)
}
pub fn value_if_accurate(
&self,
function: &'static str,
min_accuracy_bits: u32,
) -> Result<(f64, f64), ThetaError> {
if self.is_indeterminate() {
return Err(ThetaError::Indeterminate { function });
}
let acc = self.accuracy_bits();
if acc < i64::from(min_accuracy_bits) {
return Err(ThetaError::InsufficientAccuracy {
function,
requested_bits: min_accuracy_bits,
achieved_bits: Some(acc),
at_precision: self.precision(),
});
}
Ok((self.re.midpoint_f64(), self.im.midpoint_f64()))
}
}
impl std::fmt::Display for ComplexBall {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} + {}*I", self.re, self.im)
}
}