Skip to main content

complex_bessel/
machine.rs

1//! Machine constants and the `BesselFloat` trait.
2//!
3//! Constants follow the Fortran I1MACH/D1MACH conventions from TOMS 644.
4
5use num_traits::Float;
6
7/// Floating-point trait for Bessel and Airy function computation.
8///
9/// Implemented for `f64` and `f32`. Provides machine constants and
10/// derived thresholds used by the Amos algorithm.
11pub trait BesselFloat: Float + core::fmt::Debug + 'static {
12    /// Machine epsilon (D1MACH(3)).
13    const MACH_EPSILON: Self;
14    /// Smallest positive normal number (D1MACH(1)).
15    const MACH_TINY: Self;
16    /// Largest representable number (D1MACH(2)).
17    const MACH_HUGE: Self;
18    /// Number of binary digits in the mantissa (I1MACH(14)).
19    const MACH_DIGITS: i32;
20    /// Minimum binary exponent (I1MACH(12)).
21    const MACH_MIN_EXP: i32;
22    /// Maximum binary exponent (I1MACH(11)).
23    const MACH_MAX_EXP: i32;
24
25    /// Infallible conversion from f64.
26    ///
27    /// For f64 this is the identity; for f32 it truncates via `as f32`.
28    /// All Amos algorithm constants originate as f64 literals, so this
29    /// conversion always succeeds for the supported types.
30    fn from_f64(x: f64) -> Self;
31
32    /// Tolerance: max(MACH_EPSILON, 1e-18).
33    fn tol() -> Self;
34    /// Large order threshold: 10 + 6*(DIG - 3), where DIG = log10(2) * (DIGITS - 1).
35    fn fnul() -> Self;
36    /// Asymptotic region boundary: 1.2*DIG + 3, where DIG = log10(2) * (DIGITS - 1).
37    fn rl() -> Self;
38    /// Underflow elimination threshold: 2.303*(K*R1M5 - 3), K = min(|MIN_EXP|, MAX_EXP).
39    fn elim() -> Self;
40    /// Overflow elimination threshold: ELIM + max(-2.303*R1M5*(DIGITS-1), -41.45).
41    fn alim() -> Self;
42
43    /// Fused multiply-add: `self * a + b`.
44    ///
45    /// With `std` enabled, uses hardware FMA via the C library `fma()`.
46    /// Without `std`, falls back to plain `self * a + b` to avoid the
47    /// slow software FMA in libm.
48    ///
49    /// Named `fma` to avoid ambiguity with [`Float::mul_add`].
50    fn fma(self, a: Self, b: Self) -> Self;
51}
52
53impl BesselFloat for f64 {
54    const MACH_EPSILON: f64 = 2.220446049250313e-16;
55    const MACH_TINY: f64 = 2.2250738585072014e-308;
56    const MACH_HUGE: f64 = 1.7976931348623157e+308;
57    const MACH_DIGITS: i32 = 53;
58    const MACH_MIN_EXP: i32 = -1021;
59    const MACH_MAX_EXP: i32 = 1024;
60
61    #[inline]
62    fn from_f64(x: f64) -> f64 {
63        x
64    }
65    #[inline]
66    fn tol() -> f64 {
67        2.220446049250313e-16
68    } // max(EPSILON, 1e-18)
69    #[inline]
70    fn fnul() -> f64 {
71        85.92135864716212
72    } // 10 + 6*(DIG - 3)
73    #[inline]
74    fn rl() -> f64 {
75        21.784271729432426
76    } // 1.2*DIG + 3
77    #[inline]
78    fn elim() -> f64 {
79        700.9217936944459
80    } // 2.303*(K*R1M5 - 3)
81    #[inline]
82    fn alim() -> f64 {
83        664.8716455337102
84    } // ELIM + max(-2.303*R1M5*(DIGITS-1), -41.45)
85
86    #[cfg(feature = "std")]
87    #[inline]
88    fn fma(self, a: f64, b: f64) -> f64 {
89        Float::mul_add(self, a, b)
90    }
91
92    #[cfg(not(feature = "std"))]
93    #[inline]
94    fn fma(self, a: f64, b: f64) -> f64 {
95        self * a + b
96    }
97}
98
99// Derived constants are written at full f64 precision to document the exact
100// formula results; the compiler rounds to f32 at compile time.
101#[allow(clippy::excessive_precision)]
102impl BesselFloat for f32 {
103    const MACH_EPSILON: f32 = 1.1920929e-7;
104    const MACH_TINY: f32 = 1.1754944e-38;
105    const MACH_HUGE: f32 = 3.4028235e+38;
106    const MACH_DIGITS: i32 = 24;
107    const MACH_MIN_EXP: i32 = -125;
108    const MACH_MAX_EXP: i32 = 128;
109
110    #[inline]
111    fn from_f64(x: f64) -> f32 {
112        x as f32
113    }
114    #[inline]
115    fn tol() -> f32 {
116        1.1920929e-7
117    } // max(EPSILON, 1e-18)
118    #[inline]
119    fn fnul() -> f32 {
120        33.542139401629406
121    } // 10 + 6*(DIG - 3)
122    #[inline]
123    fn rl() -> f32 {
124        11.308427880325882
125    } // 1.2*DIG + 3
126    #[inline]
127    fn elim() -> f32 {
128        79.75001000176859
129    } // 2.303*(K*R1M5 - 3)
130    #[inline]
131    fn alim() -> f32 {
132        63.80475216144317
133    } // ELIM + max(-2.303*R1M5*(DIGITS-1), -41.45)
134
135    #[cfg(feature = "std")]
136    #[inline]
137    fn fma(self, a: f32, b: f32) -> f32 {
138        Float::mul_add(self, a, b)
139    }
140
141    #[cfg(not(feature = "std"))]
142    #[inline]
143    fn fma(self, a: f32, b: f32) -> f32 {
144        self * a + b
145    }
146}