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