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::consts::PI;
38
39/// Cosine
40///
41/// ```
42/// # use trig_const::cos;
43/// # use core::f64::consts::PI;
44/// # fn float_eq(lhs: f64, rhs: f64) { assert!((lhs - rhs).abs() < 0.0001, "lhs: {}, rhs: {}", lhs, rhs); }
45/// const COS_PI: f64 = cos(PI);
46/// float_eq(COS_PI, -1.0);
47/// ```
48pub const fn cos(mut x: f64) -> f64 {
49    // If value is large, fold into smaller value
50    while x < -0.1 {
51        x += 2.0 * PI;
52    }
53    while x > 2.0 * PI + 0.1 {
54        x -= 2.0 * PI;
55    }
56    let div = (x / PI) as u32;
57    x -= div as f64 * PI;
58    let sign = if div % 2 != 0 { -1.0 } else { 1.0 };
59
60    let mut result = 1.0;
61    let mut inter = 1.0;
62    let num = x * x;
63
64    let mut i = 1;
65    while i <= 16 {
66        let comp = 2.0 * i as f64;
67        let den = comp * (comp - 1.0);
68        inter *= num / den;
69        if i % 2 == 0 {
70            result += inter;
71        } else {
72            result -= inter;
73        }
74        i += 1;
75    }
76
77    sign * result
78}
79
80/// Sine
81///
82/// ```
83/// # use trig_const::sin;
84/// # use core::f64::consts::PI;
85/// # fn float_eq(lhs: f64, rhs: f64) { assert!((lhs - rhs).abs() < 0.0001, "lhs: {}, rhs: {}", lhs, rhs); }
86/// const SIN_PI: f64 = sin(PI);
87/// float_eq(SIN_PI, 0.0);
88/// ```
89pub const fn sin(x: f64) -> f64 {
90    cos(x - PI / 2.0)
91}
92
93/// Tangent
94///
95/// ```
96/// # use trig_const::tan;
97/// # use core::f64::consts::PI;
98/// # fn float_eq(lhs: f64, rhs: f64) { assert!((lhs - rhs).abs() < 0.0001, "lhs: {}, rhs: {}", lhs, rhs); }
99/// const TAN_PI_4: f64 = tan(PI / 4.0);
100/// float_eq(TAN_PI_4, 1.0);
101/// ```
102pub const fn tan(x: f64) -> f64 {
103    sin(x) / cos(x)
104}
105
106/// Cotangent
107///
108/// ```
109/// # use trig_const::cot;
110/// # use core::f64::consts::PI;
111/// # fn float_eq(lhs: f64, rhs: f64) { assert!((lhs - rhs).abs() < 0.0001, "lhs: {}, rhs: {}", lhs, rhs); }
112/// const COT_PI_4: f64 = cot(PI / 4.0);
113/// float_eq(COT_PI_4, 1.0);
114/// ```
115pub const fn cot(x: f64) -> f64 {
116    cos(x) / sin(x)
117}
118
119/// Secant
120///
121/// ```
122/// # use trig_const::sec;
123/// # use core::f64::consts::PI;
124/// # fn float_eq(lhs: f64, rhs: f64) { assert!((lhs - rhs).abs() < 0.0001, "lhs: {}, rhs: {}", lhs, rhs); }
125/// const SEC_PI: f64 = sec(PI);
126/// float_eq(SEC_PI, -1.0);
127/// ```
128pub const fn sec(x: f64) -> f64 {
129    1.0 / cos(x)
130}
131
132/// Cosecant
133///
134/// ```
135/// # use trig_const::csc;
136/// # use core::f64::consts::PI;
137/// # fn float_eq(lhs: f64, rhs: f64) { assert!((lhs - rhs).abs() < 0.0001, "lhs: {}, rhs: {}", lhs, rhs); }
138/// const CSC_PI_2: f64 = csc(PI / 2.0);
139/// float_eq(CSC_PI_2, 1.0);
140/// ```
141pub const fn csc(x: f64) -> f64 {
142    1.0 / sin(x)
143}
144
145/// Hyperbolic Sine
146///
147/// ```
148/// # use trig_const::sinh;
149/// const SINH_0: f64 = sinh(0.0);
150/// assert_eq!(SINH_0, 0.0);
151/// ```
152pub const fn sinh(x: f64) -> f64 {
153    (exp(x) - exp(-x)) / 2.0
154}
155
156/// Hyperbolic Cosine
157///
158/// ```
159/// # use trig_const::cosh;
160/// const COSH_0: f64 = cosh(0.0);
161/// assert_eq!(COSH_0, 1.0);
162/// ```
163pub const fn cosh(x: f64) -> f64 {
164    (exp(x) + exp(-x)) / 2.0
165}
166
167/// e^x
168const fn exp(x: f64) -> f64 {
169    let mut i = 1;
170    let mut s = 1.0;
171
172    while i < 16 {
173        s += expi(x, i) / factorial(i as f64);
174        i += 1;
175    }
176
177    s
178}
179
180/// x^pow
181const fn expi(x: f64, mut pow: usize) -> f64 {
182    let mut o = 1.0;
183
184    while pow > 0 {
185        o *= x;
186        pow -= 1;
187    }
188
189    o
190}
191
192/// Factorial (x!)
193const fn factorial(mut x: f64) -> f64 {
194    if x == 0.0 {
195        0.0
196    } else {
197        let mut s = 1.0;
198        while x > 1.0 {
199            s *= x;
200            x -= 1.0;
201        }
202        s
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use core::f64::consts::{E, PI};
209
210    use crate::{cos, cosh, exp, expi, factorial, sin, sinh};
211
212    macro_rules! float_eq {
213        ($lhs:expr, $rhs:expr) => {
214            assert!(($lhs - $rhs).abs() < 0.0001, "lhs: {}, rhs: {}", $lhs, $rhs);
215        };
216    }
217
218    #[test]
219    fn test_factorial() {
220        assert_eq!(factorial(0.0), 0.0);
221        assert_eq!(factorial(1.0), 1.0);
222        assert_eq!(factorial(2.0), 2.0);
223        assert_eq!(factorial(3.0), 6.0);
224        assert_eq!(factorial(4.0), 24.0);
225        assert_eq!(factorial(5.0), 120.0);
226    }
227
228    #[test]
229    fn test_expi() {
230        assert_eq!(expi(2.0, 0), 1.0);
231        assert_eq!(expi(2.0, 4), 16.0);
232        assert_eq!(expi(2.0, 5), 32.0);
233        assert_eq!(expi(3.0, 3), 27.0);
234    }
235
236    #[test]
237    fn test_exp() {
238        float_eq!(exp(0.0), 1.0);
239        float_eq!(exp(1.0), E);
240    }
241
242    #[test]
243    fn test_cos() {
244        float_eq!(cos(0.0), 0.0_f64.cos());
245        float_eq!(cos(1.0), 1.0_f64.cos());
246        float_eq!(cos(PI), PI.cos());
247        float_eq!(cos(PI * 8.0), (PI * 8.0).cos());
248    }
249
250    #[test]
251    fn test_sin() {
252        float_eq!(sin(0.0), 0.0_f64.sin());
253        float_eq!(sin(1.0), 1.0_f64.sin());
254        float_eq!(sin(PI), PI.sin());
255        float_eq!(sin(PI * 8.0), (PI * 8.0).sin());
256    }
257
258    #[test]
259    fn test_sinh() {
260        for x in [0.0, 0.5, 1.0, 1.5, 2.0, 2.5] {
261            float_eq!(sinh(x), x.sinh());
262        }
263    }
264
265    #[test]
266    fn test_cosh() {
267        for x in [0.0, 0.5, 1.0, 1.5, 2.0, 2.5] {
268            float_eq!(cosh(x), x.cosh());
269        }
270    }
271}