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