floatguard/f64/guarded/
mod.rs

1//! This module provides a checked floating-point number type, `GuardedF64`, which ensures that the
2//! value is neither NaN nor infinite.
3mod cmp;
4mod convert;
5
6use crate::FloatError;
7
8/// Represents a checked floating-point number that ensures it is neither NaN nor infinite.
9///
10/// # Example
11///
12/// ```rust
13/// use floatguard::{GuardedF64, FloatError};
14///
15/// let checked_f64 = GuardedF64::new(1.0).expect("1.0 is a valid f64 value");
16/// assert_eq!((checked_f64 + 1.0).check(), GuardedF64::new(2.0));
17///
18/// assert_eq!((checked_f64 / 0.0).check(), Err(FloatError::Infinity));
19///
20/// assert_eq!((checked_f64 - f64::INFINITY).check(), Err(FloatError::Infinity));
21///
22/// assert_eq!((checked_f64 % f64::NAN).check(), Err(FloatError::NaN));
23/// ```
24#[derive(Debug, Default, Clone, Copy)]
25pub struct GuardedF64(pub(crate) f64);
26
27impl GuardedF64 {
28    /// Creates a new `GuardedF64` instance.
29    ///
30    /// # Returns
31    ///
32    /// Returns a new `GuardedF64` instance containing the provided `f64` value if it is valid (finite).
33    ///
34    /// # Errors
35    ///
36    /// Returns `FloatError` if the value is NaN or infinite.
37    ///
38    /// # Example
39    ///
40    /// ```rust
41    /// use floatguard::{GuardedF64, FloatError};
42    ///
43    /// let valid_value = GuardedF64::new(2.0).unwrap();
44    /// assert_eq!(valid_value, 2.0f64);
45    ///
46    /// let invalid_value = GuardedF64::new(f64::NAN);
47    /// assert_eq!(invalid_value, Err(FloatError::NaN));
48    ///
49    /// let inf_value = GuardedF64::new(f64::INFINITY);
50    /// assert_eq!(inf_value, Err(FloatError::Infinity));
51    /// ```
52    pub const fn new(value: f64) -> Result<Self, FloatError> {
53        if value.is_finite() {
54            Ok(Self(value))
55        } else {
56            Err(if value.is_nan() {
57                FloatError::NaN
58            } else {
59                FloatError::Infinity
60            })
61        }
62    }
63}
64
65impl std::fmt::Display for GuardedF64 {
66    /// Formats the `GuardedF64` as a string.
67    ///
68    /// # Returns
69    ///
70    /// Returns a string representation of the inner `f64` value.
71    ///
72    /// # Example
73    ///
74    /// ```rust
75    /// use floatguard::GuardedF64;
76    ///
77    /// let value = GuardedF64::new(2.0).unwrap();
78    /// assert_eq!(value.to_string(), "2");
79    /// ```
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        write!(f, "{}", self.0)
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    #![allow(clippy::float_cmp)]
88
89    use super::*;
90    use crate::f64::tests::{invalid_f64, valid_f64};
91    use proptest::prelude::*;
92
93    proptest! {
94        #[test]
95        fn test_new_valid(a in valid_f64()) {
96            prop_assert_eq!(GuardedF64::new(a), Ok(GuardedF64(a)));
97            prop_assert_eq!(GuardedF64::new(a).map(f64::from), Ok(a));
98            prop_assert_eq!(*GuardedF64::new(a).unwrap(), a);
99        }
100
101        #[test]
102        fn test_new_invalid(a in invalid_f64()) {
103            let err = if a.is_nan() {
104                FloatError::NaN
105            } else {
106                FloatError::Infinity
107            };
108            prop_assert_eq!(GuardedF64::new(a), Err(err));
109        }
110
111        #[test]
112        fn test_display(a in any::<f64>()) {
113            let checked_a = GuardedF64::new(a);
114            if let Ok(guarded_a) = checked_a {
115                prop_assert_eq!(guarded_a.to_string(), a.to_string());
116            } else {
117                prop_assert!(a.is_nan() || a.is_infinite());
118            }
119        }
120    }
121}