#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SamplePoint(u16);
impl SamplePoint {
pub const NOMINAL_DEFAULT: Self = Self::from_per_mille(700);
pub const DATA_DEFAULT: Self = Self::from_per_mille(800);
pub const PCT_75: Self = Self::from_per_mille(750);
pub const PCT_87_5: Self = Self::from_per_mille(875);
#[must_use]
pub const fn from_per_mille(per_mille: u16) -> Self {
assert!(
per_mille >= 500 && per_mille <= 950,
"sample point per-mille must be in [500, 950]"
);
Self(per_mille)
}
#[must_use]
pub const fn per_mille(self) -> u16 {
self.0
}
#[must_use]
pub fn as_fraction(self) -> f32 {
f32::from(self.0) / 1000.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nominal_default_is_70_percent() {
assert_eq!(SamplePoint::NOMINAL_DEFAULT.per_mille(), 700);
assert!((SamplePoint::NOMINAL_DEFAULT.as_fraction() - 0.70).abs() < 1e-6);
}
#[test]
fn data_default_is_80_percent() {
assert_eq!(SamplePoint::DATA_DEFAULT.per_mille(), 800);
assert!((SamplePoint::DATA_DEFAULT.as_fraction() - 0.80).abs() < 1e-6);
}
#[test]
fn pct_87_5_is_875_per_mille() {
assert_eq!(SamplePoint::PCT_87_5.per_mille(), 875);
assert!((SamplePoint::PCT_87_5.as_fraction() - 0.875).abs() < 1e-6);
}
#[test]
fn from_per_mille_accepts_range_bounds() {
assert_eq!(SamplePoint::from_per_mille(500).per_mille(), 500);
assert_eq!(SamplePoint::from_per_mille(950).per_mille(), 950);
}
#[test]
#[should_panic = "sample point per-mille must be in [500, 950]"]
#[allow(clippy::let_underscore_must_use)]
fn from_per_mille_rejects_too_low() {
let _ = SamplePoint::from_per_mille(499);
}
#[test]
#[should_panic = "sample point per-mille must be in [500, 950]"]
#[allow(clippy::let_underscore_must_use)]
fn from_per_mille_rejects_too_high() {
let _ = SamplePoint::from_per_mille(951);
}
}