Skip to main content

graphviz_pure_core/math/
mod.rs

1//! Transcendental functions, called as `math::hypot(x, y)` the way the C sources call
2//! `hypot(x, y)`.
3//!
4//! On macOS, `f64::hypot` and friends call the system libm, the same one C Graphviz uses, so
5//! the port reproduces C's last bits. On other targets Rust takes them from a different libm
6//! (glibc on Linux, compiler-builtins' musl port on wasm32) that disagrees in the last ulp on
7//! some inputs, which is enough to change a force-directed layout. Every call in the port goes
8//! through this module, and on those targets it uses [`portable`] implementations: a
9//! reproduction of Apple's `hypot`, and the `libm` crate (the musl port Rust itself uses on
10//! wasm32) for the rest, so that every non-Apple target computes the same bits. See `docs/porting/libm.md`.
11//!
12//! `sqrt` and `mul_add` are correctly rounded everywhere and stay `f64` methods.
13
14/// The pure-Rust functions behind the portable profile, exposed for `graphviz-pure-portable-libm`.
15#[doc(hidden)]
16pub mod portable;
17#[cfg(feature = "math-trace")]
18mod trace;
19#[cfg(not(feature = "math-trace"))]
20mod trace {
21    #[inline(always)]
22    pub fn log(_name: u8, _x: f64, _y: f64) {}
23}
24
25/// Whether this build uses [`portable`] rather than the platform libm. The `portable-math`
26/// feature forces it on macOS, to check the portable code against the corpus natively.
27pub const PORTABLE: bool = cfg!(any(not(target_vendor = "apple"), feature = "portable-math"));
28
29/// Which libm this build computes with. The corpus ratchets keep one baseline per profile:
30/// `Apple` results are C's bit for bit, `Portable` ones document the gap to C (see
31/// `docs/porting/libm.md`).
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum Profile {
34    /// The system libm on Apple targets, the one C Graphviz uses there.
35    Apple,
36    /// [`portable`]: identical on every target (Linux, wasm32, macOS with `portable-math`).
37    Portable,
38}
39
40impl Profile {
41    /// Lower-case name, as used in baseline file names (`baseline.portable.json`).
42    #[must_use]
43    pub const fn name(self) -> &'static str {
44        match self {
45            Profile::Apple => "apple",
46            Profile::Portable => "portable",
47        }
48    }
49}
50
51/// The math profile of this build.
52pub const PROFILE: Profile = if PORTABLE {
53    Profile::Portable
54} else {
55    Profile::Apple
56};
57
58/// C `hypot(x, y)`.
59#[inline]
60#[must_use]
61pub fn hypot(x: f64, y: f64) -> f64 {
62    trace::log(0, x, y);
63    if PORTABLE {
64        portable::hypot(x, y)
65    } else {
66        x.hypot(y)
67    }
68}
69
70/// C `pow(x, y)`.
71#[inline]
72#[must_use]
73pub fn powf(x: f64, y: f64) -> f64 {
74    trace::log(1, x, y);
75    if PORTABLE {
76        portable::pow(x, y)
77    } else {
78        x.powf(y)
79    }
80}
81
82/// C `atan2(y, x)` is `math::atan2(y, x)`.
83#[inline]
84#[must_use]
85pub fn atan2(y: f64, x: f64) -> f64 {
86    trace::log(2, y, x);
87    if PORTABLE {
88        portable::atan2(y, x)
89    } else {
90        y.atan2(x)
91    }
92}
93
94/// C `sin(x)`.
95#[inline]
96#[must_use]
97pub fn sin(x: f64) -> f64 {
98    trace::log(3, x, 0.0);
99    if PORTABLE { portable::sin(x) } else { x.sin() }
100}
101
102/// C `cos(x)`.
103#[inline]
104#[must_use]
105pub fn cos(x: f64) -> f64 {
106    trace::log(4, x, 0.0);
107    if PORTABLE { portable::cos(x) } else { x.cos() }
108}
109
110/// C `tan(x)`.
111#[inline]
112#[must_use]
113pub fn tan(x: f64) -> f64 {
114    trace::log(5, x, 0.0);
115    if PORTABLE { portable::tan(x) } else { x.tan() }
116}
117
118/// C `asin(x)`.
119#[inline]
120#[must_use]
121pub fn asin(x: f64) -> f64 {
122    trace::log(6, x, 0.0);
123    if PORTABLE {
124        portable::asin(x)
125    } else {
126        x.asin()
127    }
128}
129
130/// C `acos(x)`.
131#[inline]
132#[must_use]
133pub fn acos(x: f64) -> f64 {
134    trace::log(7, x, 0.0);
135    if PORTABLE {
136        portable::acos(x)
137    } else {
138        x.acos()
139    }
140}
141
142/// C `exp(x)`.
143#[inline]
144#[must_use]
145pub fn exp(x: f64) -> f64 {
146    trace::log(8, x, 0.0);
147    if PORTABLE { portable::exp(x) } else { x.exp() }
148}
149
150/// C `log(x)`.
151#[inline]
152#[must_use]
153pub fn ln(x: f64) -> f64 {
154    trace::log(9, x, 0.0);
155    if PORTABLE { portable::log(x) } else { x.ln() }
156}
157
158/// C `log2(x)`.
159#[inline]
160#[must_use]
161pub fn log2(x: f64) -> f64 {
162    trace::log(10, x, 0.0);
163    if PORTABLE {
164        portable::log2(x)
165    } else {
166        x.log2()
167    }
168}
169
170/// C `cbrt(x)`.
171#[inline]
172#[must_use]
173pub fn cbrt(x: f64) -> f64 {
174    trace::log(11, x, 0.0);
175    if PORTABLE {
176        portable::cbrt(x)
177    } else {
178        x.cbrt()
179    }
180}
181
182#[cfg(all(test, target_vendor = "apple"))]
183mod tests;