use crate::error::StatError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum NanPolicy {
#[default]
Omit,
Propagate,
}
pub fn clean(xs: &[f64], policy: NanPolicy) -> Result<Vec<f64>, StatError> {
if xs.is_empty() {
return Err(StatError::EmptyInput);
}
match policy {
NanPolicy::Propagate => {
if xs.iter().any(|x| x.is_nan()) {
Err(StatError::AllNaN)
} else {
Ok(xs.to_vec())
}
}
NanPolicy::Omit => {
let kept: Vec<f64> = xs.iter().copied().filter(|x| !x.is_nan()).collect();
if kept.is_empty() { Err(StatError::AllNaN) } else { Ok(kept) }
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::StatError;
#[test]
fn omit_drops_nan_and_reports_effective_n() {
let v = clean(&[1.0, f64::NAN, 3.0], NanPolicy::Omit).unwrap();
assert_eq!(v, vec![1.0, 3.0]);
}
#[test]
fn omit_all_nan_is_error() {
assert_eq!(clean(&[f64::NAN], NanPolicy::Omit), Err(StatError::AllNaN));
}
#[test]
fn empty_is_error() {
assert_eq!(clean(&[], NanPolicy::Omit), Err(StatError::EmptyInput));
}
#[test]
fn propagate_poisons_on_any_nan() {
assert_eq!(clean(&[1.0, f64::NAN], NanPolicy::Propagate), Err(StatError::AllNaN));
assert_eq!(clean(&[1.0, 2.0], NanPolicy::Propagate).unwrap(), vec![1.0, 2.0]);
}
}