trig_const/
lib.rs

1//! ## trig-const
2//!
3//! Rust implementation of const trig functions.
4//!
5//! This is implemented using a 16-term Taylor series approximation of cosine.
6//! Correctness is favored over speed, especially considering the main use case for
7//! this crate is to expose trigonometric functions for compile time.
8//!
9//! The implemntation was largely inspired by the work of Dr. Austin Henley and Dr. Stephen Marz:
10//!   - GitHub Repo: <https://github.com/AZHenley/cosine>
11//!   - Article: <https://austinhenley.com/blog/cosine.html>
12//!
13//! The implementation carries forward the original MIT license contained in the GitHub repo above.
14//!
15//! ## Requirements
16//!
17//! This crate supports any compiler version back to rustc 1.85
18//!
19//! ```toml
20//! [dependencies]
21//! trig-const = "0"
22//! ```
23//!
24//! ## Example
25//!
26//! ```
27//! # use trig_const::cos;
28//! # use core::f64::consts::PI;
29//! # fn float_eq(lhs: f64, rhs: f64) { assert!((lhs - rhs).abs() < 0.0001, "lhs: {}, rhs: {}", lhs, rhs); }
30//! const COS_PI: f64 = cos(PI);
31//! float_eq(COS_PI, -1.0);
32//! ```
33
34#![no_std]
35#![forbid(unsafe_code)]
36
37use core::f64::{self, consts::PI};
38const TAYLOR_SERIES_SUMS: usize = 16;
39
40/// Cosine
41///
42/// ```
43/// # use trig_const::cos;
44/// # use core::f64::consts::PI;
45/// # fn float_eq(lhs: f64, rhs: f64) { assert!((lhs - rhs).abs() < 0.0001, "lhs: {}, rhs: {}", lhs, rhs); }
46/// const COS_PI: f64 = cos(PI);
47/// float_eq(COS_PI, -1.0);
48/// ```
49pub const fn cos(mut x: f64) -> f64 {
50    // If value is large, fold into smaller value
51    while x < -0.1 {
52        x += 2.0 * PI;
53    }
54    while x > 2.0 * PI + 0.1 {
55        x -= 2.0 * PI;
56    }
57    let div = (x / PI) as u32;
58    x -= div as f64 * PI;
59    let sign = if div % 2 != 0 { -1.0 } else { 1.0 };
60
61    let mut result = 1.0;
62    let mut inter = 1.0;
63    let num = x * x;
64
65    let mut i = 1;
66    while i <= TAYLOR_SERIES_SUMS {
67        let comp = 2.0 * i as f64;
68        let den = comp * (comp - 1.0);
69        inter *= num / den;
70        if i % 2 == 0 {
71            result += inter;
72        } else {
73            result -= inter;
74        }
75        i += 1;
76    }
77
78    sign * result
79}
80
81/// Sine
82///
83/// ```
84/// # use trig_const::sin;
85/// # use core::f64::consts::PI;
86/// # fn float_eq(lhs: f64, rhs: f64) { assert!((lhs - rhs).abs() < 0.0001, "lhs: {}, rhs: {}", lhs, rhs); }
87/// const SIN_PI: f64 = sin(PI);
88/// float_eq(SIN_PI, 0.0);
89/// ```
90pub const fn sin(x: f64) -> f64 {
91    cos(x - PI / 2.0)
92}
93
94/// Tangent
95///
96/// ```
97/// # use trig_const::tan;
98/// # use core::f64::consts::PI;
99/// # fn float_eq(lhs: f64, rhs: f64) { assert!((lhs - rhs).abs() < 0.0001, "lhs: {}, rhs: {}", lhs, rhs); }
100/// const TAN_PI_4: f64 = tan(PI / 4.0);
101/// float_eq(TAN_PI_4, 1.0);
102/// ```
103pub const fn tan(x: f64) -> f64 {
104    sin(x) / cos(x)
105}
106
107/// Cotangent
108///
109/// ```
110/// # use trig_const::cot;
111/// # use core::f64::consts::PI;
112/// # fn float_eq(lhs: f64, rhs: f64) { assert!((lhs - rhs).abs() < 0.0001, "lhs: {}, rhs: {}", lhs, rhs); }
113/// const COT_PI_4: f64 = cot(PI / 4.0);
114/// float_eq(COT_PI_4, 1.0);
115/// ```
116pub const fn cot(x: f64) -> f64 {
117    let sin_calc = sin(x);
118    if sin_calc == 0.0 {
119        f64::INFINITY
120    } else {
121        cos(x) / sin_calc
122    }
123}
124
125/// Secant
126///
127/// ```
128/// # use trig_const::sec;
129/// # use core::f64::consts::PI;
130/// # fn float_eq(lhs: f64, rhs: f64) { assert!((lhs - rhs).abs() < 0.0001, "lhs: {}, rhs: {}", lhs, rhs); }
131/// const SEC_PI: f64 = sec(PI);
132/// float_eq(SEC_PI, -1.0);
133/// ```
134pub const fn sec(x: f64) -> f64 {
135    let cos_calc = cos(x);
136    if cos_calc == 0.0 {
137        f64::INFINITY
138    } else {
139        1.0 / cos_calc
140    }
141}
142
143/// Cosecant
144///
145/// ```
146/// # use trig_const::csc;
147/// # use core::f64::consts::PI;
148/// # fn float_eq(lhs: f64, rhs: f64) { assert!((lhs - rhs).abs() < 0.0001, "lhs: {}, rhs: {}", lhs, rhs); }
149/// const CSC_PI_2: f64 = csc(PI / 2.0);
150/// float_eq(CSC_PI_2, 1.0);
151/// ```
152pub const fn csc(x: f64) -> f64 {
153    let sin_calc = sin(x);
154    if sin_calc == 0.0 {
155        f64::INFINITY
156    } else {
157        1.0 / sin_calc
158    }
159}
160
161/// Hyperbolic Sine
162///
163/// ```
164/// # use trig_const::sinh;
165/// const SINH_0: f64 = sinh(0.0);
166/// assert_eq!(SINH_0, 0.0);
167/// ```
168pub const fn sinh(x: f64) -> f64 {
169    (exp(x) - exp(-x)) / 2.0
170}
171
172/// Hyperbolic Cosine
173///
174/// ```
175/// # use trig_const::cosh;
176/// const COSH_0: f64 = cosh(0.0);
177/// assert_eq!(COSH_0, 1.0);
178/// ```
179pub const fn cosh(x: f64) -> f64 {
180    (exp(x) + exp(-x)) / 2.0
181}
182
183/// Arcsine
184///
185/// ```
186/// # use trig_const::asin;
187/// const ASIN_PI: f64 = asin(0.0);
188/// assert_eq!(ASIN_PI, 0.0);
189/// ```
190pub const fn asin(x: f64) -> f64 {
191    if x.is_infinite() {
192        return f64::NAN;
193    }
194    if x.abs() > 1.0 {
195        return f64::NAN;
196    }
197    if x == 1.0 {
198        return f64::consts::FRAC_PI_2;
199    }
200    if x == -1.0 {
201        return -f64::consts::FRAC_PI_2;
202    }
203
204    // As we start to get past 0.8, the number of summations needed for an accurate
205    // Taylor series approximation starts to get unweidy. We can use the property
206    // that arcsin(x) = pi/2 - 2*arcsin(sqrt((1 - x) / 2)) to reduce
207    const RANGE_REDUCTION_THRESHOLD: f64 = 0.5;
208    if x.abs() > RANGE_REDUCTION_THRESHOLD {
209        let sign = x.signum();
210        let abs_x = x.abs();
211
212        let y = sqrt((1.0 - abs_x) / 2.0);
213        return sign * (f64::consts::FRAC_PI_2 - 2.0 * asin(y));
214    }
215
216    let mut n = 1;
217    let mut s = x;
218
219    while n < TAYLOR_SERIES_SUMS {
220        let numer1 = factorial(2.0 * n as f64);
221        let numer2 = expi(x, 2 * n + 1);
222
223        // Calculate all denom terms;
224        let denom1 = expi(4.0, n);
225        let denom2 = factorial(n as f64) * factorial(n as f64);
226        let denom3 = 2.0 * n as f64 + 1.0;
227
228        // Try to match terms to divide to stop number getting too large
229        let f1 = numer1 / denom2;
230        let f2 = numer2 / denom1;
231
232        s += f1 * f2 / denom3;
233
234        n += 1;
235    }
236
237    s
238}
239
240/// Arccosine
241///
242/// ```
243/// # use trig_const::acos;
244/// # use core::f64::consts::PI;
245/// const ACOS_1: f64 = acos(1.0);
246/// assert_eq!(ACOS_1, 0.0);
247/// ```
248pub const fn acos(x: f64) -> f64 {
249    if x.is_infinite() {
250        return f64::NAN;
251    }
252    if x.abs() > 1.0 {
253        return f64::NAN;
254    }
255
256    f64::consts::FRAC_PI_2 - asin(x)
257}
258
259/// e^x
260const fn exp(x: f64) -> f64 {
261    let mut i = 1;
262    let mut s = 1.0;
263
264    while i < 16 {
265        s += expi(x, i) / factorial(i as f64);
266        i += 1;
267    }
268
269    s
270}
271
272/// x^pow
273const fn expi(x: f64, mut pow: usize) -> f64 {
274    let mut o = 1.0;
275
276    while pow > 0 {
277        o *= x;
278        pow -= 1;
279    }
280
281    o
282}
283
284/// Factorial (x!)
285const fn factorial(mut x: f64) -> f64 {
286    if x == 0.0 || x == 1.0 {
287        1.0
288    } else {
289        let mut s = 1.0;
290        while x > 1.0 {
291            s *= x;
292            x -= 1.0;
293        }
294        s
295    }
296}
297
298/// Const sqrt function using Newton's method
299const fn sqrt(x: f64) -> f64 {
300    if x.is_nan() || x < 0.0 {
301        return f64::NAN;
302    }
303    if x.is_infinite() {
304        return x;
305    }
306    if x == 0.0 {
307        return x;
308    }
309
310    // Use Newton's method for sqrt calculation
311    let mut current_guess = 1.0;
312
313    let mut i = 0;
314    while i < TAYLOR_SERIES_SUMS {
315        current_guess = 0.5 * (current_guess + x / current_guess);
316        i += 1;
317    }
318
319    current_guess
320}
321
322#[cfg(test)]
323mod tests {
324    use core::f64::consts::{E, PI};
325
326    use crate::{cos, cosh, exp, expi, factorial, sin, sinh, sqrt};
327
328    macro_rules! float_eq {
329        ($lhs:expr, $rhs:expr) => {
330            assert!(($lhs - $rhs).abs() < 0.0001, "lhs: {}, rhs: {}", $lhs, $rhs);
331        };
332    }
333
334    #[test]
335    fn test_factorial() {
336        assert_eq!(factorial(0.0), 1.0);
337        assert_eq!(factorial(1.0), 1.0);
338        assert_eq!(factorial(2.0), 2.0);
339        assert_eq!(factorial(3.0), 6.0);
340        assert_eq!(factorial(4.0), 24.0);
341        assert_eq!(factorial(5.0), 120.0);
342    }
343
344    #[test]
345    fn test_expi() {
346        assert_eq!(expi(2.0, 0), 1.0);
347        assert_eq!(expi(2.0, 4), 16.0);
348        assert_eq!(expi(2.0, 5), 32.0);
349        assert_eq!(expi(3.0, 3), 27.0);
350    }
351
352    #[test]
353    fn test_exp() {
354        float_eq!(exp(0.0), 1.0);
355        float_eq!(exp(1.0), E);
356    }
357
358    #[test]
359    fn test_sqrt() {
360        float_eq!(sqrt(4.0), 2.0);
361        float_eq!(sqrt(9.0), 3.0);
362        float_eq!(sqrt(16.0), 4.0);
363        float_eq!(sqrt(25.0), 5.0);
364    }
365
366    #[test]
367    fn test_cos() {
368        float_eq!(cos(0.0), 0.0_f64.cos());
369        float_eq!(cos(1.0), 1.0_f64.cos());
370        float_eq!(cos(PI), PI.cos());
371        float_eq!(cos(PI * 8.0), (PI * 8.0).cos());
372    }
373
374    #[test]
375    fn test_sin() {
376        float_eq!(sin(0.0), 0.0_f64.sin());
377        float_eq!(sin(1.0), 1.0_f64.sin());
378        float_eq!(sin(PI), PI.sin());
379        float_eq!(sin(PI * 8.0), (PI * 8.0).sin());
380    }
381
382    #[test]
383    fn test_sinh() {
384        for x in [0.0, 0.5, 1.0, 1.5, 2.0, 2.5] {
385            float_eq!(sinh(x), x.sinh());
386        }
387    }
388
389    #[test]
390    fn test_cosh() {
391        for x in [0.0, 0.5, 1.0, 1.5, 2.0, 2.5] {
392            float_eq!(cosh(x), x.cosh());
393        }
394    }
395}