Skip to main content

dotloom_geometry/
math.rs

1//! Deterministic elementary functions.
2//!
3//! `f64::sin` and friends call the platform's math library, whose results differ in
4//! the last bit between Windows, Linux, macOS and WebAssembly. Everything that feeds
5//! stored geometry, solver iterations, exported files or the scene goes through these
6//! wrappers (the pure-Rust `libm`), so the same input gives the same bits on every
7//! platform (DL-TEST-6). `sqrt` is correctly rounded everywhere and stays `f64::sqrt`.
8
9/// Sine.
10#[inline]
11#[must_use]
12pub fn sin(x: f64) -> f64 {
13    libm::sin(x)
14}
15
16/// Cosine.
17#[inline]
18#[must_use]
19pub fn cos(x: f64) -> f64 {
20    libm::cos(x)
21}
22
23/// `(sin x, cos x)`.
24#[inline]
25#[must_use]
26pub fn sin_cos(x: f64) -> (f64, f64) {
27    libm::sincos(x)
28}
29
30/// Tangent.
31#[inline]
32#[must_use]
33pub fn tan(x: f64) -> f64 {
34    libm::tan(x)
35}
36
37/// Arctangent.
38#[inline]
39#[must_use]
40pub fn atan(x: f64) -> f64 {
41    libm::atan(x)
42}
43
44/// Four-quadrant arctangent of `y / x`.
45#[inline]
46#[must_use]
47pub fn atan2(y: f64, x: f64) -> f64 {
48    libm::atan2(y, x)
49}
50
51/// Arccosine.
52#[inline]
53#[must_use]
54pub fn acos(x: f64) -> f64 {
55    libm::acos(x)
56}
57
58/// `sqrt(x² + y²)` without undue overflow.
59#[inline]
60#[must_use]
61pub fn hypot(x: f64, y: f64) -> f64 {
62    libm::hypot(x, y)
63}
64
65/// Base-2 logarithm.
66#[inline]
67#[must_use]
68pub fn log2(x: f64) -> f64 {
69    libm::log2(x)
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    #[allow(clippy::disallowed_methods)] // compares against the platform functions on purpose
78    fn wrappers_agree_with_std_within_two_ulps() {
79        for i in -200..200 {
80            let x = f64::from(i) * 0.0731;
81            let close = |a: f64, b: f64| (a - b).abs() <= 2.0 * f64::EPSILON * a.abs().max(b.abs()).max(1.0);
82            assert!(close(sin(x), x.sin()));
83            assert!(close(cos(x), x.cos()));
84            assert!(close(atan2(x, 1.3), x.atan2(1.3)));
85            assert!(close(hypot(x, 2.0), x.hypot(2.0)));
86            assert_eq!(sin_cos(x), (sin(x), cos(x)));
87        }
88    }
89}