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}