retrofire_core/math/
float.rs

1//! Floating-point compatibility API.
2//!
3//! Most floating-point functions are currently unavailable in `no_std`.
4//! This module provides the missing functions using either the `libm` or
5//! `micromath` crate, depending on which feature is enabled. As a fallback,
6//! it also implements a critical subset of the functions even if none of
7//! the features is enabled.
8
9#[cfg(feature = "libm")]
10pub mod libm {
11    pub use libm::fabsf as abs;
12    pub use libm::floorf as floor;
13
14    pub use libm::powf;
15    pub use libm::sqrtf as sqrt;
16
17    pub use libm::cosf as cos;
18    pub use libm::sinf as sin;
19    pub use libm::tanf as tan;
20
21    pub use libm::acosf as acos;
22    pub use libm::asinf as asin;
23    pub use libm::atan2f as atan2;
24
25    pub use libm::expf as exp;
26    pub use libm::log2;
27
28    pub use super::fallback::rem_euclid;
29
30    pub fn recip_sqrt(x: f32) -> f32 {
31        powf(x, -0.5)
32    }
33}
34
35#[cfg(feature = "mm")]
36pub mod mm {
37    use micromath::F32Ext as mm;
38
39    #[inline]
40    pub fn abs(x: f32) -> f32 {
41        mm::abs(x)
42    }
43    #[inline]
44    pub fn floor(x: f32) -> f32 {
45        mm::floor(x)
46    }
47    #[inline]
48    pub fn rem_euclid(x: f32, m: f32) -> f32 {
49        mm::rem_euclid(x, m)
50    }
51    /// Returns the approximate square root of `x`.
52    #[inline]
53    pub fn sqrt(x: f32) -> f32 {
54        let y = mm::sqrt(x);
55        // One round of Newton's method
56        0.5 * (y + (x / y))
57    }
58    /// Returns the approximate reciprocal of the square root of `x`.
59    #[inline]
60    pub fn recip_sqrt(x: f32) -> f32 {
61        let y = mm::invsqrt(x);
62        // A round of Newton's method
63        y * (1.5 - 0.5 * x * y * y)
64    }
65    #[inline]
66    pub fn powf(x: f32, y: f32) -> f32 {
67        mm::powf(x, y)
68    }
69    #[inline]
70    pub fn sin(x: f32) -> f32 {
71        mm::sin(x)
72    }
73    #[inline]
74    pub fn cos(x: f32) -> f32 {
75        mm::cos(x)
76    }
77    #[inline]
78    pub fn tan(x: f32) -> f32 {
79        mm::tan(x)
80    }
81    #[inline]
82    pub fn asin(x: f32) -> f32 {
83        mm::asin(x)
84    }
85    #[inline]
86    pub fn acos(x: f32) -> f32 {
87        mm::acos(x)
88    }
89    #[inline]
90    pub fn atan2(y: f32, x: f32) -> f32 {
91        #[cfg(debug_assertions)]
92        if y == 0.0 && x == 0.0 {
93            // Micromath yields a NaN but others return zero
94            return 0.0;
95        }
96        mm::atan2(y, x)
97    }
98}
99
100pub mod fallback {
101    /// Returns the absolute value of `x`.
102    #[inline]
103    pub fn abs(x: f32) -> f32 {
104        f32::from_bits(x.to_bits() & !0x8000_0000)
105    }
106    /// Returns the largest integer less than or equal to `x`.
107    #[inline]
108    pub fn floor(x: f32) -> f32 {
109        (x as i64 - x.is_sign_negative() as i64) as f32
110    }
111    // Returns the least non-negative remainder of `x` (mod `m`).
112    #[inline]
113    pub fn rem_euclid(x: f32, m: f32) -> f32 {
114        x % m + (x.is_sign_negative() as u32 as f32) * m
115    }
116    /// Returns the approximate reciprocal of the square root of `x`.
117    #[inline]
118    pub fn recip_sqrt(x: f32) -> f32 {
119        // https://en.wikipedia.org/wiki/Fast_inverse_square_root
120        let y = f32::from_bits(0x5f37_5a86 - (x.to_bits() >> 1));
121        // A round of Newton's method
122        y * (1.5 - 0.5 * x * y * y)
123    }
124}
125
126#[cfg(feature = "std")]
127#[allow(non_camel_case_types)]
128pub type f32 = core::primitive::f32;
129
130#[allow(unused)]
131pub(crate) trait RecipSqrt {
132    fn recip_sqrt(x: Self) -> Self;
133}
134
135#[cfg(feature = "std")]
136impl RecipSqrt for f32 {
137    fn recip_sqrt(x: f32) -> f32 {
138        x.powf(-0.5)
139    }
140}
141
142#[cfg(all(feature = "libm", not(feature = "std")))]
143pub use libm as f32;
144
145#[cfg(all(feature = "mm", not(feature = "std"), not(feature = "libm")))]
146pub use mm as f32;
147
148#[cfg(not(feature = "fp"))]
149pub use fallback as f32;
150
151#[cfg(test)]
152#[allow(unused_imports)]
153mod tests {
154
155    use crate::assert_approx_eq;
156
157    #[cfg(feature = "libm")]
158    #[test]
159    fn libm_functions() {
160        use super::libm;
161        use core::f32::consts::PI;
162        assert_eq!(libm::cos(PI), -1.0);
163        assert_eq!(libm::sqrt(9.0), 3.0);
164    }
165
166    #[cfg(feature = "mm")]
167    #[test]
168    fn mm_functions() {
169        use core::f32::consts::*;
170
171        use super::f32;
172
173        assert_approx_eq!(f32::sin(FRAC_PI_6), 0.5);
174        assert_eq!(f32::cos(PI), -1.0);
175        assert_eq!(f32::sqrt(16.0), 4.0);
176        assert_approx_eq!(f32::sqrt(9.0), 3.0, eps = 1e-3);
177    }
178
179    #[cfg(feature = "std")]
180    #[test]
181    fn std_functions() {
182        use super::f32;
183        use core::f32::consts::PI;
184        assert_eq!(f32::cos(PI), -1.0);
185        assert_eq!(f32::sqrt(9.0), 3.0);
186    }
187
188    #[cfg(not(feature = "fp"))]
189    #[test]
190    fn fallback_functions() {
191        use super::{f32, RecipSqrt};
192
193        assert_eq!(f32::floor(1.23), 1.0);
194        assert_eq!(f32::floor(0.0), 0.0);
195        assert_eq!(f32::floor(-1.23), -2.0);
196
197        assert_eq!(f32::abs(1.23), 1.23);
198        assert_eq!(f32::abs(0.0), 0.0);
199        assert_eq!(f32::abs(-1.23), 1.23);
200
201        assert_approx_eq!(f32::rem_euclid(1.23, 4.0), 1.23);
202        assert_approx_eq!(f32::rem_euclid(4.0, 4.0), 0.0);
203        assert_approx_eq!(f32::rem_euclid(5.67, 4.0), 1.67);
204        assert_approx_eq!(f32::rem_euclid(-1.23, 4.0), 2.77);
205    }
206
207    #[test]
208    fn recip_sqrt() {
209        use super::{f32, RecipSqrt};
210        assert_approx_eq!(f32::recip_sqrt(2.0), f32::sqrt(0.5), eps = 1e-2);
211        assert_approx_eq!(f32::recip_sqrt(9.0), 1.0 / 3.0, eps = 1e-3);
212    }
213}