amari_core/
precision.rs

1//! High-precision arithmetic traits for scientific computing
2//!
3//! This module provides a unified interface for different precision levels:
4//! - Standard f64 for general use
5//! - Arbitrary precision using rug::Float for critical calculations
6//! - Configurable precision based on application requirements
7
8#[allow(unused_imports)]
9use num_traits::{FromPrimitive, ToPrimitive, Zero};
10
11#[cfg(feature = "std")]
12use std::fmt::{Debug, Display, Formatter, Result};
13
14#[cfg(not(feature = "std"))]
15use core::fmt::{Debug, Display, Formatter, Result};
16
17#[cfg(feature = "std")]
18use std::f64::consts;
19
20#[cfg(not(feature = "std"))]
21use core::f64::consts;
22
23/// Unified trait for floating-point arithmetic in scientific computing
24///
25/// This trait abstracts over different precision levels to allow
26/// mathematical calculations with appropriate numerical accuracy.
27pub trait PrecisionFloat:
28    Clone
29    + Debug
30    + Display
31    + PartialEq
32    + PartialOrd
33    + core::ops::Add<Output = Self>
34    + core::ops::Sub<Output = Self>
35    + core::ops::Mul<Output = Self>
36    + core::ops::Div<Output = Self>
37    + core::ops::Neg<Output = Self>
38    + Send
39    + Sync
40    + 'static
41{
42    /// Create from f64 value
43    fn from_f64(value: f64) -> Self;
44
45    /// Convert to f64 (may lose precision)
46    fn to_f64(&self) -> f64;
47
48    /// Square root with high precision
49    fn sqrt_precise(self) -> Self;
50
51    /// Power function with high precision
52    fn powf_precise(self, exp: Self) -> Self;
53
54    /// Trigonometric functions with high precision
55    fn sin_precise(self) -> Self;
56    /// Cosine function with high precision
57    fn cos_precise(self) -> Self;
58    /// Tangent function with high precision
59    fn tan_precise(self) -> Self;
60
61    /// Hyperbolic functions with high precision
62    fn sinh_precise(self) -> Self;
63    /// Hyperbolic cosine with high precision
64    fn cosh_precise(self) -> Self;
65    /// Hyperbolic tangent with high precision
66    fn tanh_precise(self) -> Self;
67
68    /// Natural logarithm with high precision
69    fn ln_precise(self) -> Self;
70
71    /// Exponential function with high precision
72    fn exp_precise(self) -> Self;
73
74    /// Absolute value
75    fn abs_precise(self) -> Self;
76
77    /// Machine epsilon for this precision level
78    fn epsilon() -> Self;
79
80    /// Recommended tolerance for numerical calculations
81    fn default_tolerance() -> Self;
82
83    /// Specialized tolerance for orbital mechanics calculations
84    fn orbital_tolerance() -> Self;
85
86    /// Mathematical constant π
87    #[allow(non_snake_case)]
88    fn PI() -> Self;
89
90    /// The multiplicative identity (1)
91    fn one() -> Self;
92
93    /// The additive identity (0)
94    fn zero() -> Self;
95
96    /// Square root function (delegated to sqrt_precise)
97    fn sqrt(self) -> Self {
98        self.sqrt_precise()
99    }
100
101    /// Absolute value function (delegated to abs_precise)
102    fn abs(self) -> Self {
103        self.abs_precise()
104    }
105
106    /// Maximum of two values
107    fn max(self, other: Self) -> Self;
108
109    /// Minimum of two values
110    fn min(self, other: Self) -> Self;
111
112    /// Create a 4-element array filled with zero values
113    fn zero_array_4() -> [Self; 4] {
114        [Self::zero(), Self::zero(), Self::zero(), Self::zero()]
115    }
116
117    /// Create a 4x4 matrix filled with zero values
118    fn zero_matrix_4x4() -> [[Self; 4]; 4] {
119        [
120            Self::zero_array_4(),
121            Self::zero_array_4(),
122            Self::zero_array_4(),
123            Self::zero_array_4(),
124        ]
125    }
126
127    /// Create a 4x4x4 tensor filled with zero values
128    fn zero_tensor_4x4x4() -> [[[Self; 4]; 4]; 4] {
129        [
130            Self::zero_matrix_4x4(),
131            Self::zero_matrix_4x4(),
132            Self::zero_matrix_4x4(),
133            Self::zero_matrix_4x4(),
134        ]
135    }
136}
137
138/// Standard f64 implementation for general use
139impl PrecisionFloat for f64 {
140    #[inline]
141    fn from_f64(value: f64) -> Self {
142        value
143    }
144
145    #[inline]
146    fn to_f64(&self) -> f64 {
147        *self
148    }
149
150    #[inline]
151    fn sqrt_precise(self) -> Self {
152        self.sqrt()
153    }
154
155    #[inline]
156    fn powf_precise(self, exp: Self) -> Self {
157        self.powf(exp)
158    }
159
160    #[inline]
161    fn sin_precise(self) -> Self {
162        self.sin()
163    }
164
165    #[inline]
166    fn cos_precise(self) -> Self {
167        self.cos()
168    }
169
170    #[inline]
171    fn tan_precise(self) -> Self {
172        self.tan()
173    }
174
175    #[inline]
176    fn sinh_precise(self) -> Self {
177        self.sinh()
178    }
179
180    #[inline]
181    fn cosh_precise(self) -> Self {
182        self.cosh()
183    }
184
185    #[inline]
186    fn tanh_precise(self) -> Self {
187        self.tanh()
188    }
189
190    #[inline]
191    fn ln_precise(self) -> Self {
192        self.ln()
193    }
194
195    #[inline]
196    fn exp_precise(self) -> Self {
197        self.exp()
198    }
199
200    #[inline]
201    fn abs_precise(self) -> Self {
202        self.abs()
203    }
204
205    #[inline]
206    fn epsilon() -> Self {
207        f64::EPSILON
208    }
209
210    #[inline]
211    fn default_tolerance() -> Self {
212        1e-12
213    }
214
215    #[inline]
216    fn orbital_tolerance() -> Self {
217        1e-15
218    }
219
220    #[inline]
221    fn PI() -> Self {
222        consts::PI
223    }
224
225    #[inline]
226    fn one() -> Self {
227        1.0
228    }
229
230    #[inline]
231    fn zero() -> Self {
232        0.0
233    }
234
235    #[inline]
236    fn max(self, other: Self) -> Self {
237        f64::max(self, other)
238    }
239
240    #[inline]
241    fn min(self, other: Self) -> Self {
242        f64::min(self, other)
243    }
244}
245
246/// High-precision wrapper around rug::Float for critical calculations (native-only)
247#[cfg(feature = "native-precision")]
248#[derive(Clone, Debug)]
249pub struct HighPrecisionFloat {
250    /// The arbitrary precision floating point value
251    value: rug::Float,
252    /// Precision in bits
253    precision: u32,
254}
255
256#[cfg(feature = "native-precision")]
257impl HighPrecisionFloat {
258    /// Create with specified precision (bits)
259    pub fn with_precision(value: f64, precision: u32) -> Self {
260        Self {
261            value: rug::Float::with_val(precision, value),
262            precision,
263        }
264    }
265
266    /// Create with standard precision (128 bits)
267    pub fn standard(value: f64) -> Self {
268        Self::with_precision(value, 128)
269    }
270
271    /// Create with high precision (256 bits) for critical calculations
272    pub fn high(value: f64) -> Self {
273        Self::with_precision(value, 256)
274    }
275
276    /// Get the precision in bits
277    pub fn precision(&self) -> u32 {
278        self.precision
279    }
280}
281
282#[cfg(feature = "native-precision")]
283impl PartialEq for HighPrecisionFloat {
284    fn eq(&self, other: &Self) -> bool {
285        self.value == other.value
286    }
287}
288
289#[cfg(feature = "native-precision")]
290impl PartialOrd for HighPrecisionFloat {
291    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
292        self.value.partial_cmp(&other.value)
293    }
294}
295
296#[cfg(feature = "native-precision")]
297impl Display for HighPrecisionFloat {
298    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
299        write!(f, "{}", self.value)
300    }
301}
302
303// TODO: Implement NumFloat and other required traits for HighPrecisionFloat
304// This would require substantial implementation for full compatibility
305
306/// High-precision wrapper around dashu_float::FBig for WASM-compatible calculations
307#[cfg(all(
308    any(feature = "high-precision", feature = "wasm-precision"),
309    not(feature = "native-precision")
310))]
311#[derive(Clone, Debug)]
312pub struct DashuFloat {
313    /// The arbitrary precision floating point value
314    value: dashu_float::FBig,
315    /// Precision in decimal digits (approximation)
316    precision: u32,
317}
318
319#[cfg(all(
320    any(feature = "high-precision", feature = "wasm-precision"),
321    not(feature = "native-precision")
322))]
323impl DashuFloat {
324    /// Create with specified precision (decimal digits)
325    pub fn with_precision(value: f64, precision: u32) -> Self {
326        use dashu_float::FBig;
327
328        Self {
329            value: FBig::try_from(value).unwrap_or(FBig::ZERO),
330            precision,
331        }
332    }
333
334    /// Create with standard precision (40 decimal digits ≈ 128 bits)
335    pub fn standard(value: f64) -> Self {
336        Self::with_precision(value, 40)
337    }
338
339    /// Create with high precision (80 decimal digits ≈ 256 bits) for critical calculations
340    pub fn high(value: f64) -> Self {
341        Self::with_precision(value, 80)
342    }
343
344    /// Get the precision in decimal digits
345    pub fn precision(&self) -> u32 {
346        self.precision
347    }
348}
349
350#[cfg(all(
351    any(feature = "high-precision", feature = "wasm-precision"),
352    not(feature = "native-precision")
353))]
354impl PartialEq for DashuFloat {
355    fn eq(&self, other: &Self) -> bool {
356        self.value == other.value
357    }
358}
359
360#[cfg(all(
361    any(feature = "high-precision", feature = "wasm-precision"),
362    not(feature = "native-precision")
363))]
364impl PartialOrd for DashuFloat {
365    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
366        self.value.partial_cmp(&other.value)
367    }
368}
369
370#[cfg(all(
371    any(feature = "high-precision", feature = "wasm-precision"),
372    not(feature = "native-precision")
373))]
374impl Display for DashuFloat {
375    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
376        write!(f, "{}", self.value)
377    }
378}
379
380#[cfg(all(
381    any(feature = "high-precision", feature = "wasm-precision"),
382    not(feature = "native-precision")
383))]
384impl PrecisionFloat for DashuFloat {
385    #[inline]
386    fn from_f64(value: f64) -> Self {
387        Self::standard(value)
388    }
389
390    #[inline]
391    fn to_f64(&self) -> f64 {
392        // Convert dashu FBig to f64 (may lose precision)
393        f64::try_from(self.value.clone()).unwrap_or(0.0)
394    }
395
396    #[inline]
397    fn sqrt_precise(self) -> Self {
398        // Use f64 conversion for mathematical operations not directly available in dashu
399        let f64_val = f64::try_from(self.value).unwrap_or(0.0);
400        Self::with_precision(f64_val.sqrt(), self.precision)
401    }
402
403    #[inline]
404    fn powf_precise(self, exp: Self) -> Self {
405        let base_f64 = f64::try_from(self.value).unwrap_or(0.0);
406        let exp_f64 = f64::try_from(exp.value).unwrap_or(0.0);
407        Self::with_precision(base_f64.powf(exp_f64), self.precision.min(exp.precision))
408    }
409
410    #[inline]
411    fn sin_precise(self) -> Self {
412        let f64_val = f64::try_from(self.value).unwrap_or(0.0);
413        Self::with_precision(f64_val.sin(), self.precision)
414    }
415
416    #[inline]
417    fn cos_precise(self) -> Self {
418        let f64_val = f64::try_from(self.value).unwrap_or(0.0);
419        Self::with_precision(f64_val.cos(), self.precision)
420    }
421
422    #[inline]
423    fn tan_precise(self) -> Self {
424        let f64_val = f64::try_from(self.value).unwrap_or(0.0);
425        Self::with_precision(f64_val.tan(), self.precision)
426    }
427
428    #[inline]
429    fn sinh_precise(self) -> Self {
430        let f64_val = f64::try_from(self.value).unwrap_or(0.0);
431        Self::with_precision(f64_val.sinh(), self.precision)
432    }
433
434    #[inline]
435    fn cosh_precise(self) -> Self {
436        let f64_val = f64::try_from(self.value).unwrap_or(0.0);
437        Self::with_precision(f64_val.cosh(), self.precision)
438    }
439
440    #[inline]
441    fn tanh_precise(self) -> Self {
442        let f64_val = f64::try_from(self.value).unwrap_or(0.0);
443        Self::with_precision(f64_val.tanh(), self.precision)
444    }
445
446    #[inline]
447    fn ln_precise(self) -> Self {
448        // Use dashu's ln directly
449        Self {
450            value: self.value.ln(),
451            precision: self.precision,
452        }
453    }
454
455    #[inline]
456    fn exp_precise(self) -> Self {
457        let f64_val = f64::try_from(self.value).unwrap_or(0.0);
458        Self::with_precision(f64_val.exp(), self.precision)
459    }
460
461    #[inline]
462    fn abs_precise(self) -> Self {
463        // Import Abs trait and use it
464        use dashu_float::ops::Abs;
465        Self {
466            value: self.value.abs(),
467            precision: self.precision,
468        }
469    }
470
471    #[inline]
472    fn epsilon() -> Self {
473        // Machine epsilon for high precision calculations
474        Self::with_precision(1e-40, 40)
475    }
476
477    #[inline]
478    fn default_tolerance() -> Self {
479        Self::with_precision(1e-12, 40)
480    }
481
482    #[inline]
483    fn orbital_tolerance() -> Self {
484        Self::with_precision(1e-15, 80)
485    }
486
487    #[inline]
488    fn PI() -> Self {
489        Self::with_precision(consts::PI, 40)
490    }
491
492    #[inline]
493    fn one() -> Self {
494        Self::with_precision(1.0, 40)
495    }
496
497    #[inline]
498    fn zero() -> Self {
499        Self::with_precision(0.0, 40)
500    }
501
502    #[inline]
503    fn max(self, other: Self) -> Self {
504        let self_f64 = f64::try_from(self.value.clone()).unwrap_or(0.0);
505        let other_f64 = f64::try_from(other.value.clone()).unwrap_or(0.0);
506        if self_f64 >= other_f64 {
507            self
508        } else {
509            other
510        }
511    }
512
513    #[inline]
514    fn min(self, other: Self) -> Self {
515        let self_f64 = f64::try_from(self.value.clone()).unwrap_or(0.0);
516        let other_f64 = f64::try_from(other.value.clone()).unwrap_or(0.0);
517        if self_f64 <= other_f64 {
518            self
519        } else {
520            other
521        }
522    }
523}
524
525// Implement basic arithmetic traits for DashuFloat
526#[cfg(all(
527    any(feature = "high-precision", feature = "wasm-precision"),
528    not(feature = "native-precision")
529))]
530impl core::ops::Add for DashuFloat {
531    type Output = Self;
532
533    fn add(self, other: Self) -> Self {
534        Self {
535            value: self.value + other.value,
536            precision: self.precision.min(other.precision),
537        }
538    }
539}
540
541#[cfg(all(
542    any(feature = "high-precision", feature = "wasm-precision"),
543    not(feature = "native-precision")
544))]
545impl core::ops::Sub for DashuFloat {
546    type Output = Self;
547
548    fn sub(self, other: Self) -> Self {
549        Self {
550            value: self.value - other.value,
551            precision: self.precision.min(other.precision),
552        }
553    }
554}
555
556#[cfg(all(
557    any(feature = "high-precision", feature = "wasm-precision"),
558    not(feature = "native-precision")
559))]
560impl core::ops::Mul for DashuFloat {
561    type Output = Self;
562
563    fn mul(self, other: Self) -> Self {
564        Self {
565            value: self.value * other.value,
566            precision: self.precision.min(other.precision),
567        }
568    }
569}
570
571#[cfg(all(
572    any(feature = "high-precision", feature = "wasm-precision"),
573    not(feature = "native-precision")
574))]
575impl core::ops::Div for DashuFloat {
576    type Output = Self;
577
578    fn div(self, other: Self) -> Self {
579        Self {
580            value: self.value / other.value,
581            precision: self.precision.min(other.precision),
582        }
583    }
584}
585
586#[cfg(all(
587    any(feature = "high-precision", feature = "wasm-precision"),
588    not(feature = "native-precision")
589))]
590impl core::ops::Neg for DashuFloat {
591    type Output = Self;
592
593    fn neg(self) -> Self {
594        Self {
595            value: -self.value,
596            precision: self.precision,
597        }
598    }
599}
600
601#[cfg(all(
602    any(feature = "high-precision", feature = "wasm-precision"),
603    not(feature = "native-precision")
604))]
605impl num_traits::Zero for DashuFloat {
606    fn zero() -> Self {
607        Self {
608            value: dashu_float::FBig::ZERO,
609            precision: 40,
610        }
611    }
612
613    fn is_zero(&self) -> bool {
614        // Check if the value equals zero by converting to f64 and checking
615        f64::try_from(self.value.clone()).unwrap_or(1.0) == 0.0
616    }
617}
618
619#[cfg(all(
620    any(feature = "high-precision", feature = "wasm-precision"),
621    not(feature = "native-precision")
622))]
623impl num_traits::One for DashuFloat {
624    fn one() -> Self {
625        Self {
626            value: dashu_float::FBig::ONE,
627            precision: 40,
628        }
629    }
630}
631
632#[cfg(all(
633    any(feature = "high-precision", feature = "wasm-precision"),
634    not(feature = "native-precision")
635))]
636impl num_traits::FromPrimitive for DashuFloat {
637    fn from_f64(n: f64) -> Option<Self> {
638        Some(<DashuFloat as PrecisionFloat>::from_f64(n))
639    }
640
641    fn from_f32(n: f32) -> Option<Self> {
642        Some(<DashuFloat as PrecisionFloat>::from_f64(n as f64))
643    }
644
645    fn from_i64(n: i64) -> Option<Self> {
646        Some(<DashuFloat as PrecisionFloat>::from_f64(n as f64))
647    }
648
649    fn from_u64(n: u64) -> Option<Self> {
650        Some(<DashuFloat as PrecisionFloat>::from_f64(n as f64))
651    }
652}
653
654// ToPrimitive implementation removed to avoid method name collision with PrecisionFloat::to_f64
655
656/// Type alias for standard precision calculations
657pub type StandardFloat = f64;
658
659/// Type alias for extended precision calculations - backend selected based on features
660///
661/// Selection priority:
662/// 1. `native-precision` feature -> rug backend (maximum performance, native-only)
663/// 2. `high-precision` or `wasm-precision` -> dashu backend (WASM-compatible)
664/// 3. No precision features -> f64 fallback
665#[cfg(feature = "native-precision")]
666pub type ExtendedFloat = HighPrecisionFloat;
667
668#[cfg(all(
669    any(feature = "high-precision", feature = "wasm-precision"),
670    not(feature = "native-precision")
671))]
672pub type ExtendedFloat = DashuFloat;
673
674#[cfg(not(any(
675    feature = "native-precision",
676    feature = "wasm-precision",
677    feature = "high-precision"
678)))]
679pub type ExtendedFloat = f64;
680
681#[cfg(test)]
682mod tests {
683    use super::*;
684
685    #[test]
686    fn test_standard_float_precision() {
687        let x = 2.0_f64;
688        assert!((x.sqrt_precise() - f64::sqrt(2.0)).abs() < f64::EPSILON);
689        assert!((x.powf_precise(3.0) - 8.0).abs() < f64::EPSILON);
690    }
691
692    #[test]
693    fn test_default_tolerance() {
694        assert!(f64::default_tolerance() < f64::EPSILON.sqrt());
695        assert!(f64::default_tolerance() > 0.0);
696    }
697
698    #[cfg(feature = "native-precision")]
699    #[test]
700    fn test_high_precision_creation() {
701        let hp = HighPrecisionFloat::standard(consts::PI);
702        assert_eq!(hp.precision(), 128);
703
704        let sp = HighPrecisionFloat::high(consts::E);
705        assert_eq!(sp.precision(), 256);
706    }
707
708    #[cfg(all(
709        any(feature = "high-precision", feature = "wasm-precision"),
710        not(feature = "native-precision")
711    ))]
712    #[test]
713    fn test_dashu_float_creation() {
714        let df = DashuFloat::standard(core::f64::consts::PI);
715        assert_eq!(df.precision(), 40);
716
717        let df_high = DashuFloat::high(core::f64::consts::E);
718        assert_eq!(df_high.precision(), 80);
719    }
720
721    #[cfg(all(
722        any(feature = "high-precision", feature = "wasm-precision"),
723        not(feature = "native-precision")
724    ))]
725    #[test]
726    fn test_dashu_float_precision_operations() {
727        let x = <DashuFloat as PrecisionFloat>::from_f64(2.0);
728        let sqrt_result = x.clone().sqrt_precise();
729        let expected = <DashuFloat as PrecisionFloat>::from_f64(f64::sqrt(2.0));
730
731        // Test that results are approximately equal (within tolerance)
732        let diff = (sqrt_result.to_f64() - expected.to_f64()).abs();
733        assert!(diff < 1e-10, "sqrt precision test failed: diff = {}", diff);
734
735        // Test power function
736        let power_result = x
737            .clone()
738            .powf_precise(<DashuFloat as PrecisionFloat>::from_f64(3.0));
739        let power_expected = <DashuFloat as PrecisionFloat>::from_f64(8.0);
740        let power_diff = (power_result.to_f64() - power_expected.to_f64()).abs();
741        assert!(
742            power_diff < 1e-10,
743            "power precision test failed: diff = {}",
744            power_diff
745        );
746    }
747
748    #[cfg(all(
749        any(feature = "high-precision", feature = "wasm-precision"),
750        not(feature = "native-precision")
751    ))]
752    #[test]
753    fn test_dashu_float_arithmetic() {
754        let a = <DashuFloat as PrecisionFloat>::from_f64(3.0);
755        let b = <DashuFloat as PrecisionFloat>::from_f64(4.0);
756
757        let sum = a.clone() + b.clone();
758        assert!((sum.to_f64() - 7.0).abs() < 1e-10);
759
760        let product = a.clone() * b.clone();
761        assert!((product.clone().to_f64() - 12.0).abs() < 1e-10);
762
763        let quotient = product / a;
764        assert!((quotient.to_f64() - 4.0).abs() < 1e-10);
765    }
766
767    #[cfg(all(
768        any(feature = "high-precision", feature = "wasm-precision"),
769        not(feature = "native-precision")
770    ))]
771    #[test]
772    fn test_dashu_float_transcendental() {
773        let x = <DashuFloat as PrecisionFloat>::from_f64(1.0);
774
775        // Test sin/cos for pi/2
776        let pi_half = <DashuFloat as PrecisionFloat>::from_f64(core::f64::consts::PI / 2.0);
777        let sin_result = pi_half.clone().sin_precise();
778        let cos_result = pi_half.cos_precise();
779
780        // sin(π/2) should be approximately 1
781        assert!((sin_result.to_f64() - 1.0).abs() < 1e-6);
782        // cos(π/2) should be approximately 0
783        assert!(cos_result.to_f64().abs() < 1e-6);
784
785        // Test ln/exp identity: ln(exp(x)) = x
786        let exp_result = x.clone().exp_precise();
787        let ln_exp_result = exp_result.ln_precise();
788        assert!((ln_exp_result.to_f64() - 1.0).abs() < 1e-10);
789    }
790}