1use std::fmt;
13use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
14
15use num_traits::Zero;
16
17use crate::domain::Domain;
18
19#[derive(Debug, Clone, Copy, PartialEq)]
27pub struct DoubleF64 {
28 pub hi: f64,
30 pub lo: f64,
32}
33
34impl Eq for DoubleF64 {}
35
36impl DoubleF64 {
41 pub const ZERO: Self = Self { hi: 0.0, lo: 0.0 };
43 pub const ONE: Self = Self { hi: 1.0, lo: 0.0 };
45
46 #[inline]
49 pub fn new(hi: f64, lo: f64) -> Self {
50 Self { hi, lo }
51 }
52
53 #[inline]
55 pub fn from_f64(x: f64) -> Self {
56 Self { hi: x, lo: 0.0 }
57 }
58
59 #[inline]
61 pub fn to_f64(self) -> f64 {
62 self.hi
63 }
64
65 pub fn quick_two_sum(a: f64, b: f64) -> Self {
67 let s = a + b;
68 let e = b - (s - a);
69 Self { hi: s, lo: e }
70 }
71
72 pub fn two_sum(a: f64, b: f64) -> Self {
74 let s = a + b;
75 let a_prime = s - b;
76 let b_prime = s - a_prime;
77 let delta_a = a - a_prime;
78 let delta_b = b - b_prime;
79 let e = delta_a + delta_b;
80 Self { hi: s, lo: e }
81 }
82
83 #[allow(dead_code)]
85 fn split(x: f64) -> (f64, f64) {
86 const SPLITTER: f64 = 134217729.0; let c = SPLITTER * x;
88 let c_hi = c - (c - x);
89 let c_lo = x - c_hi;
90 (c_hi, c_lo)
91 }
92
93 #[allow(dead_code)]
95 fn two_prod_no_fma(a: f64, b: f64) -> Self {
96 let p = a * b;
97 let (a_hi, a_lo) = Self::split(a);
98 let (b_hi, b_lo) = Self::split(b);
99 let err = ((a_hi * b_hi - p) + a_hi * b_lo + a_lo * b_hi) + a_lo * b_lo;
100 Self { hi: p, lo: err }
101 }
102
103 #[inline]
105 fn two_prod(a: f64, b: f64) -> Self {
106 let p = a * b;
107 let err = f64::mul_add(a, b, -p);
108 Self { hi: p, lo: err }
109 }
110
111 #[inline]
113 pub fn abs(self) -> Self {
114 if self.hi < 0.0 {
115 Self {
116 hi: -self.hi,
117 lo: -self.lo,
118 }
119 } else {
120 self
121 }
122 }
123
124 #[inline]
126 pub fn is_nan(self) -> bool {
127 self.hi.is_nan()
128 }
129
130 #[inline]
132 pub fn is_infinite(self) -> bool {
133 self.hi.is_infinite()
134 }
135
136 #[inline]
138 pub fn is_finite(self) -> bool {
139 self.hi.is_finite()
140 }
141
142 #[allow(clippy::should_implement_trait)]
148 pub fn add(self, other: Self) -> Self {
149 let s = Self::two_sum(self.hi, other.hi);
150 let v = self.lo + other.lo;
151 let w = s.lo + v;
152 Self::quick_two_sum(s.hi, w)
153 }
154
155 #[allow(clippy::should_implement_trait)]
157 pub fn sub(self, other: Self) -> Self {
158 self.add(-other)
159 }
160
161 #[allow(clippy::should_implement_trait)]
163 pub fn mul(self, other: Self) -> Self {
164 let p = Self::two_prod(self.hi, other.hi);
165 let err = self.hi * other.lo + self.lo * other.hi;
166 Self::quick_two_sum(p.hi, p.lo + err)
167 }
168
169 #[allow(clippy::should_implement_trait)]
171 pub fn div(self, other: Self) -> Self {
172 let q1 = self.hi / other.hi;
173 let p = Self::two_prod(q1, other.hi);
174 let delta = self.hi - p.hi;
175 let err = (delta - p.lo + self.lo) / other.hi;
176 Self::quick_two_sum(q1, err)
177 }
178
179 pub fn powi(self, mut n: i64) -> Self {
181 if n == 0 {
182 return Self::ONE;
183 }
184 let negate = n < 0;
185 if negate {
186 n = -n;
187 }
188 let mut base = self;
189 let mut result = Self::ONE;
190 let mut exp = n as u64;
191 while exp > 0 {
192 if exp & 1 == 1 {
193 result = result.mul(base);
194 }
195 base = base.mul(base);
196 exp >>= 1;
197 }
198 if negate {
199 Self::ONE.div(result)
200 } else {
201 result
202 }
203 }
204
205 pub fn sqrt(self) -> Self {
211 if self.hi < 0.0 {
212 return Self::from_f64(f64::NAN);
213 }
214 if self.hi == 0.0 {
215 return Self::ZERO;
216 }
217 let x0 = self.hi.sqrt();
219 let mut x = Self::from_f64(x0);
220 for _ in 0..4 {
223 x = x.add(self.div(x)).mul(Self::from_f64(0.5));
224 }
225 x
226 }
227
228 pub fn dabs(self) -> Self {
230 if self.hi < 0.0 { -self } else { self }
231 }
232
233 #[allow(clippy::approx_constant)]
235 pub fn exp(self) -> Self {
236 if self.hi == 0.0 && self.lo == 0.0 {
237 return Self::ONE;
238 }
239 const LN2: DoubleF64 = DoubleF64 {
241 hi: std::f64::consts::LN_2,
242 lo: 2.319046813846299e-17,
243 };
244 let k = (self.hi / LN2.hi).round() as i64;
245 let reduced = self.sub(LN2.mul(Self::from_f64(k as f64)));
246
247 let one = Self::ONE;
249 let mut term = one;
250 let mut sum = one;
251 for i in 1..40 {
252 term = term.mul(reduced).div(Self::from_f64(i as f64));
253 sum = sum.add(term);
254 }
255
256 if k >= 0 {
258 sum.mul(Self::from_f64((1u64 << k.min(62) as u64) as f64))
259 } else {
260 sum.div(Self::from_f64((1u64 << (-k).min(62) as u64) as f64))
261 }
262 }
263
264 pub fn ln(self) -> Self {
266 if self.hi <= 0.0 {
267 return Self::from_f64(f64::NAN);
268 }
269 if self.hi == 1.0 && self.lo == 0.0 {
270 return Self::ZERO;
271 }
272 let x0 = self.hi.ln();
274 let mut x = Self::from_f64(x0);
275 for _ in 0..6 {
278 let ex = x.exp();
279 x = x.add(self.sub(ex).div(ex));
280 }
281 x
282 }
283
284 #[allow(clippy::approx_constant)]
286 pub fn sin(self) -> Self {
287 const PI: DoubleF64 = DoubleF64 {
288 hi: std::f64::consts::PI,
289 lo: 1.2246467991473532e-16,
290 };
291 const TWO_PI: DoubleF64 = DoubleF64 {
292 hi: std::f64::consts::TAU,
293 lo: 2.4492935982947064e-16,
294 };
295
296 let mut x = self;
298 if x.dabs().hi > PI.hi {
299 let k = (x.hi / TWO_PI.hi).round();
300 x = x.sub(TWO_PI.mul(Self::from_f64(k)));
301 }
302
303 let x2 = x.mul(x);
305 let mut term = x;
306 let mut sum = x;
307 for i in 1..25 {
308 let n = (2 * i + 1) as f64;
309 term = term.mul(x2).div(Self::from_f64(-n * (n - 1.0)));
310 sum = sum.add(term);
311 }
312 sum
313 }
314
315 #[allow(clippy::approx_constant)]
317 pub fn cos(self) -> Self {
318 const FRAC_PI_2: DoubleF64 = DoubleF64 {
319 hi: std::f64::consts::FRAC_PI_2,
320 lo: 6.123233995736766e-17,
321 };
322 FRAC_PI_2.sub(self).sin()
323 }
324
325 pub fn tan(self) -> Self {
327 self.sin().div(self.cos())
328 }
329}
330
331impl Add for DoubleF64 {
341 type Output = Self;
342 #[inline]
343 fn add(self, other: Self) -> Self {
344 DoubleF64::add(self, other)
345 }
346}
347
348impl Sub for DoubleF64 {
349 type Output = Self;
350 #[inline]
351 fn sub(self, other: Self) -> Self {
352 DoubleF64::sub(self, other)
353 }
354}
355
356impl Mul for DoubleF64 {
357 type Output = Self;
358 #[inline]
359 fn mul(self, other: Self) -> Self {
360 DoubleF64::mul(self, other)
361 }
362}
363
364impl Div for DoubleF64 {
365 type Output = Self;
366 #[inline]
367 fn div(self, other: Self) -> Self {
368 DoubleF64::div(self, other)
369 }
370}
371
372impl Neg for DoubleF64 {
373 type Output = Self;
374 #[inline]
375 fn neg(self) -> Self {
376 Self {
377 hi: -self.hi,
378 lo: -self.lo,
379 }
380 }
381}
382
383impl AddAssign for DoubleF64 {
384 fn add_assign(&mut self, other: Self) {
385 *self = *self + other;
386 }
387}
388
389impl SubAssign for DoubleF64 {
390 fn sub_assign(&mut self, other: Self) {
391 *self = *self - other;
392 }
393}
394
395impl MulAssign for DoubleF64 {
396 fn mul_assign(&mut self, other: Self) {
397 *self = *self * other;
398 }
399}
400
401impl DivAssign for DoubleF64 {
402 fn div_assign(&mut self, other: Self) {
403 *self = *self / other;
404 }
405}
406
407impl PartialOrd for DoubleF64 {
408 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
409 self.hi.partial_cmp(&other.hi)
410 }
411}
412
413impl From<f64> for DoubleF64 {
414 fn from(x: f64) -> Self {
415 Self::from_f64(x)
416 }
417}
418
419impl Zero for DoubleF64 {
420 fn zero() -> Self {
421 Self::ZERO
422 }
423 fn is_zero(&self) -> bool {
424 self.hi == 0.0 && self.lo == 0.0
425 }
426}
427
428impl fmt::Display for DoubleF64 {
429 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430 if self.lo == 0.0 {
432 write!(f, "{}", self.hi)
433 } else {
434 write!(f, "{:.31e}", self.hi + self.lo)
435 }
436 }
437}
438
439#[derive(Debug, Clone, Copy, PartialEq, Eq)]
445pub struct DoubleF64Domain;
446
447impl Domain for DoubleF64Domain {
448 type Element = DoubleF64;
449
450 fn zero(&self) -> Self::Element {
451 DoubleF64::ZERO
452 }
453
454 fn one(&self) -> Self::Element {
455 DoubleF64::ONE
456 }
457
458 fn add(&self, a: &Self::Element, b: &Self::Element) -> Self::Element {
459 *a + *b
460 }
461
462 fn sub(&self, a: &Self::Element, b: &Self::Element) -> Self::Element {
463 *a - *b
464 }
465
466 fn neg(&self, a: &Self::Element) -> Self::Element {
467 -*a
468 }
469
470 fn mul(&self, a: &Self::Element, b: &Self::Element) -> Self::Element {
471 *a * *b
472 }
473
474 fn div(&self, a: &Self::Element, b: &Self::Element) -> Option<Self::Element> {
475 if b.is_zero() { None } else { Some(*a / *b) }
476 }
477
478 fn inv(&self, a: &Self::Element) -> Option<Self::Element> {
479 if a.is_zero() {
480 None
481 } else {
482 Some(DoubleF64::ONE / *a)
483 }
484 }
485
486 fn is_zero(&self, a: &Self::Element) -> bool {
487 a.is_zero()
488 }
489
490 fn is_one(&self, a: &Self::Element) -> bool {
491 *a == DoubleF64::ONE
492 }
493}
494
495#[cfg(test)]
500mod tests {
501 use super::*;
502
503 #[test]
504 fn basic_arithmetic() {
505 let a = DoubleF64::from_f64(1.0);
506 let b = DoubleF64::from_f64(2.0);
507 assert_eq!((a + b).hi, 3.0);
508 assert_eq!((a - b).hi, -1.0);
509 assert_eq!((a * b).hi, 2.0);
510 assert_eq!((a / b).hi, 0.5);
511 }
512
513 #[test]
514 fn precision_gain() {
515 let a = DoubleF64::from_f64(1.0);
517 let b = DoubleF64::from_f64(f64::EPSILON);
518 let sum = a + b;
519 let reconstructed = sum.hi + sum.lo;
521 assert_eq!(reconstructed, 1.0 + f64::EPSILON);
522 }
523
524 #[test]
525 fn two_sum_correctness() {
526 let s = DoubleF64::two_sum(1.0, f64::EPSILON);
527 assert_eq!(s.hi, 1.0 + f64::EPSILON);
528 }
530
531 #[test]
532 fn two_prod_correctness() {
533 let p = DoubleF64::two_prod(3.0, 5.0);
534 assert_eq!(p.hi, 15.0);
535 assert_eq!(p.lo, 0.0);
536 }
537
538 #[test]
539 fn powi_basic() {
540 let x = DoubleF64::from_f64(3.0);
541 assert_eq!(x.powi(0).hi, 1.0);
542 assert_eq!(x.powi(1).hi, 3.0);
543 assert_eq!(x.powi(2).hi, 9.0);
544 assert_eq!(x.powi(3).hi, 27.0);
545 }
546
547 #[test]
548 fn powi_negative() {
549 let x = DoubleF64::from_f64(2.0);
550 assert_eq!(x.powi(-1).hi, 0.5);
551 assert_eq!(x.powi(-2).hi, 0.25);
552 }
553
554 #[test]
555 fn sqrt_basic() {
556 let x = DoubleF64::from_f64(4.0);
557 let s = x.sqrt();
558 assert!((s.hi - 2.0).abs() < 1e-30);
559 }
560
561 #[test]
562 fn sqrt_two() {
563 let x = DoubleF64::from_f64(2.0);
564 let s = x.sqrt();
565 let sq = s * s;
567 assert!((sq.hi - 2.0).abs() < 1e-28);
568 }
569
570 #[test]
571 fn exp_basic() {
572 let zero = DoubleF64::ZERO;
573 assert_eq!(zero.exp().hi, 1.0);
574
575 let one = DoubleF64::ONE;
576 let e = one.exp();
577 assert!((e.hi - std::f64::consts::E).abs() < 1e-28);
579 }
580
581 #[test]
582 fn ln_basic() {
583 let one = DoubleF64::ONE;
584 assert_eq!(one.ln().hi, 0.0);
585
586 let e = DoubleF64::from_f64(std::f64::consts::E);
587 let ln_e = e.ln();
588 assert!((ln_e.hi - 1.0).abs() < 1e-28);
589 }
590
591 #[test]
592 fn sin_cos_basic() {
593 let zero = DoubleF64::ZERO;
594 assert_eq!(zero.sin().hi, 0.0);
595 assert_eq!(zero.cos().hi, 1.0);
596 }
597
598 #[test]
599 fn sin_pi() {
600 let pi = DoubleF64::from_f64(std::f64::consts::PI);
601 let s = pi.sin();
602 assert!(s.hi.abs() < 1e-14, "sin(π) ≈ 0, got {}", s.hi);
604 }
605
606 #[test]
607 fn domain_trait() {
608 let dom = DoubleF64Domain;
609 let a = DoubleF64::from_f64(3.0);
610 let b = DoubleF64::from_f64(4.0);
611 assert_eq!(dom.add(&a, &b).hi, 7.0);
612 assert_eq!(dom.mul(&a, &b).hi, 12.0);
613 assert_eq!(dom.div(&a, &b).unwrap().hi, 0.75);
614 assert!(dom.div(&a, &DoubleF64::ZERO).is_none());
615 }
616
617 #[test]
618 fn display() {
619 #[allow(clippy::approx_constant)]
620 let x = DoubleF64::from_f64(3.14);
621 assert_eq!(format!("{x}"), "3.14");
622 }
623}