use std::{cmp::Ordering, marker::PhantomData};
use crate::support::constraint::{Constrained, Constraint, ConstraintError, UnitBounds};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct UnitInterval;
impl UnitInterval {
pub fn new<T: UnitBounds>(value: T) -> Result<Constrained<T, UnitInterval>, ConstraintError> {
Constrained::<T, UnitInterval>::new(value)
}
#[must_use]
pub fn zero<T: UnitBounds>() -> Constrained<T, UnitInterval> {
Constrained::<T, UnitInterval> {
value: T::zero(),
_marker: PhantomData,
}
}
#[must_use]
pub fn one<T: UnitBounds>() -> Constrained<T, UnitInterval> {
Constrained::<T, UnitInterval> {
value: T::one(),
_marker: PhantomData,
}
}
}
impl<T: UnitBounds> Constraint<T> for UnitInterval {
fn check(value: &T) -> Result<(), ConstraintError> {
match (value.partial_cmp(&T::zero()), value.partial_cmp(&T::one())) {
(None, _) | (_, None) => Err(ConstraintError::NotANumber),
(Some(Ordering::Less), _) => Err(ConstraintError::BelowMinimum),
(_, Some(Ordering::Greater)) => Err(ConstraintError::AboveMaximum),
_ => Ok(()),
}
}
}
#[cfg(test)]
mod tests {
use crate::support::constraint::*;
use uom::si::{
f64::Ratio,
ratio::{percent, ratio},
};
#[test]
#[allow(clippy::float_cmp)]
fn floats_valid() {
assert!(Constrained::<f64, UnitInterval>::new(0.0).is_ok());
assert!(Constrained::<f64, UnitInterval>::new(1.0).is_ok());
assert!(UnitInterval::new(0.5).is_ok());
let z = UnitInterval::zero::<f64>();
let o = UnitInterval::one::<f64>();
assert_eq!(z.into_inner(), 0.0);
assert_eq!(o.into_inner(), 1.0);
}
#[test]
fn floats_out_of_range() {
assert!(matches!(
UnitInterval::new(-1.0),
Err(ConstraintError::BelowMinimum)
));
assert!(matches!(
UnitInterval::new(2.0),
Err(ConstraintError::AboveMaximum)
));
assert!(matches!(
UnitInterval::new(-1e-15),
Err(ConstraintError::BelowMinimum),
));
assert!(matches!(
UnitInterval::new(1.0 + 1e-15),
Err(ConstraintError::AboveMaximum)
));
assert!(matches!(
UnitInterval::new(f64::INFINITY),
Err(ConstraintError::AboveMaximum)
));
assert!(matches!(
UnitInterval::new(f64::NEG_INFINITY),
Err(ConstraintError::BelowMinimum)
));
}
#[test]
fn floats_nan_is_not_a_number() {
assert!(matches!(
UnitInterval::new(f64::NAN),
Err(ConstraintError::NotANumber)
));
}
#[test]
#[allow(clippy::float_cmp)]
fn uom_ratio_valid() {
assert!(Constrained::<Ratio, UnitInterval>::new(Ratio::new::<ratio>(0.0)).is_ok());
assert!(Constrained::<Ratio, UnitInterval>::new(Ratio::new::<ratio>(1.0)).is_ok());
assert!(UnitInterval::new(Ratio::new::<ratio>(0.5)).is_ok());
let z = UnitInterval::zero::<Ratio>();
let o = UnitInterval::one::<Ratio>();
assert_eq!(z.into_inner().get::<ratio>(), 0.0);
assert_eq!(z.into_inner().get::<percent>(), 0.0);
assert_eq!(o.into_inner().get::<ratio>(), 1.0);
assert_eq!(o.into_inner().get::<percent>(), 100.0);
}
#[test]
fn uom_ratio_out_of_range() {
assert!(matches!(
UnitInterval::new(Ratio::new::<ratio>(-0.1)),
Err(ConstraintError::BelowMinimum)
));
assert!(matches!(
UnitInterval::new(Ratio::new::<ratio>(1.1)),
Err(ConstraintError::AboveMaximum)
));
}
}