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};
38
39/// Number of sum iterations for Taylor series
40const TAYLOR_SERIES_SUMS: usize = 16;
41/// Number of sum iterations for ln
42const LN_SUM_TERMS: f64 = 101.0;
43
44/// Cosine
45///
46/// ```
47/// # use trig_const::cos;
48/// # use core::f64::consts::PI;
49/// # fn float_eq(lhs: f64, rhs: f64) { assert!((lhs - rhs).abs() < 0.0001, "lhs: {}, rhs: {}", lhs, rhs); }
50/// const COS_PI: f64 = cos(PI);
51/// float_eq(COS_PI, -1.0);
52/// ```
53pub const fn cos(mut x: f64) -> f64 {
54    // If value is large, fold into smaller value
55    while x < -0.1 {
56        x += 2.0 * PI;
57    }
58    while x > 2.0 * PI + 0.1 {
59        x -= 2.0 * PI;
60    }
61    let div = (x / PI) as u32;
62    x -= div as f64 * PI;
63    let sign = if div % 2 != 0 { -1.0 } else { 1.0 };
64
65    let mut result = 1.0;
66    let mut inter = 1.0;
67    let num = x * x;
68
69    let mut i = 1;
70    while i <= TAYLOR_SERIES_SUMS {
71        let comp = 2.0 * i as f64;
72        let den = comp * (comp - 1.0);
73        inter *= num / den;
74        if i % 2 == 0 {
75            result += inter;
76        } else {
77            result -= inter;
78        }
79        i += 1;
80    }
81
82    sign * result
83}
84
85/// Sine
86///
87/// ```
88/// # use trig_const::sin;
89/// # use core::f64::consts::PI;
90/// # fn float_eq(lhs: f64, rhs: f64) { assert!((lhs - rhs).abs() < 0.0001, "lhs: {}, rhs: {}", lhs, rhs); }
91/// const SIN_PI: f64 = sin(PI);
92/// float_eq(SIN_PI, 0.0);
93/// ```
94pub const fn sin(x: f64) -> f64 {
95    cos(x - PI / 2.0)
96}
97
98/// Tangent
99///
100/// ```
101/// # use trig_const::tan;
102/// # use core::f64::consts::PI;
103/// # fn float_eq(lhs: f64, rhs: f64) { assert!((lhs - rhs).abs() < 0.0001, "lhs: {}, rhs: {}", lhs, rhs); }
104/// const TAN_PI_4: f64 = tan(PI / 4.0);
105/// float_eq(TAN_PI_4, 1.0);
106/// ```
107pub const fn tan(x: f64) -> f64 {
108    sin(x) / cos(x)
109}
110
111/// Cotangent
112///
113/// ```
114/// # use trig_const::cot;
115/// # use core::f64::consts::PI;
116/// # fn float_eq(lhs: f64, rhs: f64) { assert!((lhs - rhs).abs() < 0.0001, "lhs: {}, rhs: {}", lhs, rhs); }
117/// const COT_PI_4: f64 = cot(PI / 4.0);
118/// float_eq(COT_PI_4, 1.0);
119/// ```
120pub const fn cot(x: f64) -> f64 {
121    let sin_calc = sin(x);
122    if sin_calc == 0.0 {
123        f64::INFINITY
124    } else {
125        cos(x) / sin_calc
126    }
127}
128
129/// Secant
130///
131/// ```
132/// # use trig_const::sec;
133/// # use core::f64::consts::PI;
134/// # fn float_eq(lhs: f64, rhs: f64) { assert!((lhs - rhs).abs() < 0.0001, "lhs: {}, rhs: {}", lhs, rhs); }
135/// const SEC_PI: f64 = sec(PI);
136/// float_eq(SEC_PI, -1.0);
137/// ```
138pub const fn sec(x: f64) -> f64 {
139    let cos_calc = cos(x);
140    if cos_calc == 0.0 {
141        f64::INFINITY
142    } else {
143        1.0 / cos_calc
144    }
145}
146
147/// Cosecant
148///
149/// ```
150/// # use trig_const::csc;
151/// # use core::f64::consts::PI;
152/// # fn float_eq(lhs: f64, rhs: f64) { assert!((lhs - rhs).abs() < 0.0001, "lhs: {}, rhs: {}", lhs, rhs); }
153/// const CSC_PI_2: f64 = csc(PI / 2.0);
154/// float_eq(CSC_PI_2, 1.0);
155/// ```
156pub const fn csc(x: f64) -> f64 {
157    let sin_calc = sin(x);
158    if sin_calc == 0.0 {
159        f64::INFINITY
160    } else {
161        1.0 / sin_calc
162    }
163}
164
165/// Hyperbolic Sine
166///
167/// ```
168/// # use trig_const::sinh;
169/// const SINH_0: f64 = sinh(0.0);
170/// assert_eq!(SINH_0, 0.0);
171/// ```
172pub const fn sinh(x: f64) -> f64 {
173    (exp(x) - exp(-x)) / 2.0
174}
175
176/// Hyperbolic Cosine
177///
178/// ```
179/// # use trig_const::cosh;
180/// const COSH_0: f64 = cosh(0.0);
181/// assert_eq!(COSH_0, 1.0);
182/// ```
183pub const fn cosh(x: f64) -> f64 {
184    (exp(x) + exp(-x)) / 2.0
185}
186
187/// Arcsine
188///
189/// ```
190/// # use trig_const::asin;
191/// const ASIN_PI: f64 = asin(0.0);
192/// assert_eq!(ASIN_PI, 0.0);
193/// ```
194pub const fn asin(x: f64) -> f64 {
195    if x.is_infinite() || x.abs() > 1.0 {
196        return f64::NAN;
197    } else if x == 1.0 {
198        return f64::consts::FRAC_PI_2;
199    } else if x == -1.0 {
200        return -f64::consts::FRAC_PI_2;
201    }
202
203    // As we start to get past 0.8, the number of summations needed for an accurate
204    // Taylor series approximation starts to get unweidy. We can use the property
205    // that arcsin(x) = pi/2 - 2*arcsin(sqrt((1 - x) / 2)) to reduce
206    const RANGE_REDUCTION_THRESHOLD: f64 = 0.5;
207    if x.abs() > RANGE_REDUCTION_THRESHOLD {
208        let sign = x.signum();
209        let abs_x = x.abs();
210
211        let y = sqrt((1.0 - abs_x) / 2.0);
212        return sign * (f64::consts::FRAC_PI_2 - 2.0 * asin(y));
213    }
214
215    let mut n = 1;
216    let mut s = x;
217
218    while n < TAYLOR_SERIES_SUMS {
219        let numer1 = factorial(2.0 * n as f64);
220        let numer2 = expi(x, 2 * n + 1);
221
222        // Calculate all denom terms;
223        let denom1 = expi(4.0, n);
224        let denom2 = factorial(n as f64) * factorial(n as f64);
225        let denom3 = 2.0 * n as f64 + 1.0;
226
227        // Try to match terms to divide to stop number getting too large
228        let f1 = numer1 / denom2;
229        let f2 = numer2 / denom1;
230
231        s += f1 * f2 / denom3;
232
233        n += 1;
234    }
235
236    s
237}
238
239/// Arccosine
240///
241/// ```
242/// # use trig_const::acos;
243/// # use core::f64::consts::PI;
244/// const ACOS_1: f64 = acos(1.0);
245/// assert_eq!(ACOS_1, 0.0);
246/// ```
247pub const fn acos(x: f64) -> f64 {
248    if x.is_infinite() || x.abs() > 1.0 {
249        f64::NAN
250    } else {
251        f64::consts::FRAC_PI_2 - asin(x)
252    }
253}
254
255/// Inverse hyperbolic sine
256///
257/// ```
258/// # use trig_const::asinh;
259/// const ASINH_1: f64 = asinh(0.0);
260/// assert_eq!(ASINH_1, 0.0);
261/// ```
262pub const fn asinh(x: f64) -> f64 {
263    if x.is_nan() {
264        f64::NAN
265    } else if x.is_infinite() {
266        x
267    } else {
268        ln(x + sqrt(x * x + 1.0))
269    }
270}
271
272/// Inverse hyperbolic cosine
273///
274/// ```
275/// # use trig_const::acosh;
276/// const ACOSH_1: f64 = acosh(1.0);
277/// assert_eq!(ACOSH_1, 0.0);
278/// ```
279pub const fn acosh(x: f64) -> f64 {
280    if x.is_nan() {
281        f64::NAN
282    } else if x.is_infinite() {
283        x
284    } else if x < 1.0 {
285        f64::NAN
286    } else {
287        ln(x + sqrt(x * x - 1.0))
288    }
289}
290
291/// e^x
292const fn exp(x: f64) -> f64 {
293    let mut i = 1;
294    let mut s = 1.0;
295
296    while i < 16 {
297        s += expi(x, i) / factorial(i as f64);
298        i += 1;
299    }
300
301    s
302}
303
304/// x^pow
305const fn expi(x: f64, mut pow: usize) -> f64 {
306    let mut o = 1.0;
307
308    while pow > 0 {
309        o *= x;
310        pow -= 1;
311    }
312
313    o
314}
315
316/// Factorial (x!)
317const fn factorial(mut x: f64) -> f64 {
318    if x == 0.0 || x == 1.0 {
319        1.0
320    } else {
321        let mut s = 1.0;
322        while x > 1.0 {
323            s *= x;
324            x -= 1.0;
325        }
326        s
327    }
328}
329
330/// Const sqrt function using Newton's method
331const fn sqrt(x: f64) -> f64 {
332    if x.is_nan() || x < 0.0 {
333        return f64::NAN;
334    } else if x.is_infinite() || x == 0.0 {
335        return x;
336    }
337
338    // Use Newton's method for sqrt calculation
339    let mut current_guess = 1.0;
340
341    let mut i = 0;
342    while i < TAYLOR_SERIES_SUMS {
343        current_guess = 0.5 * (current_guess + x / current_guess);
344        i += 1;
345    }
346
347    current_guess
348}
349
350/// Computes natural log using Taylor series approximation
351const fn ln(x: f64) -> f64 {
352    if x.is_nan() || x < 0.0 {
353        return f64::NAN;
354    } else if x == 0.0 {
355        return f64::NEG_INFINITY;
356    } else if x == 1.0 {
357        return 0.0;
358    } else if x.is_infinite() {
359        return f64::INFINITY;
360    }
361
362    // Put into form ln(x) = ln(a * 2^k) = ln(a) + k * ln(2)
363
364    let mut a = x;
365    let mut k = 0;
366
367    // Normalize `a` to [1.0, 2.0)
368    while a >= 2.0 {
369        a /= 2.0;
370        k += 1;
371    }
372    while a < 1.0 {
373        a *= 2.0;
374        k -= 1;
375    }
376
377    let x = a - 1.0;
378
379    let mut s = 0.0;
380    let mut term = x;
381    let mut n = 1.0;
382
383    while n < LN_SUM_TERMS {
384        s += term;
385        n += 1.0;
386        term = -term * x * (n - 1.0) / n;
387    }
388
389    s + (k as f64) * f64::consts::LN_2
390}
391
392#[cfg(test)]
393mod tests {
394    use core::f64::consts::{E, PI};
395
396    use crate::{cos, cosh, exp, expi, factorial, ln, sin, sinh, sqrt};
397
398    macro_rules! float_eq {
399        ($lhs:expr, $rhs:expr) => {
400            assert!(($lhs - $rhs).abs() < 0.0001, "lhs: {}, rhs: {}", $lhs, $rhs);
401        };
402    }
403
404    #[test]
405    fn test_factorial() {
406        assert_eq!(factorial(0.0), 1.0);
407        assert_eq!(factorial(1.0), 1.0);
408        assert_eq!(factorial(2.0), 2.0);
409        assert_eq!(factorial(3.0), 6.0);
410        assert_eq!(factorial(4.0), 24.0);
411        assert_eq!(factorial(5.0), 120.0);
412    }
413
414    #[test]
415    fn test_expi() {
416        assert_eq!(expi(2.0, 0), 1.0);
417        assert_eq!(expi(2.0, 4), 16.0);
418        assert_eq!(expi(2.0, 5), 32.0);
419        assert_eq!(expi(3.0, 3), 27.0);
420    }
421
422    #[test]
423    fn test_exp() {
424        float_eq!(exp(0.0), 1.0);
425        float_eq!(exp(1.0), E);
426    }
427
428    #[test]
429    fn test_sqrt() {
430        float_eq!(sqrt(0.0), 0.0);
431        float_eq!(sqrt(1.0), 1.0);
432        float_eq!(sqrt(4.0), 2.0);
433        float_eq!(sqrt(9.0), 3.0);
434        float_eq!(sqrt(16.0), 4.0);
435        float_eq!(sqrt(25.0), 5.0);
436    }
437
438    #[test]
439    fn test_cos() {
440        float_eq!(cos(0.0), 0.0_f64.cos());
441        float_eq!(cos(1.0), 1.0_f64.cos());
442        float_eq!(cos(PI), PI.cos());
443        float_eq!(cos(PI * 8.0), (PI * 8.0).cos());
444    }
445
446    #[test]
447    fn test_sin() {
448        float_eq!(sin(0.0), 0.0_f64.sin());
449        float_eq!(sin(1.0), 1.0_f64.sin());
450        float_eq!(sin(PI), PI.sin());
451        float_eq!(sin(PI * 8.0), (PI * 8.0).sin());
452    }
453
454    #[test]
455    fn test_sinh() {
456        for x in [0.0, 0.5, 1.0, 1.5, 2.0, 2.5] {
457            float_eq!(sinh(x), x.sinh());
458        }
459    }
460
461    #[test]
462    fn test_cosh() {
463        for x in [0.0, 0.5, 1.0, 1.5, 2.0, 2.5] {
464            float_eq!(cosh(x), x.cosh());
465        }
466    }
467
468    #[test]
469    fn test_ln() {
470        float_eq!(ln(0.01), 0.01_f64.ln());
471        float_eq!(ln(0.5), 0.5_f64.ln());
472        float_eq!(ln(1.0), 1.0_f64.ln());
473        float_eq!(ln(2.0), 2.0_f64.ln());
474        float_eq!(ln(10.0), 10.0_f64.ln());
475        float_eq!(ln(1_000.0), 1_000.0_f64.ln());
476    }
477}