Skip to main content

axiolid_contracts/
displacement.rs

1//! A bound on how far a rounding step moved a boundary.
2
3/// A finite, non-negative distance bound in model units.
4///
5/// Never NaN, so `Eq` is sound.
6#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
7pub struct DisplacementBound(f64);
8
9impl Eq for DisplacementBound {}
10
11impl DisplacementBound {
12    /// No displacement.
13    pub const ZERO: Self = Self(0.0);
14
15    /// The bound `value`; `None` unless it is finite and at least zero.
16    /// `-0.0` becomes `+0.0`.
17    #[must_use]
18    pub fn new(value: f64) -> Option<Self> {
19        (value.is_finite() && value >= 0.0).then_some(Self(value + 0.0))
20    }
21
22    /// The bound as a number.
23    #[must_use]
24    pub const fn get(self) -> f64 {
25        self.0
26    }
27
28    /// The larger of two bounds.
29    #[must_use]
30    pub fn max(self, other: Self) -> Self {
31        if other.0 > self.0 {
32            other
33        } else {
34            self
35        }
36    }
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42
43    #[test]
44    fn only_finite_non_negative_values_are_bounds() {
45        for bad in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, -1.0, -1e-300] {
46            assert_eq!(DisplacementBound::new(bad), None, "{bad}");
47        }
48        assert_eq!(
49            DisplacementBound::new(0.5).map(DisplacementBound::get),
50            Some(0.5)
51        );
52    }
53
54    #[test]
55    fn negative_zero_becomes_positive_zero() {
56        let zero = DisplacementBound::new(-0.0).unwrap();
57        assert!(zero.get().is_sign_positive());
58        assert_eq!(zero, DisplacementBound::ZERO);
59        assert_eq!(DisplacementBound::default(), DisplacementBound::ZERO);
60    }
61
62    #[test]
63    fn max_takes_the_larger() {
64        let a = DisplacementBound::new(1.0).unwrap();
65        let b = DisplacementBound::new(2.0).unwrap();
66        assert_eq!(a.max(b), b);
67        assert_eq!(b.max(a), b);
68        assert_eq!(DisplacementBound::ZERO.max(a), a);
69    }
70
71    #[test]
72    fn bounds_are_eq_and_ordered() {
73        fn is_eq<T: Eq>() {}
74        is_eq::<DisplacementBound>();
75        assert!(DisplacementBound::ZERO < DisplacementBound::new(1e-300).unwrap());
76    }
77}