use num_bigint::{BigInt, Sign as BigSign};
use axiolid_guarantees::Sign;
use crate::arith::Arith;
use crate::interval::Interval;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Dyadic {
mantissa: BigInt,
exponent: i64,
}
impl Dyadic {
#[must_use]
pub fn zero() -> Self {
Self {
mantissa: BigInt::from(0),
exponent: 0,
}
}
#[must_use]
pub fn from_parts(mantissa: BigInt, exponent: i64) -> Self {
Self { mantissa, exponent }.normalised()
}
#[must_use]
pub fn try_from_f64(value: f64) -> Option<Self> {
if !value.is_finite() {
return None;
}
let bits = value.to_bits();
let negative = bits >> 63 == 1;
let biased = ((bits >> 52) & 0x7ff) as i64;
let fraction = bits & ((1u64 << 52) - 1);
let (magnitude, exponent) = if biased == 0 {
(fraction, -1074)
} else {
(fraction | (1u64 << 52), biased - 1075)
};
let mut mantissa = BigInt::from(magnitude);
if negative {
mantissa = -mantissa;
}
Some(Self::from_parts(mantissa, exponent))
}
#[must_use]
pub fn mantissa(&self) -> &BigInt {
&self.mantissa
}
#[must_use]
pub fn exponent(&self) -> i64 {
self.exponent
}
#[must_use]
pub fn enclosure(&self) -> Interval {
if self.mantissa.sign() == BigSign::NoSign {
return Interval::point(0.0);
}
let bits = self.mantissa.bits();
let drop = bits.saturating_sub(64);
let exponent = self.exponent + drop as i64;
if !(-1000..=900).contains(&exponent) {
return Interval::WHOLE;
}
let scale = 2f64.powi(exponent as i32);
if drop == 0 && bits <= 53 {
let exact = i64::try_from(&self.mantissa).expect("at most 53 bits") as f64 * scale;
return Interval::from_bounds(exact, exact);
}
let top = &self.mantissa >> drop;
let top = i128::try_from(&top).expect("at most 65 bits");
let low = (top as f64) * scale;
let high = if drop == 0 {
low
} else {
((top + 1) as f64) * scale
};
Interval::from_bounds(low.next_down().next_down(), high.next_up().next_up())
}
#[must_use]
pub fn approx_parts(&self) -> (f64, i64) {
let bits = self.mantissa.bits();
if bits == 0 {
return (0.0, 0);
}
let drop = bits.saturating_sub(53);
let top = &self.mantissa >> drop;
let top = i64::try_from(&top).expect("at most 53 bits");
let mut m = top as f64;
let mut e = self.exponent + drop as i64;
let lift = 53 - bits.min(53) as i64;
m *= 2f64.powi(lift as i32);
e -= lift;
(m, e)
}
#[must_use]
pub fn bits(&self) -> u64 {
self.mantissa.bits()
}
#[must_use]
pub fn to_f64(&self) -> f64 {
let bits = self.mantissa.bits();
let drop = bits.saturating_sub(64);
let top = &self.mantissa >> drop;
let top = i128::try_from(&top).expect("at most 64 bits");
let exponent = self.exponent + drop as i64;
let exponent = exponent.clamp(-2000, 2000) as i32;
(top as f64) * 2f64.powi(exponent / 2) * 2f64.powi(exponent - exponent / 2)
}
fn normalised(mut self) -> Self {
match self.mantissa.trailing_zeros() {
None => Self::zero(),
Some(0) => self,
Some(shift) => {
self.mantissa >>= shift;
self.exponent += shift as i64;
self
}
}
}
fn aligned(&self, other: &Self) -> (BigInt, BigInt, i64) {
if self.exponent <= other.exponent {
let shift = (other.exponent - self.exponent) as u64;
(
self.mantissa.clone(),
&other.mantissa << shift,
self.exponent,
)
} else {
let shift = (self.exponent - other.exponent) as u64;
(
&self.mantissa << shift,
other.mantissa.clone(),
other.exponent,
)
}
}
}
impl Arith for Dyadic {
fn from_f64(value: f64) -> Self {
Self::try_from_f64(value).expect("exact arithmetic needs finite input")
}
fn from_dyadic(value: &Dyadic) -> Self {
value.clone()
}
fn add(&self, other: &Self) -> Self {
let (left, right, exponent) = self.aligned(other);
Self::from_parts(left + right, exponent)
}
fn sub(&self, other: &Self) -> Self {
let (left, right, exponent) = self.aligned(other);
Self::from_parts(left - right, exponent)
}
fn mul(&self, other: &Self) -> Self {
Self {
mantissa: &self.mantissa * &other.mantissa,
exponent: self.exponent + other.exponent,
}
.normalised()
}
fn neg(&self) -> Self {
Self {
mantissa: -&self.mantissa,
exponent: self.exponent,
}
}
fn sign(&self) -> Option<Sign> {
Some(match self.mantissa.sign() {
BigSign::Plus => Sign::Positive,
BigSign::Minus => Sign::Negative,
BigSign::NoSign => Sign::Zero,
})
}
}