ixa/
numeric.rs

1#![allow(clippy::approx_constant)]
2//! Vendored from [statrs@0.18.0 (prec.rs)](http://github.com/statrs-dev/statrs/blob/v0.18.0/src/prec.rs), convenience
3//! wrappers around methods from the approx crate. Provides utility functions for working with floating point precision.
4
5use approx::AbsDiffEq;
6
7/// Targeted accuracy instantiated over `f64`
8pub const ACC: f64 = 10e-11;
9
10/// Compares if two floats are close via `approx::abs_diff_eq` using a maximum absolute difference
11/// (epsilon) of `acc`.
12#[must_use]
13pub fn almost_eq(a: f64, b: f64, acc: f64) -> bool {
14    if a.is_infinite() && b.is_infinite() {
15        return a == b;
16    }
17    a.abs_diff_eq(&b, acc)
18}
19
20/// Compares if two floats are close via `approx::relative_eq!` and `ACC` relative precision.
21/// Updates first argument to value of second argument.
22#[must_use]
23pub fn convergence(x: &mut f64, x_new: f64) -> bool {
24    let res = approx::relative_eq!(*x, x_new, max_relative = ACC);
25    *x = x_new;
26    res
27}
28
29#[macro_export]
30macro_rules! assert_almost_eq {
31    ($a:expr, $b:expr, $prec:expr $(,)?) => {
32        if !$crate::numeric::almost_eq($a, $b, $prec) {
33            panic!(
34                "assertion failed: `abs(left - right) < {:e}`, (left: `{}`, right: `{}`)",
35                $prec, $a, $b
36            );
37        }
38    };
39}
40
41// Not from statrs.
42#[cfg(test)]
43mod tests {
44    use super::*;
45
46    #[test]
47    fn almost_eq_within_tolerance() {
48        let a = 1.0;
49        let b = 1.0 + 0.5e-11;
50        // within ACC = 10e-11
51        assert!(almost_eq(a, b, ACC));
52    }
53
54    #[test]
55    fn almost_eq_outside_tolerance() {
56        let a = 1.0;
57        let b = 1.0 + 2e-10;
58        // 2e-10 > 10e-11
59        assert!(!almost_eq(a, b, ACC));
60    }
61
62    #[test]
63    fn almost_eq_infinities() {
64        assert!(almost_eq(f64::INFINITY, f64::INFINITY, ACC));
65        assert!(almost_eq(f64::NEG_INFINITY, f64::NEG_INFINITY, ACC));
66        assert!(!almost_eq(f64::INFINITY, f64::NEG_INFINITY, ACC));
67    }
68
69    #[test]
70    fn convergence_updates_and_compares() {
71        let mut x = 100.0;
72        // first call: compare 100.0 vs 100.0 → exactly equal → true
73        assert!(convergence(&mut x, 100.0));
74        // x should now be updated
75        assert_eq!(x, 100.0);
76
77        // now pick a new value within relative ACC
78        let x_new = x * (1.0 + 0.5 * ACC);
79        assert!(convergence(&mut x, x_new));
80        assert_eq!(x, x_new);
81
82        // now pick something well outside relative ACC
83        let x_new2 = x * (1.0 + 2.0 * ACC);
84        assert!(!convergence(&mut x, x_new2));
85        assert_eq!(x, x_new2);
86    }
87
88    #[test]
89    fn assert_almost_eq_macro_passes() {
90        // should not panic
91        assert_almost_eq!(3.14159265, 3.14159264, 1e-7);
92    }
93
94    #[test]
95    #[should_panic(expected = "assertion failed")]
96    fn assert_almost_eq_macro_panics() {
97        // difference is 1e-3, but prec=1e-4 → panic
98        assert_almost_eq!(1.0, 1.001, 1e-4);
99    }
100}