#[derive(Clone, Copy, Debug)]
pub(crate) struct DD {
hi: f64,
lo: f64,
}
const SPLIT: f64 = 134217729.0;
impl DD {
pub(crate) fn value_of_double(x: f64) -> DD {
DD::new_double(x)
}
fn new_double(x: f64) -> DD {
let mut dd = DD { hi: 0.0, lo: 0.0 };
dd.init_double(x);
dd
}
fn init_double(&mut self, x: f64) {
self.hi = x;
self.lo = 0.0;
}
fn init_double_double(&mut self, hi: f64, lo: f64) {
self.hi = hi;
self.lo = lo;
}
pub(crate) fn self_add_double(&mut self, y: f64) -> &mut DD {
let big_s = self.hi + y;
let e = big_s - self.hi;
let mut s = big_s - e;
s = (y - e) + (self.hi - s);
let f = s + self.lo;
let big_h = big_s + f;
let h = f + (big_s - big_h);
self.hi = big_h + h;
self.lo = h + (big_h - self.hi);
self
}
fn self_add_double_double(&mut self, yhi: f64, ylo: f64) -> &mut DD {
let big_s = self.hi + yhi;
let big_t = self.lo + ylo;
let mut e = big_s - self.hi;
let f = big_t - self.lo;
let mut s = big_s - e;
let mut t = big_t - f;
s = (yhi - e) + (self.hi - s);
t = (ylo - f) + (self.lo - t);
e = s + big_t;
let big_h = big_s + e;
let h = e + (big_s - big_h);
e = t + h;
let zhi = big_h + e;
let zlo = e + (big_h - zhi);
self.init_double_double(zhi, zlo);
self
}
pub(crate) fn self_subtract_dd(&mut self, y: DD) -> &mut DD {
self.self_add_double_double(-y.hi, -y.lo)
}
pub(crate) fn self_multiply_dd(&mut self, y: DD) -> &mut DD {
self.self_multiply_double_double(y.hi, y.lo)
}
fn self_multiply_double_double(&mut self, yhi: f64, ylo: f64) -> &mut DD {
let mut big_c = SPLIT * self.hi;
let mut hx = big_c - self.hi;
let mut c = SPLIT * yhi;
hx = big_c - hx;
let tx = self.hi - hx;
let mut hy = c - yhi;
big_c = self.hi * yhi;
hy = c - hy;
let ty = yhi - hy;
c = (((hx * hy - big_c) + hx * ty) + tx * hy) + tx * ty + (self.hi * ylo + self.lo * yhi);
let zhi = big_c + c;
hx = big_c - zhi;
let zlo = c + hx;
self.init_double_double(zhi, zlo);
self
}
pub(crate) fn signum(&self) -> i32 {
if self.hi > 0.0 {
return 1;
}
if self.hi < 0.0 {
return -1;
}
if self.lo > 0.0 {
return 1;
}
if self.lo < 0.0 {
return -1;
}
0
}
}
#[cfg(test)]
mod tests {
use super::DD;
fn det(ax: f64, ay: f64, bx: f64, by: f64) -> i32 {
let mut a = DD::value_of_double(ax);
let b = DD::value_of_double(by);
let mut c = DD::value_of_double(ay);
let d = DD::value_of_double(bx);
a.self_multiply_dd(b);
c.self_multiply_dd(d);
a.self_subtract_dd(c);
a.signum()
}
#[test]
fn represents_a_plain_double_exactly() {
assert_eq!(DD::value_of_double(0.0).signum(), 0);
assert_eq!(DD::value_of_double(1.5).signum(), 1);
assert_eq!(DD::value_of_double(-1.5).signum(), -1);
}
#[test]
fn keeps_the_sign_of_a_determinant_that_cancels_in_plain_f64() {
let e = f64::powi(2.0, -52);
let (ax, ay, bx, by) = (1.0 + e, 1.0, 1.0 + 2.0 * e, 1.0 + e);
assert_eq!(ax * by - ay * bx, 0.0);
assert_eq!(det(ax, ay, bx, by), 1);
}
#[test]
fn treats_a_determinant_of_four_identical_values_as_collinear() {
assert_eq!(1e17 + 1.0, 1e17);
assert_eq!(det(1e17, 1e17 + 1.0, 1e17 + 2.0, 1e17 + 3.0), 0);
}
#[test]
fn adds_without_losing_the_low_order_component() {
assert_eq!((1.0 + 1e-30) - 1.0, 0.0);
let mut dd = DD::value_of_double(1.0);
dd.self_add_double(1e-30);
dd.self_subtract_dd(DD::value_of_double(1.0));
assert_eq!(dd.signum(), 1);
}
}