hoomd_utility/valid/
positive_real.rs

1// Copyright (c) 2024-2026 The Regents of the University of Michigan.
2// Part of hoomd-rs, released under the BSD 3-Clause License.
3
4//! Implement `PositiveReal`
5
6use serde::{Deserialize, Serialize};
7use std::{
8    cmp::Ordering,
9    fmt,
10    ops::{Div, DivAssign, Mul, MulAssign},
11};
12
13use super::Error;
14
15/// A f64 value that is not +/- inf, nan, or a value <= 0.
16///
17/// # Example
18///
19/// ```
20/// use hoomd_utility::valid::PositiveReal;
21///
22/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
23/// let positive = PositiveReal::try_from(1.0)?;
24/// # Ok(())
25/// # }
26/// ```
27#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
28pub struct PositiveReal(f64);
29
30impl PositiveReal {
31    /// Access the value.
32    ///
33    /// # Example
34    ///
35    /// ```
36    /// use hoomd_utility::valid::PositiveReal;
37    ///
38    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
39    /// let positive = PositiveReal::try_from(1.0)?;
40    ///
41    /// assert_eq!(positive.get(), 1.0);
42    /// # Ok(())
43    /// # }
44    #[must_use]
45    #[inline]
46    pub fn get(&self) -> f64 {
47        self.0
48    }
49}
50
51impl Eq for PositiveReal {}
52
53impl PartialOrd for PositiveReal {
54    #[inline]
55    fn partial_cmp(&self, other: &PositiveReal) -> Option<Ordering> {
56        Some(self.cmp(other))
57    }
58}
59
60impl Ord for PositiveReal {
61    #[inline]
62    fn cmp(&self, other: &Self) -> Ordering {
63        self.get().total_cmp(&other.get())
64    }
65}
66
67impl TryFrom<f64> for PositiveReal {
68    type Error = Error;
69
70    /// Convert [`f64`] to [`PositiveReal`].
71    ///
72    /// # Example
73    ///
74    /// Valid conversion:
75    /// ```
76    /// use hoomd_utility::valid::PositiveReal;
77    ///
78    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
79    /// let positive = PositiveReal::try_from(1.0)?;
80    /// # Ok(())
81    /// # }
82    /// ```
83    ///
84    /// Invalid conversion
85    /// ```
86    /// use hoomd_utility::valid::PositiveReal;
87    ///
88    /// let result = PositiveReal::try_from(-1.0);
89    /// assert!(matches!(
90    ///     result,
91    ///     Err(hoomd_utility::valid::Error::NotPositive(_))
92    /// ));
93    /// ```
94    ///
95    /// # Errors
96    ///
97    /// [`Error::NotFinite`] when `v` is not finite.
98    /// [`Error::NotPositive`] when `v` is not a positive value
99    #[inline]
100    fn try_from(v: f64) -> Result<PositiveReal, Error> {
101        if !v.is_finite() {
102            Err(Error::NotFinite(v))
103        } else if v <= 0.0 {
104            Err(Error::NotPositive(v))
105        } else {
106            Ok(PositiveReal(v))
107        }
108    }
109}
110
111impl Default for PositiveReal {
112    /// The default value is 1.0.
113    #[inline]
114    fn default() -> Self {
115        PositiveReal(1.0)
116    }
117}
118
119impl fmt::Display for PositiveReal {
120    #[inline]
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        self.0.fmt(f)
123    }
124}
125
126impl Mul for PositiveReal {
127    type Output = Self;
128
129    #[inline]
130    fn mul(self, rhs: Self) -> Self {
131        Self(self.0 * rhs.0)
132    }
133}
134
135impl MulAssign<PositiveReal> for PositiveReal {
136    #[inline]
137    fn mul_assign(&mut self, rhs: PositiveReal) {
138        self.0 *= rhs.0;
139    }
140}
141
142impl Div for PositiveReal {
143    type Output = Self;
144
145    #[inline]
146    fn div(self, rhs: Self) -> Self {
147        Self(self.0 / rhs.0)
148    }
149}
150
151impl DivAssign<PositiveReal> for PositiveReal {
152    #[inline]
153    fn div_assign(&mut self, rhs: PositiveReal) {
154        self.0 /= rhs.0;
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use assert2::check;
162
163    #[test]
164    fn positive_real_validation() {
165        let result = PositiveReal::try_from(f64::INFINITY);
166        check!(result == Err(Error::NotFinite(f64::INFINITY)));
167
168        let result = PositiveReal::try_from(-f64::INFINITY);
169        check!(result == Err(Error::NotFinite(-f64::INFINITY)));
170
171        let result = PositiveReal::try_from(f64::NAN);
172        check!(matches!(result, Err(Error::NotFinite(_))));
173
174        let result = PositiveReal::try_from(0.0);
175        check!(result == Err(Error::NotPositive(0.0)));
176
177        let result = PositiveReal::try_from(-1.0);
178        check!(result == Err(Error::NotPositive(-1.0)));
179    }
180}