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