use core::cmp::Ordering;
use core::fmt;
use crate::error::{RcfError, RcfResult};
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AnomalyScore(f64);
impl AnomalyScore {
pub fn new(value: f64) -> RcfResult<Self> {
if !value.is_finite() || value < 0.0 {
return Err(RcfError::NaNValue);
}
Ok(Self(value))
}
#[must_use]
pub fn into_inner(self) -> f64 {
self.0
}
}
impl PartialEq for AnomalyScore {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for AnomalyScore {}
impl PartialOrd for AnomalyScore {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for AnomalyScore {
fn cmp(&self, other: &Self) -> Ordering {
self.0.partial_cmp(&other.0).unwrap_or(Ordering::Equal)
}
}
impl fmt::Display for AnomalyScore {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.6}", self.0)
}
}
impl From<AnomalyScore> for f64 {
fn from(score: AnomalyScore) -> Self {
score.0
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)] mod tests {
use super::*;
#[test]
fn accepts_zero() {
let s = AnomalyScore::new(0.0).unwrap();
assert_eq!(s.into_inner(), 0.0);
}
#[test]
fn accepts_positive() {
let s = AnomalyScore::new(1.25).unwrap();
assert_eq!(s.into_inner(), 1.25);
}
#[test]
fn rejects_negative() {
assert!(matches!(
AnomalyScore::new(-0.0001).unwrap_err(),
RcfError::NaNValue
));
}
#[test]
fn rejects_nan() {
assert!(AnomalyScore::new(f64::NAN).is_err());
}
#[test]
fn rejects_infinity() {
assert!(AnomalyScore::new(f64::INFINITY).is_err());
assert!(AnomalyScore::new(f64::NEG_INFINITY).is_err());
}
#[test]
fn ord_total() {
let a = AnomalyScore::new(0.0).unwrap();
let b = AnomalyScore::new(1.0).unwrap();
let c = AnomalyScore::new(1.0).unwrap();
assert!(a < b);
assert_eq!(b, c);
assert!(b >= c);
}
#[test]
fn display_uses_six_decimals() {
let s = AnomalyScore::new(1.5).unwrap();
assert_eq!(format!("{s}"), "1.500000");
}
#[test]
fn into_f64_roundtrip() {
let original = 0.42;
let s = AnomalyScore::new(original).unwrap();
let raw: f64 = s.into();
assert_eq!(raw, original);
}
}