ape_table_trig/
lib.rs

1//! ## *Implementations of sin, cos, and tan using precalculated tables.*
2//!
3//! Using these functions can significantly improve performance on systems with
4//! limited to no naitive floating point support, like the RP2040. Designed to
5//! be no_std compatible out of the box.
6//!
7//! # Example:
8//!
9//! ```rust
10//! use ape_table_trig::*;
11//!
12//! // Table has an accuracy down to 1πmrad
13//! static TABLE: [f32; 1000] = trig_table_gen_f32!(1000);
14//!
15//! fn main() {
16//!     let table = TrigTableF32::new(&TABLE);
17//!
18//!     // Calculate the sine of 1π radians
19//!     let sine = table.sin(PI_F32);
20//! }
21//! ```
22
23
24pub use ape_table_trig_macros::*;
25
26pub use core::f32::consts::PI as PI_F32;
27pub use core::f64::consts::PI as PI_F64;
28
29/// Quarter circumference in radians, equal to ½π. F32.
30pub const QUART_CIRC_F32: f32 = 0.5 * PI_F32;
31/// Quarter circumference in radians, equal to ½π. F64.
32pub const QUART_CIRC_F64: f64 = 0.5 * PI_F64;
33
34/// Half circumference in radians, equal to 1π. F32.
35pub const HALF_CIRC_F32: f32 = PI_F32;
36/// Half circumference in radians, equal to 1π. F64.
37pub const HALF_CIRC_F64: f64 = PI_F64;
38
39/// Full circumference in radians, equal to 2π. F32.
40pub const FULL_CIRC_F32: f32 = 2.0 * PI_F32;
41/// Full circumference in radians, equal to 2π. F32.
42pub const FULL_CIRC_F64: f64 = 2.0 * PI_F64;
43
44/// Generation limit for the trig table. Currently the table generation generates
45/// sin(0)..sin(1π), and then uses some math to finesse the table to work for all
46/// other values. F32.
47pub const GEN_LIMIT_F32: f32 = QUART_CIRC_F32;
48/// Generation limit for the trig table. F64
49pub const GEN_LIMIT_F64: f64 = QUART_CIRC_F64;
50
51#[inline]
52/// Get the absolute value of a float. F32.
53pub fn abs_f32(float: f32) -> f32 {
54    // Ungodly ugly, but it optimises down really well
55    f32::from_ne_bytes(
56        (u32::from_ne_bytes(
57            float.to_ne_bytes()
58        ) & (u32::MAX >> 1)).to_ne_bytes()
59    )
60}
61
62#[inline]
63/// Corrected remainder due to floating point silliness
64pub fn rem_32(a: f32, b: f32) -> f32 {
65    let a_over_b = ((a / b) as u32) as f32;
66
67    a - (a_over_b * b)
68}
69
70#[inline]
71/// Corrected remainder due to floating point silliness
72pub fn rem_64(a: f64, b: f64) -> f64 {
73    let a_over_b = ((a / b) as u64) as f64;
74
75    a - (a_over_b * b)
76}
77
78#[inline]
79/// Get the absolute value of a float. F64.
80pub fn abs_f64(float: f64) -> f64 {
81    // Ungodly ugly, but it optimises down really well
82    f64::from_ne_bytes(
83        (u64::from_ne_bytes(
84            float.to_ne_bytes()
85        ) & (u64::MAX >> 1)).to_ne_bytes()
86    )
87}
88
89/// Used to perform sin, cos, and tan functions on trig tables. F32.
90pub struct TrigTableF32 {
91    table: &'static [f32],
92}
93
94impl TrigTableF32 {
95    /// Create a table struct with a reference to a static table.
96    pub fn new(table: &'static [f32]) -> Self {
97        Self {
98            table
99        }
100    }
101
102    /// Calculate the approximate sine of the radians provided.
103    pub fn sin(&self, radians: f32) -> f32 {
104        let is_negative = radians < 0.0;
105        let radians = abs_f32(radians);
106
107        let rad_mod = rem_32(radians, GEN_LIMIT_F32);
108
109        // Every other quadrant reverse the sine
110        let is_reverse = ((radians / GEN_LIMIT_F32) as u32) % 2 == 1;
111
112        let table_len = self.table.len();
113
114        // Add 0.5 to do a quick round...
115        let index = (((rad_mod / GEN_LIMIT_F32) * (table_len as f32)) + 0.5) as usize;
116
117        // Reverse the index if necissary...
118        let index = match is_reverse {
119            false => index,
120            true => table_len - index,
121        };
122
123        let sin = match index >= table_len {
124            false => self.table[index],
125            true => 1.0,
126        };
127
128        // Every two quadrants negate the sine
129        let is_negative = is_negative ^ (((radians / (2.0 * GEN_LIMIT_F32)) as u32) % 2 == 1);
130
131        match is_negative {
132            true => -sin,
133            false => sin,
134        }
135    }
136
137    #[inline]
138    /// Calculate the approximate cosine of the radians provided.
139    pub fn cos(&self, radians: f32) -> f32 {
140        self.sin(radians + QUART_CIRC_F32)
141    }
142
143    #[inline]
144    /// Calculate the approximate tangent of the radians provided.
145    pub fn tan(&self, radians: f32) -> f32 {
146        self.sin(radians) / self.cos(radians)
147    }
148}
149
150/// Used to perform sin, cos, and tan functions on trig tables. F64.
151pub struct TrigTableF64 {
152    table: &'static [f64],
153}
154
155impl TrigTableF64 {
156    /// Create a table struct with a reference to a static table.
157    pub fn new(table: &'static [f64]) -> Self {
158        Self {
159            table
160        }
161    }
162
163    /// Calculate the approximate sine of the radians provided.
164    pub fn sin(&self, radians: f64) -> f64 {
165        let is_negative = radians < 0.0;
166        let radians = abs_f64(radians);
167
168        let rad_mod = rem_64(radians, GEN_LIMIT_F64);
169
170        // Every other quadrant reverse the sine
171        let is_reverse = ((radians / GEN_LIMIT_F64) as u64) % 2 == 1;
172
173        let table_len = self.table.len();
174
175        // Add 0.5 to do a quick round...
176        let index = (((rad_mod / GEN_LIMIT_F64) * (table_len as f64)) + 0.5) as usize;
177
178        // Reverse the index if necissary...
179        let index = match is_reverse {
180            false => index,
181            true => table_len - index,
182        };
183
184        let sin = match index >= table_len {
185            false => self.table[index],
186            true => 1.0,
187        };
188
189        // Every two quadrants negate the sine
190        let is_negative = is_negative ^ (((radians / (2.0 * GEN_LIMIT_F64)) as u64) % 2 == 1);
191
192        match is_negative {
193            true => -sin,
194            false => sin,
195        }
196    }
197
198    #[inline]
199    /// Calculate the approximate cosine of the radians provided.
200    pub fn cos(&self, radians: f64) -> f64 {
201        self.sin(radians + QUART_CIRC_F64)
202    }
203
204    #[inline]
205    /// Calculate the approximate tangent of the radians provided.
206    pub fn tan(&self, radians: f64) -> f64 {
207        self.sin(radians) / self.cos(radians)
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214
215    static TABLE_F32: [f32; 1_000] = trig_table_gen_f32!(1000);
216    static TABLE_F64: [f64; 1_000_000] = trig_table_gen_f64!(1000000);
217
218    #[test]
219    fn test_sin_f32() {
220        let table = TrigTableF32::new(&TABLE_F32);
221
222        // We can only test half the table since floating point innacuracy
223        // kicks in past the GEN_LIMIT
224        for i in 0..1000 {
225            // Go through the table and verify everything
226            let radians = (i as f32)/4000.0 * FULL_CIRC_F32;
227
228            let sin1 = radians.sin();
229            let sin2 = table.sin(radians);
230
231            assert_eq!(sin1, sin2, "\ni={}", i);
232        }
233
234        // Ensure the quadrants work as expected
235        assert_eq!(table.sin(QUART_CIRC_F32), 1.0);
236        assert_eq!(table.sin(HALF_CIRC_F32), 0.0);
237        assert_eq!(table.sin(HALF_CIRC_F32 + QUART_CIRC_F32), -1.0);
238        assert_eq!(table.sin(FULL_CIRC_F32), 0.0);
239        
240        assert_eq!(table.sin(-QUART_CIRC_F32), -1.0);
241        assert_eq!(table.sin(-HALF_CIRC_F32), 0.0);
242        assert_eq!(table.sin(-HALF_CIRC_F32 - QUART_CIRC_F32), 1.0);
243        assert_eq!(table.sin(-FULL_CIRC_F32), 0.0);
244    }
245    
246    #[test]
247    fn test_sin_f64() {
248        let table = TrigTableF64::new(&TABLE_F64);
249
250        // We can only test half the table since floating point innacuracy
251        // kicks in past the GEN_LIMIT
252        for i in 0..1_000_000 {
253            // Go through the table and verify everything
254            let radians = (i as f64)/4_000_000.0 * FULL_CIRC_F64;
255
256            let sin1 = radians.sin();
257            let sin2 = table.sin(radians);
258
259            assert_eq!(sin1, sin2, "\ni={}", i);
260        }
261
262        // Ensure the quadrants work as expected
263        assert_eq!(table.sin(QUART_CIRC_F64), 1.0);
264        assert_eq!(table.sin(HALF_CIRC_F64), 0.0);
265        assert_eq!(table.sin(HALF_CIRC_F64 + QUART_CIRC_F64), -1.0);
266        assert_eq!(table.sin(FULL_CIRC_F64), 0.0);
267        
268        assert_eq!(table.sin(-QUART_CIRC_F64), -1.0);
269        assert_eq!(table.sin(-HALF_CIRC_F64), 0.0);
270        assert_eq!(table.sin(-HALF_CIRC_F64 - QUART_CIRC_F64), 1.0);
271        assert_eq!(table.sin(-FULL_CIRC_F64), 0.0);
272    }
273}