nonneg_float/
lib.rs

1//! A generic wrapper for non-negative floating point values.
2//!
3//! Ensures that values are >= 0 and finite, providing safe construction
4//! methods and a convenient macro.
5//!
6//! Supports any float type implementing `num_traits::Float`.
7//!
8//! # Examples
9//!
10//! ```
11//! use nonneg_float::{NonNegative, nonneg};
12//!
13//! let zero = NonNegative::<f64>::zero();
14//! let val = NonNegative::try_new(3.14).unwrap();
15//! let macro_val = nonneg!(5.0f64).unwrap();
16//!
17//! assert_eq!(zero.get(), 0.0);
18//! assert_eq!(val.get(), 3.14);
19//! assert_eq!(macro_val.get(), 5.0);
20//! ```
21
22use num_traits::Float;
23use std::fmt;
24
25#[cfg(feature = "serde")]
26use serde::{Deserialize, Serialize};
27
28/// Error type returned when trying to create a `NonNegative` from an invalid value.
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub enum NonNegativeError {
31    /// The value was negative or not finite.
32    InvalidValue,
33}
34
35impl fmt::Display for NonNegativeError {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        match self {
38            NonNegativeError::InvalidValue => write!(f, "Value must be non-negative and finite"),
39        }
40    }
41}
42
43impl std::error::Error for NonNegativeError {}
44
45/// Wrapper type guaranteeing a non-negative floating-point value.
46#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
47#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
48pub struct NonNegative<T: Float>(T);
49
50impl<T: Float> NonNegative<T> {
51    /// Returns a `NonNegative` wrapping zero.
52    pub fn zero() -> Self
53    where
54        T: num_traits::Zero,
55    {
56        Self(T::zero())
57    }
58
59    /// Attempts to create a new `NonNegative<T>` from a value.
60    ///
61    /// Returns `Err` if the value is negative or not finite.
62    pub fn try_new(value: T) -> Result<Self, NonNegativeError> {
63        if value >= T::zero() && value.is_finite() {
64            Ok(Self(value))
65        } else {
66            Err(NonNegativeError::InvalidValue)
67        }
68    }
69
70    /// Creates a new `NonNegative<T>` or panics if invalid.
71    ///
72    /// # Panics
73    ///
74    /// Panics if the value is negative or not finite.
75    pub fn new(value: T) -> Self {
76        Self::try_new(value).expect("Value must be non-negative and finite")
77    }
78
79    /// Returns the inner float value.
80    pub fn get(&self) -> T {
81        self.0
82    }
83}
84
85impl<T: Float + num_traits::Zero> Default for NonNegative<T> {
86    fn default() -> Self {
87        Self::zero()
88    }
89}
90
91impl<T: Float + fmt::Display> fmt::Display for NonNegative<T> {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        self.0.fmt(f)
94    }
95}
96
97/// Macro to create a `NonNegative` value.
98///
99/// Returns `Result<NonNegative<T>, NonNegativeError>`.
100///
101/// Usage:
102/// - `nonneg!(value)` infers type and creates a `NonNegative` from `value`.
103/// - `nonneg!(Type)` creates a default zero value of that type.
104/// - `nonneg!(Type, value)` creates a `NonNegative` of the specified type.
105#[macro_export]
106macro_rules! nonneg {
107    ($t:ty) => {
108        $crate::NonNegative::<$t>::zero()
109    };
110    ($val:expr) => {{ $crate::NonNegative::try_new($val) }};
111    ($t:ty, $val:expr) => {{ $crate::NonNegative::<$t>::try_new($val) }};
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn test_zero() {
120        let zero = NonNegative::<f64>::zero();
121        assert_eq!(zero.get(), 0.0);
122        let default: NonNegative<f64> = Default::default();
123        assert_eq!(default.get(), 0.0);
124    }
125
126    #[test]
127    fn test_try_new_valid() {
128        let val = NonNegative::try_new(3.14f64).unwrap();
129        assert_eq!(val.get(), 3.14);
130    }
131
132    #[test]
133    fn test_try_new_invalid() {
134        assert_eq!(
135            NonNegative::try_new(-0.1f64).unwrap_err(),
136            NonNegativeError::InvalidValue
137        );
138        assert_eq!(
139            NonNegative::try_new(f64::NAN).unwrap_err(),
140            NonNegativeError::InvalidValue
141        );
142        assert_eq!(
143            NonNegative::try_new(f64::INFINITY).unwrap_err(),
144            NonNegativeError::InvalidValue
145        );
146    }
147
148    #[test]
149    fn test_new_panics() {
150        let _ = NonNegative::new(1.0f64); // okay
151    }
152
153    #[test]
154    #[should_panic(expected = "Value must be non-negative and finite")]
155    fn test_new_panics_on_invalid() {
156        let _ = NonNegative::new(-1.0f64);
157    }
158
159    #[test]
160    fn test_macro() {
161        let a = nonneg!(5.0f64).unwrap();
162        assert_eq!(a.get(), 5.0);
163
164        let b = nonneg!(f64);
165        assert_eq!(b.get(), 0.0);
166
167        let c = nonneg!(f32, 2.71).unwrap();
168        assert_eq!(c.get(), 2.71);
169
170        let d = nonneg!(-1.0f64);
171        assert!(d.is_err());
172    }
173}