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}