#[derive(Debug, Clone)]
pub struct DeadBandF64 {
threshold: f64,
last_reported: f64,
initialized: bool,
}
impl DeadBandF64 {
#[inline]
#[must_use]
pub fn new(threshold: f64) -> Self {
Self {
threshold,
last_reported: 0.0,
initialized: false,
}
}
#[inline]
pub fn update(&mut self, sample: f64) -> Result<Option<f64>, crate::DataError> {
check_finite!(sample);
if !self.initialized {
self.last_reported = sample;
self.initialized = true;
return Ok(Option::Some(sample));
}
let delta = sample - self.last_reported;
let abs_delta = if delta < 0.0 { 0.0 - delta } else { delta };
if abs_delta > self.threshold {
self.last_reported = sample;
Ok(Option::Some(sample))
} else {
Ok(Option::None)
}
}
#[inline]
#[must_use]
pub fn last_reported(&self) -> Option<f64> {
if self.initialized {
Option::Some(self.last_reported)
} else {
Option::None
}
}
#[inline]
pub fn reset(&mut self) {
self.last_reported = 0.0;
self.initialized = false;
}
}
#[derive(Debug, Clone)]
pub struct DeadBandI64 {
threshold: u64,
last_reported: i64,
initialized: bool,
}
impl DeadBandI64 {
#[inline]
#[must_use]
pub fn new(threshold: u64) -> Self {
Self {
threshold,
last_reported: 0,
initialized: false,
}
}
#[inline]
#[must_use]
pub fn update(&mut self, sample: i64) -> Option<i64> {
if !self.initialized {
self.last_reported = sample;
self.initialized = true;
return Option::Some(sample);
}
if sample.abs_diff(self.last_reported) > self.threshold {
self.last_reported = sample;
Option::Some(sample)
} else {
Option::None
}
}
#[inline]
#[must_use]
pub fn last_reported(&self) -> Option<i64> {
if self.initialized {
Option::Some(self.last_reported)
} else {
Option::None
}
}
#[inline]
pub fn reset(&mut self) {
self.last_reported = 0;
self.initialized = false;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[allow(clippy::float_cmp)]
fn first_sample_always_reported() {
let mut db = DeadBandF64::new(5.0);
assert_eq!(db.update(100.0).unwrap(), Some(100.0));
}
#[test]
fn small_changes_suppressed() {
let mut db = DeadBandF64::new(5.0);
let _ = db.update(100.0).unwrap();
assert_eq!(db.update(103.0).unwrap(), None); assert_eq!(db.update(99.0).unwrap(), None); }
#[test]
#[allow(clippy::float_cmp)]
fn large_changes_reported() {
let mut db = DeadBandF64::new(5.0);
let _ = db.update(100.0).unwrap();
assert_eq!(db.update(110.0).unwrap(), Some(110.0)); }
#[test]
fn i64_basic() {
let mut db = DeadBandI64::new(10);
assert_eq!(db.update(100), Some(100));
assert_eq!(db.update(105), None);
assert_eq!(db.update(115), Some(115));
}
#[test]
fn reset() {
let mut db = DeadBandF64::new(5.0);
let _ = db.update(100.0).unwrap();
db.reset();
assert!(db.last_reported().is_none());
}
#[test]
fn rejects_nan_and_inf() {
let mut db = DeadBandF64::new(5.0);
assert_eq!(db.update(f64::NAN), Err(crate::DataError::NotANumber));
assert_eq!(db.update(f64::INFINITY), Err(crate::DataError::Infinite));
assert_eq!(
db.update(f64::NEG_INFINITY),
Err(crate::DataError::Infinite)
);
}
}