twine_models/support/constraint/unit_interval/
closed.rs1use std::{cmp::Ordering, marker::PhantomData};
2
3use crate::support::constraint::{Constrained, Constraint, ConstraintError, UnitBounds};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
57pub struct UnitInterval;
58
59impl UnitInterval {
60 pub fn new<T: UnitBounds>(value: T) -> Result<Constrained<T, UnitInterval>, ConstraintError> {
70 Constrained::<T, UnitInterval>::new(value)
71 }
72
73 #[must_use]
75 pub fn zero<T: UnitBounds>() -> Constrained<T, UnitInterval> {
76 Constrained::<T, UnitInterval> {
77 value: T::zero(),
78 _marker: PhantomData,
79 }
80 }
81
82 #[must_use]
84 pub fn one<T: UnitBounds>() -> Constrained<T, UnitInterval> {
85 Constrained::<T, UnitInterval> {
86 value: T::one(),
87 _marker: PhantomData,
88 }
89 }
90}
91
92impl<T: UnitBounds> Constraint<T> for UnitInterval {
93 fn check(value: &T) -> Result<(), ConstraintError> {
94 match (value.partial_cmp(&T::zero()), value.partial_cmp(&T::one())) {
95 (None, _) | (_, None) => Err(ConstraintError::NotANumber),
96 (Some(Ordering::Less), _) => Err(ConstraintError::BelowMinimum),
97 (_, Some(Ordering::Greater)) => Err(ConstraintError::AboveMaximum),
98 _ => Ok(()),
99 }
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use crate::support::constraint::*;
106
107 use uom::si::{
108 f64::Ratio,
109 ratio::{percent, ratio},
110 };
111
112 #[test]
113 #[allow(clippy::float_cmp)]
114 fn floats_valid() {
115 assert!(Constrained::<f64, UnitInterval>::new(0.0).is_ok());
116 assert!(Constrained::<f64, UnitInterval>::new(1.0).is_ok());
117 assert!(UnitInterval::new(0.5).is_ok());
118
119 let z = UnitInterval::zero::<f64>();
120 let o = UnitInterval::one::<f64>();
121 assert_eq!(z.into_inner(), 0.0);
122 assert_eq!(o.into_inner(), 1.0);
123 }
124
125 #[test]
126 fn floats_out_of_range() {
127 assert!(matches!(
128 UnitInterval::new(-1.0),
129 Err(ConstraintError::BelowMinimum)
130 ));
131 assert!(matches!(
132 UnitInterval::new(2.0),
133 Err(ConstraintError::AboveMaximum)
134 ));
135 assert!(matches!(
136 UnitInterval::new(-1e-15),
137 Err(ConstraintError::BelowMinimum),
138 ));
139 assert!(matches!(
140 UnitInterval::new(1.0 + 1e-15),
141 Err(ConstraintError::AboveMaximum)
142 ));
143 assert!(matches!(
144 UnitInterval::new(f64::INFINITY),
145 Err(ConstraintError::AboveMaximum)
146 ));
147 assert!(matches!(
148 UnitInterval::new(f64::NEG_INFINITY),
149 Err(ConstraintError::BelowMinimum)
150 ));
151 }
152
153 #[test]
154 fn floats_nan_is_not_a_number() {
155 assert!(matches!(
156 UnitInterval::new(f64::NAN),
157 Err(ConstraintError::NotANumber)
158 ));
159 }
160
161 #[test]
162 #[allow(clippy::float_cmp)]
163 fn uom_ratio_valid() {
164 assert!(Constrained::<Ratio, UnitInterval>::new(Ratio::new::<ratio>(0.0)).is_ok());
165 assert!(Constrained::<Ratio, UnitInterval>::new(Ratio::new::<ratio>(1.0)).is_ok());
166 assert!(UnitInterval::new(Ratio::new::<ratio>(0.5)).is_ok());
167
168 let z = UnitInterval::zero::<Ratio>();
169 let o = UnitInterval::one::<Ratio>();
170
171 assert_eq!(z.into_inner().get::<ratio>(), 0.0);
172 assert_eq!(z.into_inner().get::<percent>(), 0.0);
173
174 assert_eq!(o.into_inner().get::<ratio>(), 1.0);
175 assert_eq!(o.into_inner().get::<percent>(), 100.0);
176 }
177
178 #[test]
179 fn uom_ratio_out_of_range() {
180 assert!(matches!(
181 UnitInterval::new(Ratio::new::<ratio>(-0.1)),
182 Err(ConstraintError::BelowMinimum)
183 ));
184 assert!(matches!(
185 UnitInterval::new(Ratio::new::<ratio>(1.1)),
186 Err(ConstraintError::AboveMaximum)
187 ));
188 }
189}