Skip to main content

mathverse_core/
precision.rs

1//! Floating-point comparison and rounding utilities.
2//!
3//! This module provides tools for safe floating-point arithmetic, including
4//! multiple comparison strategies (absolute, relative, ULP-based) and
5//! rounding utilities.
6
7use crate::traits::{Real, Transcendental};
8
9/// Machine epsilon for `f64` (2^-52).
10pub const EPS: f64 = 2.220446049250313e-16;
11/// Machine epsilon for `f32` (2^-23).
12pub const F32_EPS: f32 = 1.1920929e-7;
13
14/// Absolute-tolerance equality: `|a - b| <= eps`.
15///
16/// Prefer [`almost_eq_rel`] when operands span many orders of magnitude.
17///
18/// # Examples
19///
20/// ```
21/// use mathverse_core::precision::almost_eq;
22///
23/// assert!(almost_eq(0.1 + 0.2, 0.3, 1e-15));
24/// assert!(!almost_eq(0.1 + 0.2, 0.3, 1e-17));
25/// ```
26#[must_use]
27#[inline]
28pub fn almost_eq<T: Real>(a: T, b: T, eps: T) -> bool {
29    (a - b).abs() <= eps
30}
31
32/// Relative-tolerance equality: `|a - b| <= tol * max(|a|, |b|)`.
33/// Equal when both are zero.
34///
35/// # Examples
36///
37/// ```
38/// use mathverse_core::precision::almost_eq_rel;
39///
40/// assert!(almost_eq_rel(1e15, 1e15 + 1.0, 1e-12));
41/// assert!(almost_eq_rel(0.0, 0.0, 1e-10));
42/// ```
43#[must_use]
44#[inline]
45pub fn almost_eq_rel<T: Real>(a: T, b: T, tol: T) -> bool {
46    let m = a.abs().max(b.abs());
47    m == T::zero() || (a - b).abs() <= tol * m
48}
49
50/// Combined absolute + relative tolerance comparison.
51///
52/// Returns `true` when `|a - b| <= max(abs_tol, rel_tol * max(|a|, |b|))`.
53/// This is the recommended general-purpose float comparison.
54///
55/// # Examples
56///
57/// ```
58/// use mathverse_core::precision::is_close;
59///
60/// assert!(is_close(1.0, 1.0 + 1e-16, 1e-12, 1e-15));
61/// assert!(!is_close(1.0, 2.0, 1e-12, 1e-15));
62/// assert!(is_close(0.0, 0.0, 1e-12, 1e-15));
63/// ```
64#[must_use]
65#[inline]
66pub fn is_close<T: Real>(a: T, b: T, rel_tol: T, abs_tol: T) -> bool {
67    let diff = (a - b).abs();
68    let m = a.abs().max(b.abs());
69    diff <= abs_tol || diff <= rel_tol * m
70}
71
72/// ULP-based floating-point equality for `f64`.
73///
74/// Returns `true` if `a` and `b` differ by at most `max_ulp` representable
75/// values. Handles signed zeros and infinities correctly.
76///
77/// # Examples
78///
79/// ```
80/// use mathverse_core::precision::almost_eq_ulp;
81///
82/// assert!(almost_eq_ulp(1.0, 1.0, 0));
83/// assert!(almost_eq_ulp(f64::INFINITY, f64::INFINITY, 0));
84/// assert!(!almost_eq_ulp(f64::NAN, f64::NAN, 0));
85/// ```
86#[must_use]
87pub fn almost_eq_ulp(a: f64, b: f64, max_ulp: i64) -> bool {
88    if a == b {
89        return true;
90    }
91    if a.is_nan() || b.is_nan() {
92        return false;
93    }
94    if a.is_infinite() || b.is_infinite() {
95        return a == b;
96    }
97    let bits_a = a.to_bits();
98    let bits_b = b.to_bits();
99    let diff = (bits_a as i64 - bits_b as i64).abs();
100    diff <= max_ulp
101}
102
103/// Check if `f64` is subnormal (denormal).
104///
105/// # Examples
106///
107/// ```
108/// use mathverse_core::precision::is_subnormal;
109///
110/// assert!(is_subnormal(f64::MIN_POSITIVE / 2.0));
111/// assert!(!is_subnormal(1.0));
112/// ```
113#[must_use]
114#[inline]
115pub fn is_subnormal(x: f64) -> bool {
116    x.is_finite() && x != 0.0 && x.abs() < f64::MIN_POSITIVE
117}
118
119/// Check if `f64` is NaN.
120///
121/// # Examples
122///
123/// ```
124/// use mathverse_core::precision::is_nan;
125///
126/// assert!(is_nan(f64::NAN));
127/// assert!(!is_nan(1.0));
128/// ```
129#[must_use]
130#[inline]
131pub const fn is_nan(x: f64) -> bool {
132    x.is_nan()
133}
134
135/// Check if `f64` is infinite (positive or negative).
136///
137/// # Examples
138///
139/// ```
140/// use mathverse_core::precision::is_infinite;
141///
142/// assert!(is_infinite(f64::INFINITY));
143/// assert!(!is_infinite(1.0));
144/// ```
145#[must_use]
146#[inline]
147pub const fn is_infinite(x: f64) -> bool {
148    x.is_infinite()
149}
150
151/// Machine epsilon for a generic `Real` type.
152///
153/// Returns the difference between 1.0 and the next representable value.
154///
155/// # Examples
156///
157/// ```
158/// use mathverse_core::precision::{epsilon, EPS, almost_eq};
159///
160/// assert!(almost_eq(epsilon::<f64>(), EPS, 1e-20));
161/// ```
162#[must_use]
163#[inline]
164pub fn epsilon<T: Real>() -> T {
165    T::epsilon()
166}
167
168/// Next representable `f64` toward positive infinity.
169///
170/// # Examples
171///
172/// ```
173/// use mathverse_core::precision::next_float;
174///
175/// let next = next_float(1.0);
176/// assert!(next > 1.0);
177/// ```
178#[must_use]
179pub fn next_float(x: f64) -> f64 {
180    if x.is_nan() {
181        return x;
182    }
183    if x == f64::INFINITY {
184        return f64::INFINITY;
185    }
186    let bits = x.to_bits();
187    if x.is_sign_negative() && x == 0.0 {
188        f64::from_bits(1)
189    } else if x >= 0.0 {
190        f64::from_bits(bits + 1)
191    } else {
192        f64::from_bits(bits - 1)
193    }
194}
195
196/// Next representable `f64` toward negative infinity.
197///
198/// # Examples
199///
200/// ```
201/// use mathverse_core::precision::{next_float, prev_float};
202///
203/// let next = next_float(1.0);
204/// assert_eq!(prev_float(next), 1.0);
205/// ```
206#[must_use]
207pub fn prev_float(x: f64) -> f64 {
208    if x.is_nan() {
209        return x;
210    }
211    if x == f64::NEG_INFINITY {
212        return f64::NEG_INFINITY;
213    }
214    let bits = x.to_bits();
215    if x.is_sign_positive() && x == 0.0 {
216        f64::from_bits(0x8000_0000_0000_0001)
217    } else if x > 0.0 {
218        f64::from_bits(bits - 1)
219    } else {
220        f64::from_bits(bits + 1)
221    }
222}
223
224/// Copy the sign of `b` to the magnitude of `a`.
225///
226/// # Examples
227///
228/// ```
229/// use mathverse_core::precision::copysign;
230///
231/// assert_eq!(copysign(3.0, -1.0), -3.0);
232/// assert_eq!(copysign(3.0, 1.0), 3.0);
233/// ```
234#[must_use]
235#[inline]
236pub fn copysign<T: Real>(a: T, b: T) -> T {
237    if b.is_negative() {
238        -a.abs()
239    } else {
240        a.abs()
241    }
242}
243
244/// Absolute difference: `|a - b|`.
245///
246/// # Examples
247///
248/// ```
249/// use mathverse_core::precision::abs_diff;
250///
251/// assert_eq!(abs_diff(5.0, 3.0), 2.0);
252/// assert_eq!(abs_diff(3.0, 5.0), 2.0);
253/// ```
254#[must_use]
255#[inline]
256pub fn abs_diff<T: Real>(a: T, b: T) -> T {
257    (a - b).abs()
258}
259
260/// Relative difference: `|a - b| / max(|a|, |b|)`.
261///
262/// Returns 0 when both are zero.
263///
264/// # Examples
265///
266/// ```
267/// use mathverse_core::precision::relative_diff;
268///
269/// assert!((relative_diff(10.0_f64, 9.0) - 0.1).abs() < 1e-12);
270/// assert_eq!(relative_diff(0.0, 0.0), 0.0);
271/// ```
272#[must_use]
273#[inline]
274pub fn relative_diff<T: Real>(a: T, b: T) -> T {
275    let diff = (a - b).abs();
276    let m = a.abs().max(b.abs());
277    if m == T::zero() {
278        T::zero()
279    } else {
280        diff / m
281    }
282}
283
284/// Round `x` to `decimals` decimal places.
285///
286/// # Examples
287///
288/// ```
289/// use mathverse_core::precision::round_to;
290///
291/// assert_eq!(round_to(2.345678, 3), 2.346);
292/// assert_eq!(round_to(-1.234, 2), -1.23);
293/// ```
294#[must_use]
295pub fn round_to<T: Real>(x: T, decimals: i32) -> T {
296    let f = T::from_f64(pow10(decimals));
297    (x * f).round() / f
298}
299
300fn pow10(n: i32) -> f64 {
301    let mut acc = 1.0;
302    for _ in 0..n.unsigned_abs() {
303        acc *= 10.0;
304    }
305    if n < 0 { 1.0 / acc } else { acc }
306}
307
308/// Round `x` to `n` significant figures.
309///
310/// # Examples
311///
312/// ```
313/// use mathverse_core::precision::significant_figures;
314///
315/// assert_eq!(significant_figures(0.00234567, 3), 0.00235);
316/// assert_eq!(significant_figures(12345.0, 2), 12000.0);
317/// ```
318#[must_use]
319#[allow(clippy::cast_lossless)] // From<i32> for f64 is unavailable in no_std
320pub fn significant_figures<T: Real + Transcendental>(x: T, n: i32) -> T {
321    if x == T::zero() {
322        return T::zero();
323    }
324    let shift = T::from_f64((n - 1) as f64) - x.abs().log10().floor();
325    round_to(x, shift.to_f64() as i32)
326}
327
328/// Distance from `x` to the next representable `f64` toward +infinity.
329///
330/// # Examples
331///
332/// ```
333/// use mathverse_core::precision::ulp;
334///
335/// assert!(ulp(1.0) < 1e-15);
336/// assert!(ulp(-1.0) < 1e-15);
337/// ```
338#[must_use]
339pub fn ulp(x: f64) -> f64 {
340    if !x.is_finite() || x == 0.0 {
341        return f64::MIN_POSITIVE;
342    }
343    let b = x.to_bits();
344    let b = if x.is_sign_negative() {
345        b.wrapping_sub(1)
346    } else {
347        b.wrapping_add(1)
348    };
349    f64::from_bits(b) - x
350}
351
352/// Round `x` up to the nearest multiple of `m`.
353///
354/// Returns `x` if `m` is zero.
355///
356/// # Examples
357///
358/// ```
359/// use mathverse_core::precision::ceil_to_multiple;
360///
361/// assert_eq!(ceil_to_multiple(7.0, 5.0), 10.0);
362/// assert_eq!(ceil_to_multiple(10.0, 5.0), 10.0);
363/// assert_eq!(ceil_to_multiple(0.0, 3.0), 0.0);
364/// ```
365#[must_use]
366pub fn ceil_to_multiple<T: Real>(x: T, m: T) -> T {
367    if m == T::zero() {
368        return x;
369    }
370    (x / m).ceil() * m
371}
372
373/// Round `x` down to the nearest multiple of `m`.
374///
375/// Returns `x` if `m` is zero.
376///
377/// # Examples
378///
379/// ```
380/// use mathverse_core::precision::floor_to_multiple;
381///
382/// assert_eq!(floor_to_multiple(7.0, 5.0), 5.0);
383/// assert_eq!(floor_to_multiple(10.0, 5.0), 10.0);
384/// assert_eq!(floor_to_multiple(0.0, 3.0), 0.0);
385/// ```
386#[must_use]
387pub fn floor_to_multiple<T: Real>(x: T, m: T) -> T {
388    if m == T::zero() {
389        return x;
390    }
391    (x / m).floor() * m
392}
393
394/// Round `x` to the nearest multiple of `m`.
395///
396/// Returns `x` if `m` is zero.
397///
398/// # Examples
399///
400/// ```
401/// use mathverse_core::precision::round_to_multiple;
402///
403/// assert_eq!(round_to_multiple(7.0, 5.0), 5.0);
404/// assert_eq!(round_to_multiple(8.0, 5.0), 10.0);
405/// assert_eq!(round_to_multiple(0.0, 3.0), 0.0);
406/// ```
407#[must_use]
408pub fn round_to_multiple<T: Real>(x: T, m: T) -> T {
409    if m == T::zero() {
410        return x;
411    }
412    (x / m).round() * m
413}
414
415#[cfg(test)]
416mod tests {
417    use super::*;
418
419    #[test]
420    fn comparisons() {
421        assert!(almost_eq(0.1 + 0.2, 0.3, 1e-15));
422        assert!(!almost_eq(0.1 + 0.2, 0.3, 1e-17));
423        assert!(almost_eq_rel(1e15, 1e15 + 1.0, 1e-12));
424        assert!(almost_eq_rel(0.0, 0.0, 1e-10));
425    }
426
427    #[test]
428    fn rounding() {
429        assert_eq!(round_to(3.14159, 2), 3.14);
430        assert_eq!(significant_figures(12345.0, 2), 12000.0);
431        assert_eq!(significant_figures(0.0, 3), 0.0);
432    }
433
434    #[test]
435    fn ulp_is_small() {
436        assert!(ulp(1.0) < 1e-15);
437        assert!(ulp(-1.0) < 1e-15);
438    }
439
440    #[test]
441    fn is_close_combined() {
442        assert!(is_close(1.0, 1.0 + 1e-16, 1e-12, 1e-15));
443        assert!(!is_close(1.0, 2.0, 1e-12, 1e-15));
444        assert!(is_close(0.0, 0.0, 1e-12, 1e-15));
445        assert!(is_close(0.0, 1e-20, 1e-12, 1e-15));
446    }
447
448    #[test]
449    fn ulp_comparison() {
450        assert!(almost_eq_ulp(1.0, 1.0, 0));
451        assert!(almost_eq_ulp(1.0, 1.0 + 1e-15, 5));
452        assert!(!almost_eq_ulp(1.0, 2.0, 100));
453        assert!(almost_eq_ulp(f64::INFINITY, f64::INFINITY, 0));
454        assert!(!almost_eq_ulp(f64::NAN, f64::NAN, 0));
455    }
456
457    #[test]
458    fn float_helpers() {
459        assert!(is_subnormal(f64::MIN_POSITIVE / 2.0));
460        assert!(!is_subnormal(1.0));
461        assert!(is_nan(f64::NAN));
462        assert!(!is_nan(1.0));
463        assert!(is_infinite(f64::INFINITY));
464        assert!(!is_infinite(1.0));
465        assert!(almost_eq(epsilon::<f64>(), EPS, 1e-20));
466    }
467
468    #[test]
469    fn next_prev_float() {
470        let next = next_float(1.0);
471        assert!(next > 1.0);
472        assert_eq!(prev_float(next), 1.0);
473        assert_eq!(next_float(f64::INFINITY), f64::INFINITY);
474    }
475
476    #[test]
477    fn copysign_abs_diff() {
478        assert_eq!(copysign(3.0, -1.0), -3.0);
479        assert_eq!(copysign(3.0, 1.0), 3.0);
480        assert_eq!(abs_diff(5.0, 3.0), 2.0);
481        assert_eq!(relative_diff(10.0, 9.0), 0.1);
482        assert_eq!(relative_diff(0.0, 0.0), 0.0);
483    }
484
485    #[test]
486    fn multiple_rounding() {
487        assert_eq!(ceil_to_multiple(7.0, 5.0), 10.0);
488        assert_eq!(ceil_to_multiple(10.0, 5.0), 10.0);
489        assert_eq!(ceil_to_multiple(0.0, 3.0), 0.0);
490        assert_eq!(floor_to_multiple(7.0, 5.0), 5.0);
491        assert_eq!(floor_to_multiple(10.0, 5.0), 10.0);
492        assert_eq!(floor_to_multiple(0.0, 3.0), 0.0);
493        assert_eq!(round_to_multiple(7.0, 5.0), 5.0);
494        assert_eq!(round_to_multiple(8.0, 5.0), 10.0);
495        assert_eq!(round_to_multiple(0.0, 3.0), 0.0);
496    }
497}