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