1use core::cmp::Ordering;
24use core::fmt;
25use core::ops::{Add, Div, Mul, Neg, Sub};
26
27use crate::error::{CoreError, CoreResult, ErrorContext};
28
29#[inline(always)]
32fn comp_err(msg: impl Into<String>) -> CoreError {
33 CoreError::ComputationError(ErrorContext::new(msg))
34}
35
36#[inline]
49fn two_sum(a: f64, b: f64) -> (f64, f64) {
50 let s = a + b;
51 let v = s - a;
52 let e = (a - (s - v)) + (b - v);
53 (s, e)
54}
55
56#[inline]
60fn two_product(a: f64, b: f64) -> (f64, f64) {
61 let p = a * b;
62 let c = 134_217_729.0_f64 * a; let a_hi = c - (c - a);
65 let a_lo = a - a_hi;
66 let c2 = 134_217_729.0_f64 * b;
68 let b_hi = c2 - (c2 - b);
69 let b_lo = b - b_hi;
70 let e = ((a_hi * b_hi - p) + a_hi * b_lo + a_lo * b_hi) + a_lo * b_lo;
71 (p, e)
72}
73
74#[inline]
77fn split(a: f64) -> (f64, f64) {
78 let c = 134_217_729.0_f64 * a;
79 let hi = c - (c - a);
80 let lo = a - hi;
81 (hi, lo)
82}
83
84#[derive(Debug, Clone, Copy, Default)]
102pub struct DoubleDouble {
103 pub hi: f64,
105 pub lo: f64,
107}
108
109impl DoubleDouble {
110 pub const ZERO: Self = Self { hi: 0.0, lo: 0.0 };
114
115 pub const ONE: Self = Self { hi: 1.0, lo: 0.0 };
117
118 pub const PI: Self = Self {
121 hi: std::f64::consts::PI,
122 lo: 1.224_646_799_147_353_2e-16,
123 };
124
125 pub const E: Self = Self {
128 hi: std::f64::consts::E,
129 lo: -2.842_170_943_040_400_8e-17,
130 };
131
132 #[inline]
136 #[must_use]
137 pub fn new(x: f64) -> Self {
138 Self { hi: x, lo: 0.0 }
139 }
140
141 #[inline]
143 #[must_use]
144 pub fn from_f64(hi: f64, lo: f64) -> Self {
145 Self::renorm(hi, lo)
146 }
147
148 #[inline]
150 #[must_use]
151 pub fn from_i64(x: i64) -> Self {
152 let hi = x as f64;
153 let lo = (x - hi as i64) as f64;
155 Self::renorm(hi, lo)
156 }
157
158 #[inline]
162 #[must_use]
163 pub fn to_f64(self) -> f64 {
164 self.hi
165 }
166
167 #[inline]
170 #[must_use]
171 pub fn to_f128_approx(self) -> f64 {
172 self.hi + self.lo
173 }
174
175 #[inline]
179 #[must_use]
180 pub fn is_finite(self) -> bool {
181 self.hi.is_finite() && self.lo.is_finite()
182 }
183
184 #[inline]
186 #[must_use]
187 pub fn is_nan(self) -> bool {
188 self.hi.is_nan()
189 }
190
191 #[inline]
193 #[must_use]
194 pub fn is_zero(self) -> bool {
195 self.hi == 0.0 && self.lo == 0.0
196 }
197
198 #[inline]
202 #[must_use]
203 pub fn abs(self) -> Self {
204 if self.hi < 0.0 {
205 Self {
206 hi: -self.hi,
207 lo: -self.lo,
208 }
209 } else {
210 self
211 }
212 }
213
214 #[inline]
216 #[must_use]
217 pub fn negate(self) -> Self {
218 Self {
219 hi: -self.hi,
220 lo: -self.lo,
221 }
222 }
223
224 #[must_use]
228 #[allow(clippy::should_implement_trait)]
229 pub fn add(self, rhs: Self) -> Self {
230 let (s1, s2) = two_sum(self.hi, rhs.hi);
231 let (t1, t2) = two_sum(self.lo, rhs.lo);
232 let c = s2 + t1;
233 let (v_hi, v_lo) = two_sum(s1, c);
234 let w = t2 + v_lo;
235 Self::renorm(v_hi, w)
236 }
237
238 #[must_use]
240 #[allow(clippy::should_implement_trait)]
241 pub fn sub(self, rhs: Self) -> Self {
242 self.add(rhs.negate())
243 }
244
245 #[must_use]
249 #[allow(clippy::should_implement_trait)]
250 pub fn mul(self, rhs: Self) -> Self {
251 let (p1, p2) = two_product(self.hi, rhs.hi);
252 let p2 = p2 + self.hi * rhs.lo + self.lo * rhs.hi;
253 Self::renorm(p1, p2)
254 }
255
256 #[allow(clippy::should_implement_trait)]
260 pub fn div(self, rhs: Self) -> CoreResult<Self> {
261 if rhs.is_zero() {
262 return Err(comp_err("DoubleDouble::div — division by zero"));
263 }
264 let q1 = self.hi / rhs.hi;
266 let r = self.sub(Self::new(q1).mul(rhs));
268 let q2 = r.hi / rhs.hi;
270 Ok(Self::renorm(q1, q2))
271 }
272
273 #[must_use]
277 pub fn mul_f64(self, rhs: f64) -> Self {
278 let (p1, p2) = two_product(self.hi, rhs);
279 let p2 = p2 + self.lo * rhs;
280 Self::renorm(p1, p2)
281 }
282
283 #[must_use]
285 pub fn square(self) -> Self {
286 let (p1, p2) = two_product(self.hi, self.hi);
287 let p2 = p2 + 2.0 * self.hi * self.lo;
288 Self::renorm(p1, p2)
289 }
290
291 pub fn sqrt(self) -> CoreResult<Self> {
295 if self.hi < 0.0 {
296 return Err(comp_err("DoubleDouble::sqrt — negative input"));
297 }
298 if self.is_zero() {
299 return Ok(Self::ZERO);
300 }
301 let x0 = Self::new(self.hi.sqrt());
303 let half = Self::new(0.5);
304 let x1 = x0.add(self.div(x0)?).mul(half);
306 let x2 = x1.add(self.div(x1)?).mul(half);
308 Ok(x2)
309 }
310
311 pub fn exp(self) -> CoreResult<Self> {
315 if !self.is_finite() {
316 return Err(comp_err("DoubleDouble::exp — non-finite input"));
317 }
318 let ln2 = Self::ln2_const();
319 let k_f = (self.hi / ln2.hi).round();
320 let k = k_f as i64;
321 let k_ln2 = Self::new(k_f).mul(ln2);
322 let r = self.sub(k_ln2);
323 let n_terms = 30usize;
325 let mut sum = Self::ONE;
326 let mut term = Self::ONE;
327 for n in 1..=n_terms {
328 term = term.mul(r).div(Self::from_i64(n as i64))?;
329 let new_sum = sum.add(term);
330 if term.abs().hi.abs() < sum.abs().hi * f64::EPSILON * 0.5 {
331 sum = new_sum;
332 break;
333 }
334 sum = new_sum;
335 }
336 let exp_k = k + 1023;
338 if exp_k <= 0 || exp_k >= 2047 {
339 let scale = f64::from(2.0_f32).powi(k as i32);
341 return Ok(Self::renorm(sum.hi * scale, sum.lo * scale));
342 }
343 let scale = f64::from_bits((exp_k as u64) << 52);
344 Ok(Self::renorm(sum.hi * scale, sum.lo * scale))
345 }
346
347 pub fn ln(self) -> CoreResult<Self> {
351 if self.hi <= 0.0 {
352 return Err(comp_err("DoubleDouble::ln — argument must be positive"));
353 }
354 if !self.is_finite() {
355 return Err(comp_err("DoubleDouble::ln — non-finite input"));
356 }
357 let a0 = Self::new(self.hi.ln());
359 let exp_a0 = a0.exp()?;
361 let correction = self.sub(exp_a0).div(exp_a0)?;
362 Ok(a0.add(correction))
363 }
364
365 pub fn sin(self) -> CoreResult<Self> {
369 let (s, _c) = self.sincos()?;
370 Ok(s)
371 }
372
373 pub fn cos(self) -> CoreResult<Self> {
377 let (_s, c) = self.sincos()?;
378 Ok(c)
379 }
380
381 pub fn sincos(self) -> CoreResult<(Self, Self)> {
385 if !self.is_finite() {
386 return Err(comp_err("DoubleDouble::sincos — non-finite input"));
387 }
388 let pi = Self::PI;
389 let two = Self::new(2.0);
390 let two_over_pi = two.div(pi)?;
391 let k_f = self.mul(two_over_pi).hi.round();
392 let k = k_f as i64;
393 let half_pi = pi.mul_f64(0.5);
394 let r = self.sub(Self::from_i64(k).mul(half_pi));
395 let r2 = r.square();
396 let n_terms = 20usize;
397 let mut sin_val = r;
399 let mut term_sin = r;
400 let mut cos_val = Self::ONE;
402 let mut term_cos = Self::ONE;
403 for i in 1..=n_terms {
404 term_sin = term_sin
405 .mul(r2.negate())
406 .div(Self::from_i64((2 * i) as i64))?
407 .div(Self::from_i64((2 * i + 1) as i64))?;
408 term_cos = term_cos
409 .mul(r2.negate())
410 .div(Self::from_i64((2 * i - 1) as i64))?
411 .div(Self::from_i64((2 * i) as i64))?;
412 let new_sin = sin_val.add(term_sin);
413 let new_cos = cos_val.add(term_cos);
414 let conv = term_sin.abs().hi.abs() < sin_val.abs().hi * f64::EPSILON * 0.5;
415 sin_val = new_sin;
416 cos_val = new_cos;
417 if conv {
418 break;
419 }
420 }
421 let km4 = ((k % 4) + 4) as usize % 4;
423 let (s, c) = match km4 {
424 0 => (sin_val, cos_val),
425 1 => (cos_val, sin_val.negate()),
426 2 => (sin_val.negate(), cos_val.negate()),
427 _ => (cos_val.negate(), sin_val),
428 };
429 Ok((s, c))
430 }
431
432 pub fn powi(self, n: i32) -> CoreResult<Self> {
436 if n == 0 {
437 return Ok(Self::ONE);
438 }
439 if n < 0 {
440 let inv = Self::ONE.div(self)?;
441 return inv.powi(-n);
442 }
443 let mut result = Self::ONE;
444 let mut base = self;
445 let mut exp = n as u32;
446 while exp > 0 {
447 if exp & 1 == 1 {
448 result = result.mul(base);
449 }
450 base = base.square();
451 exp >>= 1;
452 }
453 Ok(result)
454 }
455
456 #[must_use]
460 pub fn compare(&self, rhs: &Self) -> Ordering {
461 match self.hi.partial_cmp(&rhs.hi) {
462 Some(Ordering::Equal) => self.lo.partial_cmp(&rhs.lo).unwrap_or(Ordering::Equal),
463 Some(ord) => ord,
464 None => Ordering::Equal, }
466 }
467
468 #[inline]
472 #[must_use]
473 pub fn renorm(hi: f64, lo: f64) -> Self {
474 let (s, e) = two_sum(hi, lo);
475 Self { hi: s, lo: e }
476 }
477
478 #[inline]
480 fn ln2_const() -> Self {
481 Self {
482 hi: std::f64::consts::LN_2,
483 lo: 2.319_046_813_846_299_6e-17,
484 }
485 }
486}
487
488impl PartialEq for DoubleDouble {
491 fn eq(&self, other: &Self) -> bool {
492 self.hi == other.hi && self.lo == other.lo
493 }
494}
495
496impl PartialOrd for DoubleDouble {
497 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
498 Some(self.compare(other))
499 }
500}
501
502impl Neg for DoubleDouble {
503 type Output = DoubleDouble;
504 fn neg(self) -> DoubleDouble {
505 self.negate()
506 }
507}
508
509impl Add for DoubleDouble {
510 type Output = DoubleDouble;
511 fn add(self, rhs: DoubleDouble) -> DoubleDouble {
512 DoubleDouble::add(self, rhs)
513 }
514}
515
516impl Sub for DoubleDouble {
517 type Output = DoubleDouble;
518 fn sub(self, rhs: DoubleDouble) -> DoubleDouble {
519 DoubleDouble::sub(self, rhs)
520 }
521}
522
523impl Mul for DoubleDouble {
524 type Output = DoubleDouble;
525 fn mul(self, rhs: DoubleDouble) -> DoubleDouble {
526 DoubleDouble::mul(self, rhs)
527 }
528}
529
530impl Div for DoubleDouble {
533 type Output = DoubleDouble;
534 fn div(self, rhs: DoubleDouble) -> DoubleDouble {
535 DoubleDouble::div(self, rhs).unwrap_or(DoubleDouble {
536 hi: f64::NAN,
537 lo: f64::NAN,
538 })
539 }
540}
541
542impl From<f64> for DoubleDouble {
543 fn from(x: f64) -> DoubleDouble {
544 DoubleDouble::new(x)
545 }
546}
547
548impl From<i64> for DoubleDouble {
549 fn from(x: i64) -> DoubleDouble {
550 DoubleDouble::from_i64(x)
551 }
552}
553
554impl From<i32> for DoubleDouble {
555 fn from(x: i32) -> DoubleDouble {
556 DoubleDouble::new(x as f64)
557 }
558}
559
560impl fmt::Display for DoubleDouble {
561 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
562 write!(f, "{:.30e} + {:.10e}", self.hi, self.lo)
564 }
565}
566
567pub fn dot_dd(a: &[f64], b: &[f64]) -> DoubleDouble {
589 let n = a.len().min(b.len());
590 let mut sum = DoubleDouble::ZERO;
591 for i in 0..n {
592 let (p, e) = two_product(a[i], b[i]);
593 let (s1, s2) = two_sum(sum.hi, p);
594 let err = s2 + e + sum.lo;
595 let (hi, lo_part) = two_sum(s1, err);
596 sum = DoubleDouble { hi, lo: lo_part };
597 }
598 sum
599}
600
601pub fn sum_dd(values: &[f64]) -> DoubleDouble {
618 let mut sum = DoubleDouble::ZERO;
619 for &v in values {
620 let (s, e) = two_sum(sum.hi, v);
621 sum.lo += e;
622 sum.hi = s;
623 }
624 let (hi, lo_part) = two_sum(sum.hi, sum.lo);
626 DoubleDouble { hi, lo: lo_part }
627}
628
629#[cfg(test)]
632mod tests {
633 use super::*;
634
635 #[test]
636 fn test_zero_identity_add() {
637 let x = DoubleDouble::new(std::f64::consts::PI);
638 let result = DoubleDouble::ZERO + x;
639 assert!((result.hi - x.hi).abs() < f64::EPSILON);
640 assert!((result.lo - x.lo).abs() < f64::EPSILON * 2.0);
641 }
642
643 #[test]
644 fn test_one_identity_mul() {
645 let x = DoubleDouble::new(std::f64::consts::E);
646 let result = DoubleDouble::ONE * x;
647 assert!((result.hi - x.hi).abs() < f64::EPSILON * 2.0);
648 }
649
650 #[test]
651 fn test_two_sum_exact() {
652 let a = 1.0_f64;
653 let b = f64::EPSILON / 2.0;
654 let (s, e) = two_sum(a, b);
655 assert_eq!(s + e, a + b, "TwoSum: s + e must equal a + b exactly");
656 }
657
658 #[test]
659 fn test_two_product_exact() {
660 let a = 1.0_f64 + f64::EPSILON;
661 let b = 1.0_f64 + f64::EPSILON;
662 let (p, e) = two_product(a, b);
663 let reconstructed = p + e;
664 assert!(
665 (reconstructed - a * b).abs() <= f64::EPSILON * 4.0,
666 "TwoProd roundtrip: {reconstructed} vs {}",
667 a * b
668 );
669 }
670
671 #[test]
672 fn test_split_exact() {
673 let a = 1.234_567_890_123_456_7_f64;
674 let (hi, lo) = split(a);
675 assert_eq!(hi + lo, a, "split: hi + lo must equal a exactly");
676 }
677
678 #[test]
679 fn test_sqrt_four() {
680 let four = DoubleDouble::new(4.0);
681 let s = four.sqrt().expect("sqrt(4.0) should succeed");
682 assert!(
683 (s.hi - 2.0).abs() < f64::EPSILON * 4.0,
684 "sqrt(4.0) = 2.0, got {}",
685 s.hi
686 );
687 }
688
689 #[test]
690 fn test_exp_one_approx_e() {
691 let one = DoubleDouble::ONE;
692 let e_val = one.exp().expect("exp(1) should succeed");
693 let e_exact = std::f64::consts::E;
694 let diff = (e_val.hi + e_val.lo - e_exact).abs();
696 assert!(diff < 1e-30, "exp(1) − e = {diff}");
697 }
698
699 #[test]
700 fn test_sin_pi_over_6() {
701 let pi_over_6 = DoubleDouble::PI.mul_f64(1.0 / 6.0);
703 let s = pi_over_6.sin().expect("sin should succeed");
704 let diff = (s.hi + s.lo - 0.5).abs();
705 assert!(diff < 1e-14, "sin(π/6) ≈ 0.5, diff = {diff}");
706 }
707
708 #[test]
709 fn test_cos_pi_over_3() {
710 let pi_over_3 = DoubleDouble::PI.mul_f64(1.0 / 3.0);
712 let c = pi_over_3.cos().expect("cos should succeed");
713 let diff = (c.hi + c.lo - 0.5).abs();
714 assert!(diff < 1e-14, "cos(π/3) ≈ 0.5, diff = {diff}");
715 }
716
717 #[test]
718 fn test_dot_dd_precision() {
719 let a = [1e15_f64, 1.0, -1e15];
721 let b = [1.0_f64, 1e15, 1.0];
722 let a2 = [1.0_f64, 2.0, 3.0];
725 let b2 = [4.0_f64, 5.0, 6.0];
726 let result = dot_dd(&a2, &b2);
727 assert!(
728 (result.hi - 32.0).abs() < f64::EPSILON * 4.0,
729 "dot = {}",
730 result.hi
731 );
732 }
733
734 #[test]
735 fn test_sum_dd_catastrophic_cancellation() {
736 let values = [1.0_f64, 1e100, 1.0, -1e100];
738 let result = sum_dd(&values);
739 assert!(
740 (result.to_f64() - 2.0).abs() < 1e-10,
741 "sum_dd of [1, 1e100, 1, -1e100] should be 2.0, got {}",
742 result.to_f64()
743 );
744 }
745
746 #[test]
747 fn test_display_non_empty() {
748 let x = DoubleDouble::new(1.5);
749 let s = format!("{x}");
750 assert!(!s.is_empty(), "Display should produce non-empty string");
751 assert!(s.contains("1.5") || s.contains("1."), "Display: {s}");
752 }
753
754 #[test]
755 fn test_powi_basic() {
756 let two = DoubleDouble::new(2.0);
757 let eight = two.powi(3).expect("2^3 should succeed");
758 assert!(
759 (eight.hi - 8.0).abs() < f64::EPSILON * 4.0,
760 "2^3 = 8, got {}",
761 eight.hi
762 );
763 }
764
765 #[test]
766 fn test_mul_f64() {
767 let x = DoubleDouble::new(3.0);
768 let result = x.mul_f64(4.0);
769 assert!((result.hi - 12.0).abs() < f64::EPSILON * 4.0);
770 }
771
772 #[test]
773 fn test_pi_constant() {
774 let pi = DoubleDouble::PI;
775 let diff = (pi.hi - std::f64::consts::PI).abs();
776 assert!(diff < f64::EPSILON * 2.0, "PI hi part error: {diff}");
777 assert!(
778 pi.lo.abs() > 0.0,
779 "PI lo should be nonzero for extra precision"
780 );
781 }
782
783 #[test]
784 fn test_e_constant() {
785 let e = DoubleDouble::E;
786 let diff = (e.hi - std::f64::consts::E).abs();
787 assert!(diff < f64::EPSILON * 2.0, "E hi part error: {diff}");
788 }
789}