firefly_rust/
math.rs

1//! A few useful math (trigonometric) functions for [f32].
2//!
3//! Use [micromath] and [nalgebra] if you need more.
4//!
5//! [micromath]: https://github.com/tarcieri/micromath
6//! [nalgebra]: https://github.com/dimforge/nalgebra
7
8use core::f32::consts::{FRAC_1_PI, FRAC_PI_2, PI};
9
10const SIGN_MASK: u32 = 0b1000_0000_0000_0000_0000_0000_0000_0000;
11
12/// Approximates `tan(x)` in radians with a maximum error of `0.6`.
13#[must_use]
14pub fn tan(x: f32) -> f32 {
15    sin(x) / cos(x)
16}
17
18/// Approximates `sin(x)` in radians with a maximum error of `0.002`.
19#[must_use]
20pub fn sin(x: f32) -> f32 {
21    cos(x - PI / 2.0)
22}
23
24/// Approximates `cos(x)` in radians with a maximum error of `0.002`.
25#[must_use]
26pub fn cos(x: f32) -> f32 {
27    // https://github.com/tarcieri/micromath/blob/main/src/float/cos.rs
28    let mut x = x;
29    x *= FRAC_1_PI / 2.0;
30    x -= 0.25 + floor(x + 0.25);
31    x *= 16.0 * (abs(x) - 0.5);
32    x += 0.225 * x * (abs(x) - 1.0);
33    x
34}
35
36/// Returns the largest integer less than or equal to a number.
37#[must_use]
38pub fn floor(x: f32) -> f32 {
39    // https://github.com/tarcieri/micromath/blob/main/src/float/floor.rs
40    #[expect(clippy::cast_precision_loss)]
41    let mut res = (x as i32) as f32;
42    if x < res {
43        res -= 1.0;
44    }
45    res
46}
47
48/// Returns the absolute value of a number.
49#[must_use]
50pub fn abs(x: f32) -> f32 {
51    // https://github.com/tarcieri/micromath/blob/main/src/float/abs.rs
52    f32::from_bits(x.to_bits() & !SIGN_MASK)
53}
54
55/// Approximates the square root of a number with an average deviation of ~5%.
56#[must_use]
57pub fn sqrt(x: f32) -> f32 {
58    // https://github.com/tarcieri/micromath/blob/main/src/float/sqrt.rs
59    if x >= 0. {
60        f32::from_bits((x.to_bits() + 0x3f80_0000) >> 1)
61    } else {
62        f32::NAN
63    }
64}
65
66// Calculates the least nonnegative remainder of lhs (mod rhs).
67#[must_use]
68pub fn rem_euclid(lhs: f32, rhs: f32) -> f32 {
69    // https://github.com/tarcieri/micromath/blob/main/src/float/rem_euclid.rs
70    let r = lhs % rhs;
71    if r < 0.0 {
72        r + abs(rhs)
73    } else {
74        r
75    }
76}
77
78/// Approximates `atan(x)` approximation in radians with a maximum error of
79/// `0.002`.
80///
81/// Returns [`f32::NAN`] if the number is [`f32::NAN`].
82#[must_use]
83pub fn atan(x: f32) -> f32 {
84    // https://github.com/tarcieri/micromath/blob/main/src/float/atan.rs
85    FRAC_PI_2 * atan_norm(x)
86}
87
88/// Approximates `atan(x)` normalized to the `[−1,1]` range with a maximum
89/// error of `0.1620` degrees.
90#[must_use]
91pub fn atan_norm(x: f32) -> f32 {
92    // https://github.com/tarcieri/micromath/blob/main/src/float/atan.rs
93    const SIGN_MASK: u32 = 0x8000_0000;
94    const B: f32 = 0.596_227;
95
96    // Extract the sign bit
97    let ux_s = SIGN_MASK & x.to_bits();
98
99    // Calculate the arctangent in the first quadrant
100    let bx_a = abs(B * x);
101    let n = bx_a + x * x;
102    let atan_1q = n / (1.0 + bx_a + n);
103
104    // Restore the sign bit and convert to float
105    f32::from_bits(ux_s | atan_1q.to_bits())
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    #[expect(clippy::float_cmp)]
114    fn test_sqrt() {
115        assert_eq!(sqrt(4.), 2.);
116        assert_eq!(sqrt(9.), 3.125);
117    }
118}