1#[derive(Debug, Clone, Copy)]
19pub enum BadValue {
20 Nan,
21 PosInf,
22 NegInf,
23}
24
25#[derive(Debug)]
26pub struct NanCheckError {
27 pub kind: BadValue,
28 pub index: usize,
29 pub label: String,
30}
31
32impl std::fmt::Display for NanCheckError {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 let what = match self.kind {
35 BadValue::Nan => "NaN",
36 BadValue::PosInf => "+inf",
37 BadValue::NegInf => "-inf",
38 };
39 write!(f, "{} at index {} of `{}`", what, self.index, self.label)
40 }
41}
42
43impl std::error::Error for NanCheckError {}
44
45#[cfg(feature = "nan-check")]
50#[inline(always)]
51pub fn scan(label: &str, data: &[f32]) -> Result<(), NanCheckError> {
52 for (i, &v) in data.iter().enumerate() {
53 if v.is_nan() {
54 return Err(NanCheckError {
55 kind: BadValue::Nan,
56 index: i,
57 label: label.to_string(),
58 });
59 }
60 if v.is_infinite() {
61 let kind = if v > 0.0 {
62 BadValue::PosInf
63 } else {
64 BadValue::NegInf
65 };
66 return Err(NanCheckError {
67 kind,
68 index: i,
69 label: label.to_string(),
70 });
71 }
72 }
73 Ok(())
74}
75
76#[cfg(not(feature = "nan-check"))]
77#[inline(always)]
78pub fn scan(_label: &str, _data: &[f32]) -> Result<(), NanCheckError> {
79 Ok(())
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn clean_data_passes() {
88 let data = [1.0, 2.0, -3.5, 0.0];
89 assert!(scan("clean", &data).is_ok());
90 }
91
92 #[cfg(feature = "nan-check")]
93 #[test]
94 fn detects_nan() {
95 let data = [1.0, f32::NAN, 3.0];
96 let err = scan("nan", &data).unwrap_err();
97 assert!(matches!(err.kind, BadValue::Nan));
98 assert_eq!(err.index, 1);
99 }
100
101 #[cfg(feature = "nan-check")]
102 #[test]
103 fn detects_pos_inf() {
104 let data = [f32::INFINITY, 0.0];
105 let err = scan("inf", &data).unwrap_err();
106 assert!(matches!(err.kind, BadValue::PosInf));
107 assert_eq!(err.index, 0);
108 }
109}