1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//! A few useful math (trigonometric) functions for [f32].
//!
//! Use [micromath] and [nalgebra] if you need more.
//!
//! [micromath]: https://github.com/tarcieri/micromath
//! [nalgebra]: https://github.com/dimforge/nalgebra

use core::f32::consts::{FRAC_1_PI, FRAC_PI_2, PI};

const SIGN_MASK: u32 = 0b1000_0000_0000_0000_0000_0000_0000_0000;

/// Approximates `tan(x)` in radians with a maximum error of `0.6`.
#[must_use]
pub fn tan(x: f32) -> f32 {
    sin(x) / cos(x)
}

/// Approximates `sin(x)` in radians with a maximum error of `0.002`.
#[must_use]
pub fn sin(x: f32) -> f32 {
    cos(x - PI / 2.0)
}

/// Approximates `cos(x)` in radians with a maximum error of `0.002`.
#[must_use]
pub fn cos(x: f32) -> f32 {
    // https://github.com/tarcieri/micromath/blob/main/src/float/cos.rs
    let mut x = x;
    x *= FRAC_1_PI / 2.0;
    x -= 0.25 + floor(x + 0.25);
    x *= 16.0 * (abs(x) - 0.5);
    x += 0.225 * x * (abs(x) - 1.0);
    x
}

/// Returns the largest integer less than or equal to a number.
#[must_use]
pub fn floor(x: f32) -> f32 {
    // https://github.com/tarcieri/micromath/blob/main/src/float/floor.rs
    #[allow(clippy::cast_precision_loss)]
    let mut res = (x as i32) as f32;
    if x < res {
        res -= 1.0;
    }
    res
}

/// Returns the absolute value of a number.
#[must_use]
pub fn abs(x: f32) -> f32 {
    // https://github.com/tarcieri/micromath/blob/main/src/float/abs.rs
    f32::from_bits(x.to_bits() & !SIGN_MASK)
}

/// Approximates the square root of a number with an average deviation of ~5%.
#[must_use]
pub fn sqrt(x: f32) -> f32 {
    // https://github.com/tarcieri/micromath/blob/main/src/float/sqrt.rs
    if x >= 0. {
        f32::from_bits((x.to_bits() + 0x3f80_0000) >> 1)
    } else {
        f32::NAN
    }
}

// Calculates the least nonnegative remainder of lhs (mod rhs).
#[must_use]
pub fn rem_euclid(lhs: f32, rhs: f32) -> f32 {
    // https://github.com/tarcieri/micromath/blob/main/src/float/rem_euclid.rs
    let r = lhs % rhs;
    if r < 0.0 {
        r + abs(rhs)
    } else {
        r
    }
}

/// Approximates `atan(x)` approximation in radians with a maximum error of
/// `0.002`.
///
/// Returns [`f32::NAN`] if the number is [`f32::NAN`].
#[must_use]
pub fn atan(x: f32) -> f32 {
    // https://github.com/tarcieri/micromath/blob/main/src/float/atan.rs
    FRAC_PI_2 * atan_norm(x)
}

/// Approximates `atan(x)` normalized to the `[−1,1]` range with a maximum
/// error of `0.1620` degrees.
#[must_use]
pub fn atan_norm(x: f32) -> f32 {
    // https://github.com/tarcieri/micromath/blob/main/src/float/atan.rs
    const SIGN_MASK: u32 = 0x8000_0000;
    const B: f32 = 0.596_227;

    // Extract the sign bit
    let ux_s = SIGN_MASK & x.to_bits();

    // Calculate the arctangent in the first quadrant
    let bx_a = abs(B * x);
    let n = bx_a + x * x;
    let atan_1q = n / (1.0 + bx_a + n);

    // Restore the sign bit and convert to float
    f32::from_bits(ux_s | atan_1q.to_bits())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[allow(clippy::float_cmp)]
    fn test_sqrt() {
        assert_eq!(sqrt(4.), 2.);
        assert_eq!(sqrt(9.), 3.125);
    }
}