Skip to main content

ixa/
numeric.rs

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