1#[cfg(feature = "std")]
13mod imp {
14 pub(crate) fn sin(x: f64) -> f64 {
15 x.sin()
16 }
17 pub(crate) fn cos(x: f64) -> f64 {
18 x.cos()
19 }
20 pub(crate) fn asin(x: f64) -> f64 {
21 x.asin()
22 }
23 pub(crate) fn atan2(y: f64, x: f64) -> f64 {
24 y.atan2(x)
25 }
26 pub(crate) fn sqrt(x: f64) -> f64 {
27 x.sqrt()
28 }
29 pub(crate) fn abs(x: f64) -> f64 {
30 x.abs()
31 }
32 pub(crate) fn copysign(magnitude: f64, sign: f64) -> f64 {
33 magnitude.copysign(sign)
34 }
35 pub(crate) fn tan(x: f64) -> f64 {
36 x.tan()
37 }
38 pub(crate) fn atan(x: f64) -> f64 {
39 x.atan()
40 }
41 pub(crate) fn acos(x: f64) -> f64 {
42 x.acos()
43 }
44 pub(crate) fn ln(x: f64) -> f64 {
45 x.ln()
46 }
47 pub(crate) fn exp(x: f64) -> f64 {
48 x.exp()
49 }
50 pub(crate) fn hypot(x: f64, y: f64) -> f64 {
51 x.hypot(y)
52 }
53 pub(crate) fn round(x: f64) -> f64 {
54 x.round()
55 }
56 pub(crate) fn ceil(x: f64) -> f64 {
57 x.ceil()
58 }
59 pub(crate) fn trunc(x: f64) -> f64 {
60 x.trunc()
61 }
62}
63
64#[cfg(all(not(feature = "std"), feature = "libm"))]
65mod imp {
66 pub(crate) fn sin(x: f64) -> f64 {
67 libm::sin(x)
68 }
69 pub(crate) fn cos(x: f64) -> f64 {
70 libm::cos(x)
71 }
72 pub(crate) fn asin(x: f64) -> f64 {
73 libm::asin(x)
74 }
75 pub(crate) fn atan2(y: f64, x: f64) -> f64 {
76 libm::atan2(y, x)
77 }
78 pub(crate) fn sqrt(x: f64) -> f64 {
79 libm::sqrt(x)
80 }
81 pub(crate) fn abs(x: f64) -> f64 {
82 libm::fabs(x)
83 }
84 pub(crate) fn copysign(magnitude: f64, sign: f64) -> f64 {
85 libm::copysign(magnitude, sign)
86 }
87 pub(crate) fn tan(x: f64) -> f64 {
88 libm::tan(x)
89 }
90 pub(crate) fn atan(x: f64) -> f64 {
91 libm::atan(x)
92 }
93 pub(crate) fn acos(x: f64) -> f64 {
94 libm::acos(x)
95 }
96 pub(crate) fn ln(x: f64) -> f64 {
97 libm::log(x)
98 }
99 pub(crate) fn exp(x: f64) -> f64 {
100 libm::exp(x)
101 }
102 pub(crate) fn hypot(x: f64, y: f64) -> f64 {
103 libm::hypot(x, y)
104 }
105 pub(crate) fn round(x: f64) -> f64 {
106 libm::round(x)
107 }
108 pub(crate) fn ceil(x: f64) -> f64 {
109 libm::ceil(x)
110 }
111 pub(crate) fn trunc(x: f64) -> f64 {
112 libm::trunc(x)
113 }
114}
115
116#[cfg(not(any(feature = "std", feature = "libm")))]
117compile_error!(
118 "kinavis-kernel needs floating point math: enable the default `std` feature, \
119 or build with `--no-default-features --features libm` for `no_std` targets"
120);
121
122#[must_use]
128#[inline]
129pub fn sin(x: f64) -> f64 {
130 imp::sin(x)
131}
132
133#[must_use]
135#[inline]
136pub fn cos(x: f64) -> f64 {
137 imp::cos(x)
138}
139
140#[must_use]
142#[inline]
143pub fn tan(x: f64) -> f64 {
144 imp::tan(x)
145}
146
147#[must_use]
149#[inline]
150pub fn asin(x: f64) -> f64 {
151 imp::asin(x)
152}
153
154#[must_use]
156#[inline]
157pub fn acos(x: f64) -> f64 {
158 imp::acos(x)
159}
160
161#[must_use]
163#[inline]
164pub fn atan(x: f64) -> f64 {
165 imp::atan(x)
166}
167
168#[must_use]
173#[inline]
174pub fn atan2(y: f64, x: f64) -> f64 {
175 imp::atan2(y, x)
176}
177
178#[must_use]
180#[inline]
181pub fn sqrt(x: f64) -> f64 {
182 imp::sqrt(x)
183}
184
185#[must_use]
187#[inline]
188pub fn abs(x: f64) -> f64 {
189 imp::abs(x)
190}
191
192#[must_use]
194#[inline]
195pub fn copysign(magnitude: f64, sign: f64) -> f64 {
196 imp::copysign(magnitude, sign)
197}
198
199#[must_use]
201#[inline]
202pub fn ln(x: f64) -> f64 {
203 imp::ln(x)
204}
205
206#[must_use]
208#[inline]
209pub fn exp(x: f64) -> f64 {
210 imp::exp(x)
211}
212
213#[must_use]
215#[inline]
216pub fn hypot(x: f64, y: f64) -> f64 {
217 imp::hypot(x, y)
218}
219
220#[must_use]
222#[inline]
223pub fn round(x: f64) -> f64 {
224 imp::round(x)
225}
226
227#[must_use]
229#[inline]
230pub fn ceil(x: f64) -> f64 {
231 imp::ceil(x)
232}
233
234#[must_use]
236#[inline]
237pub fn trunc(x: f64) -> f64 {
238 imp::trunc(x)
239}
240
241const DEGREES_PER_RADIAN: f64 = 180.0 / core::f64::consts::PI;
243
244#[must_use]
246pub fn to_radians(degrees: f64) -> f64 {
247 degrees / DEGREES_PER_RADIAN
248}
249
250#[must_use]
252pub fn to_degrees(radians: f64) -> f64 {
253 radians * DEGREES_PER_RADIAN
254}
255
256#[allow(clippy::float_cmp)]
261#[must_use]
262pub fn is_integral(value: f64) -> bool {
263 value == trunc(value)
264}
265
266pub const RELATIVE_TOLERANCE: f64 = 1e-12;
271
272#[must_use]
278pub fn is_effectively_zero(value: f64, scale: f64) -> bool {
279 abs(value) <= abs(scale) * RELATIVE_TOLERANCE
280}
281
282#[allow(clippy::cast_possible_truncation)]
285#[must_use]
286pub fn round_to_i32(value: f64) -> i32 {
287 let rounded = round(value);
288 if rounded > f64::from(i32::MAX) || rounded < f64::from(i32::MIN) {
289 return 0;
290 }
291 rounded as i32
292}
293
294#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
297#[must_use]
298pub fn to_usize(value: f64) -> usize {
299 if !(0.0..=1e9).contains(&value) {
301 return 0;
302 }
303 value as usize
304}
305
306#[allow(clippy::cast_precision_loss)]
308#[must_use]
309pub fn count_to_f64(count: usize) -> f64 {
310 count as f64
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316
317 #[test]
318 fn radian_conversion_round_trips() {
319 for degrees in [0.0, 1.0, 45.0, 90.0, 180.0, 359.9] {
320 let back = to_degrees(to_radians(degrees));
321 assert!((back - degrees).abs() < 1e-12);
322 }
323 }
324
325 #[test]
326 fn trig_matches_known_values() {
327 assert!(abs(sin(to_radians(90.0)) - 1.0) < 1e-12);
328 assert!(abs(cos(to_radians(180.0)) + 1.0) < 1e-12);
329 assert!(abs(to_degrees(atan2(1.0, 0.0)) - 90.0) < 1e-12);
330 assert!(abs(hypot(3.0, 4.0) - 5.0) < 1e-12);
331 assert!(abs(sqrt(9.0) - 3.0) < 1e-12);
332 assert!(abs(to_degrees(asin(0.5)) - 30.0) < 1e-12);
333 }
334}