use core::cmp::Ordering;
use core::ops::{
Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign,
};
mod consts;
mod elementary;
mod parse;
mod rem;
mod traits;
mod trig;
pub use parse::ParseDoubleDoubleError;
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct DoubleDouble {
hi: f64,
lo: f64,
}
#[inline]
pub(crate) fn two_sum(a: f64, b: f64) -> (f64, f64) {
let s = a + b;
let bb = s - a;
let e = (a - (s - bb)) + (b - bb);
(s, e)
}
#[inline]
pub(crate) fn two_sum_safe(a: f64, b: f64) -> (f64, f64) {
let (s, e) = two_sum(a, b);
if !e.is_finite() && s.is_finite() {
return two_sum_ordered(a, b);
}
(s, e)
}
#[cold]
#[inline(never)]
fn two_sum_ordered(a: f64, b: f64) -> (f64, f64) {
if a.abs() >= b.abs() {
fast_two_sum(a, b)
} else {
fast_two_sum(b, a)
}
}
#[inline]
pub(crate) fn fast_two_sum(a: f64, b: f64) -> (f64, f64) {
let s = a + b;
let e = b - (s - a);
(s, e)
}
#[inline]
pub(crate) fn two_prod(a: f64, b: f64) -> (f64, f64) {
let p = a * b;
let e = a.mul_add(b, -p);
(p, e)
}
const DIV_RESCALE_ABOVE: f64 = f64::from_bits((960 + 1023) << 52);
const RENORMALIZE_BELOW: f64 = f64::from_bits((1023 - 968) << 52);
#[inline]
fn pow2(e: i32) -> f64 {
debug_assert!((-1022..=1023).contains(&e));
f64::from_bits(((e + 1023) as u64) << 52)
}
const TWO_POW_128: f64 = f64::from_bits((1023 + 128) << 52);
pub(crate) fn from_u128(n: u128) -> DoubleDouble {
if n >> 53 == 0 {
return DoubleDouble::from_f64(n as u64 as f64);
}
let hi = n as f64; let residual = if hi == TWO_POW_128 {
-((u128::MAX - n + 1) as f64) } else {
let h = hi as u128; if n >= h {
(n - h) as f64
} else {
-((h - n) as f64)
}
};
DoubleDouble::new(hi, residual)
}
pub(crate) fn scale_down_tiny(v: DoubleDouble, s: i32) -> DoubleDouble {
if v.hi.abs() < pow2(s - 1022) {
return DoubleDouble::from_f64(round_to_subnormal(v, s));
}
let hi = v.hi * pow2(-s);
let mut lo = v.lo * pow2(-s);
if hi + lo != hi {
lo -= f64::from_bits(1).copysign(lo);
}
DoubleDouble::from_parts_unchecked(hi, if lo == 0.0 { 0.0 } else { lo })
}
fn round_to_subnormal(v: DoubleDouble, s: i32) -> f64 {
const STEP: f64 = f64::from_bits(1); if v.hi.is_sign_negative() {
return -round_to_subnormal(-v, s);
}
let q = v.hi * pow2(-s);
let d = v.hi - q * pow2(s);
let (r, r_lo) = two_sum(d, v.lo); let half = pow2(s - 1075);
let above = r > half || (r == half && r_lo > 0.0);
let below = r < -half || (r == -half && r_lo < 0.0);
let tie = r.abs() == half && r_lo == 0.0 && q.to_bits() & 1 == 1;
if above || (tie && r > 0.0) {
q + STEP
} else if below || (tie && r < 0.0) {
q - STEP
} else {
q
}
}
impl DoubleDouble {
pub const ZERO: Self = Self { hi: 0.0, lo: 0.0 };
pub const ONE: Self = Self { hi: 1.0, lo: 0.0 };
pub const NAN: Self = Self {
hi: f64::NAN,
lo: 0.0,
};
pub const INFINITY: Self = Self {
hi: f64::INFINITY,
lo: 0.0,
};
pub const NEG_INFINITY: Self = Self {
hi: f64::NEG_INFINITY,
lo: 0.0,
};
#[inline]
pub const fn from_f64(x: f64) -> Self {
Self { hi: x, lo: 0.0 }
}
#[inline]
pub fn new(hi: f64, lo: f64) -> Self {
let (s, e) = two_sum_safe(hi, lo);
if !s.is_finite() {
return Self { hi: s, lo: 0.0 };
}
Self { hi: s, lo: e }
}
#[inline]
pub(crate) const fn from_parts_unchecked(hi: f64, lo: f64) -> Self {
Self { hi, lo }
}
#[inline]
pub const fn hi(self) -> f64 {
self.hi
}
#[inline]
pub const fn lo(self) -> f64 {
self.lo
}
#[inline]
fn renormalize(hi: f64, lo: f64) -> Self {
let (h, l) = fast_two_sum(hi, lo);
if !h.is_finite() {
return Self { hi: h, lo: 0.0 };
}
Self { hi: h, lo: l }
}
#[inline]
fn zero_signed_as(self, lead: f64) -> Self {
if self.hi == 0.0 {
Self {
hi: 0.0_f64.copysign(lead),
lo: 0.0,
}
} else {
self
}
}
#[inline]
pub fn add_f64(self, y: f64) -> Self {
let (mut sh, mut sl) = two_sum(self.hi, y);
if !sl.is_finite() {
if !sh.is_finite() {
return Self { hi: sh, lo: 0.0 };
}
(sh, sl) = two_sum_ordered(self.hi, y); }
Self::renormalize(sh, self.lo + sl).zero_signed_as(sh)
}
#[inline]
pub fn add_dd(self, y: Self) -> Self {
let (mut sh, mut sl) = two_sum(self.hi, y.hi);
if !sl.is_finite() {
if !sh.is_finite() {
return Self { hi: sh, lo: 0.0 };
}
(sh, sl) = two_sum_ordered(self.hi, y.hi); }
let (th, tl) = two_sum(self.lo, y.lo);
let c = sl + th;
let (vh, vl) = fast_two_sum(sh, c);
if !vh.is_finite() {
return Self { hi: vh, lo: 0.0 };
}
let w = tl + vl;
Self::renormalize(vh, w).zero_signed_as(sh)
}
#[inline]
pub fn mul_f64(self, y: f64) -> Self {
let (ch, cl1) = two_prod(self.hi, y);
if !ch.is_finite() {
return Self { hi: ch, lo: 0.0 };
}
let cl3 = self.lo.mul_add(y, cl1);
Self::renormalize(ch, cl3).zero_signed_as(ch)
}
#[inline]
pub fn mul_dd(self, y: Self) -> Self {
let (ch, cl1) = two_prod(self.hi, y.hi);
if !ch.is_finite() {
return Self { hi: ch, lo: 0.0 };
}
let tl0 = self.lo * y.lo;
let tl1 = self.hi.mul_add(y.lo, tl0);
let cl2 = self.lo.mul_add(y.hi, tl1);
let cl3 = cl1 + cl2;
Self::renormalize(ch, cl3).zero_signed_as(ch)
}
#[inline]
pub fn div_f64(self, y: f64) -> Self {
let th = self.hi / y;
if !th.is_finite() || !y.is_finite() {
return Self { hi: th, lo: 0.0 };
}
let (ph, pl) = two_prod(th, y);
if !ph.is_finite() {
return self.mul_pow2(0.5).div_f64(y).mul_pow2(2.0);
}
let dh = self.hi - ph;
let dt = dh - pl;
let d = dt + self.lo;
let tl = d / y;
Self::renormalize(th, tl).zero_signed_as(th)
}
#[inline]
pub fn div_dd(self, y: Self) -> Self {
let q = self.hi / y.hi;
if !q.is_finite() || !y.hi.is_finite() {
return Self { hi: q, lo: 0.0 };
}
let th = 1.0 / y.hi;
if !th.is_finite() {
return self.mul_pow2(pow2(106)).div_dd(y.mul_pow2(pow2(106)));
}
if y.hi.abs() > DIV_RESCALE_ABOVE {
return self.mul_pow2(pow2(-106)).div_dd(y.mul_pow2(pow2(-106)));
}
let rh = (-y.hi).mul_add(th, 1.0);
let rl = -y.lo * th;
let (eh, el) = fast_two_sum(rh, rl);
let m = Self { hi: eh, lo: el }.mul_f64(th).add_f64(th);
let z = self.mul_dd(m);
if z.hi.is_infinite() {
return self.mul_pow2(0.5).div_dd(y).mul_pow2(2.0);
}
z
}
#[inline]
pub fn mul_pow2(self, p: f64) -> Self {
if p.to_bits() & ((1 << 52) - 1) != 0 {
return self.mul_f64(p);
}
let hi = self.hi * p;
if !hi.is_finite() {
return Self { hi, lo: 0.0 };
}
let lo = self.lo * p;
if p < 1.0 && hi.abs() < RENORMALIZE_BELOW {
return Self::renormalize_tiny(hi, lo);
}
Self { hi, lo }
}
#[cold]
#[inline(never)]
fn renormalize_tiny(hi: f64, lo: f64) -> Self {
if hi == 0.0 {
return Self { hi, lo: 0.0 };
}
let (h, l) = fast_two_sum(hi, lo);
Self { hi: h, lo: l }
}
pub fn ldexp(self, e: i32) -> Self {
let mut x = self;
let mut e = e.clamp(-2200, 2200);
while e > 1000 {
x = x.mul_pow2(pow2(1000));
e -= 1000;
}
while e < -1000 {
x = x.mul_pow2(pow2(-1000));
e += 1000;
}
x.mul_pow2(pow2(e))
}
#[inline]
pub fn recip(self) -> Self {
Self::ONE.div_dd(self)
}
#[inline]
pub fn abs(self) -> Self {
if self.hi.is_sign_negative() {
-self
} else {
self
}
}
pub fn floor(self) -> Self {
if !self.hi.is_finite() {
return self;
}
let f = self.hi.floor();
if f != self.hi {
return Self { hi: f, lo: 0.0 };
}
if self.lo == 0.0 {
return Self { hi: f, lo: 0.0 }; }
let (h, l) = two_sum(f, self.lo.floor()); Self { hi: h, lo: l }
}
pub fn ceil(self) -> Self {
-(-self).floor()
}
pub fn trunc(self) -> Self {
if self.hi.is_sign_negative() {
self.ceil()
} else {
self.floor()
}
}
pub fn round(self) -> Self {
let t = self.trunc();
if (self - t).abs() >= Self::from_f64(0.5) {
if self.hi.is_sign_negative() {
t - 1.0
} else {
t + 1.0
}
} else {
t
}
}
}
impl Neg for DoubleDouble {
type Output = Self;
#[inline]
fn neg(self) -> Self {
Self {
hi: -self.hi,
lo: 0.0 - self.lo, }
}
}
impl Add for DoubleDouble {
type Output = Self;
#[inline]
fn add(self, y: Self) -> Self {
self.add_dd(y)
}
}
impl Sub for DoubleDouble {
type Output = Self;
#[inline]
fn sub(self, y: Self) -> Self {
self.add_dd(-y)
}
}
impl Mul for DoubleDouble {
type Output = Self;
#[inline]
fn mul(self, y: Self) -> Self {
self.mul_dd(y)
}
}
impl Div for DoubleDouble {
type Output = Self;
#[inline]
fn div(self, y: Self) -> Self {
self.div_dd(y)
}
}
impl Rem for DoubleDouble {
type Output = Self;
fn rem(self, y: Self) -> Self {
rem::rem(self, y)
}
}
impl Add<f64> for DoubleDouble {
type Output = Self;
#[inline]
fn add(self, y: f64) -> Self {
self.add_f64(y)
}
}
impl Sub<f64> for DoubleDouble {
type Output = Self;
#[inline]
fn sub(self, y: f64) -> Self {
self.add_f64(-y)
}
}
impl Mul<f64> for DoubleDouble {
type Output = Self;
#[inline]
fn mul(self, y: f64) -> Self {
self.mul_f64(y)
}
}
impl Div<f64> for DoubleDouble {
type Output = Self;
#[inline]
fn div(self, y: f64) -> Self {
self.div_f64(y)
}
}
impl Add<DoubleDouble> for f64 {
type Output = DoubleDouble;
#[inline]
fn add(self, y: DoubleDouble) -> DoubleDouble {
y.add_f64(self)
}
}
impl Sub<DoubleDouble> for f64 {
type Output = DoubleDouble;
#[inline]
fn sub(self, y: DoubleDouble) -> DoubleDouble {
(-y).add_f64(self)
}
}
impl Mul<DoubleDouble> for f64 {
type Output = DoubleDouble;
#[inline]
fn mul(self, y: DoubleDouble) -> DoubleDouble {
y.mul_f64(self)
}
}
impl Div<DoubleDouble> for f64 {
type Output = DoubleDouble;
#[inline]
fn div(self, y: DoubleDouble) -> DoubleDouble {
DoubleDouble::from_f64(self).div_dd(y)
}
}
macro_rules! assign_ops {
($($trait:ident, $method:ident, $op:tt);* $(;)?) => {$(
impl $trait for DoubleDouble {
#[inline]
fn $method(&mut self, y: Self) {
*self = *self $op y;
}
}
impl $trait<f64> for DoubleDouble {
#[inline]
fn $method(&mut self, y: f64) {
*self = *self $op y;
}
}
)*};
}
assign_ops!(
AddAssign, add_assign, +;
SubAssign, sub_assign, -;
MulAssign, mul_assign, *;
DivAssign, div_assign, /;
);
impl RemAssign for DoubleDouble {
#[inline]
fn rem_assign(&mut self, y: Self) {
*self = *self % y;
}
}
impl From<f64> for DoubleDouble {
#[inline]
fn from(x: f64) -> Self {
Self::from_f64(x)
}
}
impl PartialOrd for DoubleDouble {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match self.hi.partial_cmp(&other.hi)? {
Ordering::Equal => self.lo.partial_cmp(&other.lo),
ord => Some(ord),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn subnormal_rounding_uses_the_trailing_word() {
let s = 128;
let g = pow2(s - 1074); let v = DoubleDouble::from_f64(2.5 * g);
assert_eq!(round_to_subnormal(v, s), f64::from_bits(2)); assert_eq!(
round_to_subnormal(DoubleDouble::new(2.5 * g, 1e-310), s),
f64::from_bits(3)
);
assert_eq!(
round_to_subnormal(DoubleDouble::new(3.5 * g, -1e-310), s),
f64::from_bits(3)
);
assert_eq!(
round_to_subnormal(DoubleDouble::from_f64(3.5 * g), s),
f64::from_bits(4)
);
assert_eq!(
round_to_subnormal(DoubleDouble::new(0.5 * g, 1e-310), s),
f64::from_bits(1)
);
assert_eq!(round_to_subnormal(DoubleDouble::from_f64(0.5 * g), s), 0.0);
assert_eq!(round_to_subnormal(DoubleDouble::ZERO, s).to_bits(), 0);
assert_eq!(
round_to_subnormal(-DoubleDouble::ZERO, s).to_bits(),
(-0.0f64).to_bits()
);
assert_eq!(
round_to_subnormal(DoubleDouble::new(-2.5 * g, -1e-310), s),
-f64::from_bits(3)
);
assert_eq!(
round_to_subnormal(DoubleDouble::from_f64(-0.25 * g), s).to_bits(),
(-0.0f64).to_bits()
);
}
#[test]
fn from_u128_is_within_u2_next_to_ties() {
let n: u128 = (1 << 126) + (1 << 73) + 1;
let x = from_u128(n);
assert_eq!((x.hi, x.lo), (pow2(126), pow2(73)));
assert_ne!(x.hi, n as f64); let below = from_u128((1 << 126) + (1 << 73) - 1);
assert_eq!((below.hi, below.lo), (pow2(126), pow2(73)));
assert_eq!(from_u128(u128::MAX).hi, pow2(127) * 2.0);
assert_eq!(from_u128((1 << 106) - 1).lo, -1.0);
}
}