Skip to main content

lox_core/math/
float.rs

1// SPDX-FileCopyrightText: 2026 Helge Eichhorn <git@helgeeichhorn.de>
2//
3// SPDX-License-Identifier: MPL-2.0
4
5//! `f64` math dispatched per build mode: inherent methods under `std`,
6//! [`libm`](https://docs.rs/libm) under no_std. Free-function form (`sqrt(x)` not `x.sqrt()`) so
7//! call sites don't need `cfg`-gated trait imports.
8
9// Rounding helpers
10// ================
11
12/// Largest integer `<= x`.
13#[inline]
14pub fn floor(x: f64) -> f64 {
15    #[cfg(feature = "std")]
16    {
17        x.floor()
18    }
19    #[cfg(not(feature = "std"))]
20    {
21        libm::floor(x)
22    }
23}
24
25/// Rounds to the nearest integer, ties away from zero.
26#[inline]
27pub fn round(x: f64) -> f64 {
28    #[cfg(feature = "std")]
29    {
30        x.round()
31    }
32    #[cfg(not(feature = "std"))]
33    {
34        libm::round(x)
35    }
36}
37
38/// Truncates toward zero.
39#[inline]
40pub fn trunc(x: f64) -> f64 {
41    #[cfg(feature = "std")]
42    {
43        x.trunc()
44    }
45    #[cfg(not(feature = "std"))]
46    {
47        libm::trunc(x)
48    }
49}
50
51/// Returns the fractional part (`x - trunc(x)`).
52#[inline]
53pub fn fract(x: f64) -> f64 {
54    #[cfg(feature = "std")]
55    {
56        x.fract()
57    }
58    #[cfg(not(feature = "std"))]
59    {
60        x - libm::trunc(x)
61    }
62}
63
64// Algebraic
65// =========
66
67/// Absolute value.
68#[inline]
69pub fn abs(x: f64) -> f64 {
70    #[cfg(feature = "std")]
71    {
72        x.abs()
73    }
74    #[cfg(not(feature = "std"))]
75    {
76        libm::fabs(x)
77    }
78}
79
80/// Sign function: `1.0`, `-1.0`, or `NaN`.
81#[inline]
82pub fn signum(x: f64) -> f64 {
83    #[cfg(feature = "std")]
84    {
85        x.signum()
86    }
87    #[cfg(not(feature = "std"))]
88    {
89        if x.is_nan() {
90            f64::NAN
91        } else {
92            libm::copysign(1.0, x)
93        }
94    }
95}
96
97/// Square root.
98#[inline]
99pub fn sqrt(x: f64) -> f64 {
100    #[cfg(feature = "std")]
101    {
102        x.sqrt()
103    }
104    #[cfg(not(feature = "std"))]
105    {
106        libm::sqrt(x)
107    }
108}
109
110/// Cube root.
111#[inline]
112pub fn cbrt(x: f64) -> f64 {
113    #[cfg(feature = "std")]
114    {
115        x.cbrt()
116    }
117    #[cfg(not(feature = "std"))]
118    {
119        libm::cbrt(x)
120    }
121}
122
123/// Raises `x` to an integer power.
124#[inline]
125pub fn powi(x: f64, n: i32) -> f64 {
126    #[cfg(feature = "std")]
127    {
128        x.powi(n)
129    }
130    #[cfg(not(feature = "std"))]
131    {
132        libm::pow(x, n as f64)
133    }
134}
135
136/// Raises `x` to a floating-point power.
137#[inline]
138pub fn powf(x: f64, n: f64) -> f64 {
139    #[cfg(feature = "std")]
140    {
141        x.powf(n)
142    }
143    #[cfg(not(feature = "std"))]
144    {
145        libm::pow(x, n)
146    }
147}
148
149/// Fused multiply-add: `a * b + c` with a single rounding.
150#[inline]
151pub fn mul_add(a: f64, b: f64, c: f64) -> f64 {
152    #[cfg(feature = "std")]
153    {
154        a.mul_add(b, c)
155    }
156    #[cfg(not(feature = "std"))]
157    {
158        libm::fma(a, b, c)
159    }
160}
161
162// Trigonometric
163// =============
164
165/// Sine.
166#[inline]
167pub fn sin(x: f64) -> f64 {
168    #[cfg(feature = "std")]
169    {
170        x.sin()
171    }
172    #[cfg(not(feature = "std"))]
173    {
174        libm::sin(x)
175    }
176}
177
178/// Cosine.
179#[inline]
180pub fn cos(x: f64) -> f64 {
181    #[cfg(feature = "std")]
182    {
183        x.cos()
184    }
185    #[cfg(not(feature = "std"))]
186    {
187        libm::cos(x)
188    }
189}
190
191/// Tangent.
192#[inline]
193pub fn tan(x: f64) -> f64 {
194    #[cfg(feature = "std")]
195    {
196        x.tan()
197    }
198    #[cfg(not(feature = "std"))]
199    {
200        libm::tan(x)
201    }
202}
203
204/// Sine and cosine in a single call.
205#[inline]
206pub fn sin_cos(x: f64) -> (f64, f64) {
207    #[cfg(feature = "std")]
208    {
209        x.sin_cos()
210    }
211    #[cfg(not(feature = "std"))]
212    {
213        libm::sincos(x)
214    }
215}
216
217/// Arcsine.
218#[inline]
219pub fn asin(x: f64) -> f64 {
220    #[cfg(feature = "std")]
221    {
222        x.asin()
223    }
224    #[cfg(not(feature = "std"))]
225    {
226        libm::asin(x)
227    }
228}
229
230/// Arccosine.
231#[inline]
232pub fn acos(x: f64) -> f64 {
233    #[cfg(feature = "std")]
234    {
235        x.acos()
236    }
237    #[cfg(not(feature = "std"))]
238    {
239        libm::acos(x)
240    }
241}
242
243/// Arctangent.
244#[inline]
245pub fn atan(x: f64) -> f64 {
246    #[cfg(feature = "std")]
247    {
248        x.atan()
249    }
250    #[cfg(not(feature = "std"))]
251    {
252        libm::atan(x)
253    }
254}
255
256/// Four-quadrant arctangent of `y/x`.
257#[inline]
258pub fn atan2(y: f64, x: f64) -> f64 {
259    #[cfg(feature = "std")]
260    {
261        y.atan2(x)
262    }
263    #[cfg(not(feature = "std"))]
264    {
265        libm::atan2(y, x)
266    }
267}
268
269// Hyperbolic
270// ==========
271
272/// Hyperbolic sine.
273#[inline]
274pub fn sinh(x: f64) -> f64 {
275    #[cfg(feature = "std")]
276    {
277        x.sinh()
278    }
279    #[cfg(not(feature = "std"))]
280    {
281        libm::sinh(x)
282    }
283}
284
285/// Hyperbolic cosine.
286#[inline]
287pub fn cosh(x: f64) -> f64 {
288    #[cfg(feature = "std")]
289    {
290        x.cosh()
291    }
292    #[cfg(not(feature = "std"))]
293    {
294        libm::cosh(x)
295    }
296}
297
298/// Hyperbolic tangent.
299#[inline]
300pub fn tanh(x: f64) -> f64 {
301    #[cfg(feature = "std")]
302    {
303        x.tanh()
304    }
305    #[cfg(not(feature = "std"))]
306    {
307        libm::tanh(x)
308    }
309}
310
311/// Inverse hyperbolic sine.
312#[inline]
313pub fn asinh(x: f64) -> f64 {
314    #[cfg(feature = "std")]
315    {
316        x.asinh()
317    }
318    #[cfg(not(feature = "std"))]
319    {
320        libm::asinh(x)
321    }
322}
323
324/// Inverse hyperbolic cosine.
325#[inline]
326pub fn acosh(x: f64) -> f64 {
327    #[cfg(feature = "std")]
328    {
329        x.acosh()
330    }
331    #[cfg(not(feature = "std"))]
332    {
333        libm::acosh(x)
334    }
335}
336
337/// Inverse hyperbolic tangent.
338#[inline]
339pub fn atanh(x: f64) -> f64 {
340    #[cfg(feature = "std")]
341    {
342        x.atanh()
343    }
344    #[cfg(not(feature = "std"))]
345    {
346        libm::atanh(x)
347    }
348}
349
350// Exponential and logarithmic
351// ===========================
352
353/// Natural logarithm.
354#[inline]
355pub fn ln(x: f64) -> f64 {
356    #[cfg(feature = "std")]
357    {
358        x.ln()
359    }
360    #[cfg(not(feature = "std"))]
361    {
362        libm::log(x)
363    }
364}
365
366/// Base-10 logarithm.
367#[inline]
368pub fn log10(x: f64) -> f64 {
369    #[cfg(feature = "std")]
370    {
371        x.log10()
372    }
373    #[cfg(not(feature = "std"))]
374    {
375        libm::log10(x)
376    }
377}
378
379// Angle conversion (pure arithmetic, identical in both modes)
380// ===========================================================
381
382/// Converts radians to degrees.
383#[inline]
384pub const fn to_degrees(x: f64) -> f64 {
385    x * (180.0 / core::f64::consts::PI)
386}
387
388/// Converts degrees to radians.
389#[inline]
390pub const fn to_radians(x: f64) -> f64 {
391    x * (core::f64::consts::PI / 180.0)
392}
393
394#[cfg(test)]
395mod tests {
396    use super::*;
397
398    #[test]
399    fn test_sqrt() {
400        assert!(abs(sqrt(2.0) - core::f64::consts::SQRT_2) < 1e-15);
401    }
402
403    #[test]
404    fn test_sin_cos_matches() {
405        let (s, c) = sin_cos(0.5);
406        assert!(abs(s - sin(0.5)) < 1e-15);
407        assert!(abs(c - cos(0.5)) < 1e-15);
408    }
409
410    #[test]
411    fn test_atan2() {
412        assert!(abs(atan2(1.0, 1.0) - core::f64::consts::FRAC_PI_4) < 1e-15);
413    }
414}