#[derive(Debug, Clone)]
pub struct SlewF64 {
max_rate: f64,
value: f64,
initialized: bool,
}
impl SlewF64 {
#[inline]
pub fn new(max_rate: f64) -> Result<Self, crate::ConfigError> {
#[allow(clippy::neg_cmp_op_on_partial_ord)]
if !(max_rate > 0.0) {
return Err(crate::ConfigError::Invalid("max_rate must be positive"));
}
Ok(Self {
max_rate,
value: 0.0,
initialized: false,
})
}
#[inline]
pub fn update(&mut self, sample: f64) -> Result<f64, crate::DataError> {
check_finite!(sample);
if !self.initialized {
self.value = sample;
self.initialized = true;
return Ok(sample);
}
self.value = sample.clamp(self.value - self.max_rate, self.value + self.max_rate);
Ok(self.value)
}
#[inline]
#[must_use]
pub fn value(&self) -> f64 {
self.value
}
#[inline]
pub fn reset(&mut self) {
self.value = 0.0;
self.initialized = false;
}
}
#[derive(Debug, Clone)]
pub struct SlewI64 {
max_rate: i64,
value: i64,
initialized: bool,
}
impl SlewI64 {
#[inline]
pub fn new(max_rate: i64) -> Result<Self, crate::ConfigError> {
if max_rate <= 0 {
return Err(crate::ConfigError::Invalid("max_rate must be positive"));
}
Ok(Self {
max_rate,
value: 0,
initialized: false,
})
}
#[inline]
#[must_use]
pub fn update(&mut self, sample: i64) -> i64 {
if !self.initialized {
self.value = sample;
self.initialized = true;
return sample;
}
self.value = sample.clamp(self.value - self.max_rate, self.value + self.max_rate);
self.value
}
#[inline]
#[must_use]
pub fn value(&self) -> i64 {
self.value
}
#[inline]
pub fn reset(&mut self) {
self.value = 0;
self.initialized = false;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[allow(clippy::float_cmp)]
fn spike_clamped() {
let mut s = SlewF64::new(10.0).unwrap();
assert_eq!(s.update(100.0).unwrap(), 100.0); assert_eq!(s.update(200.0).unwrap(), 110.0); assert_eq!(s.update(200.0).unwrap(), 120.0); }
#[test]
#[allow(clippy::float_cmp)]
fn gradual_passes_through() {
let mut s = SlewF64::new(10.0).unwrap();
assert_eq!(s.update(100.0).unwrap(), 100.0);
assert_eq!(s.update(105.0).unwrap(), 105.0); }
#[test]
#[allow(clippy::float_cmp)]
fn negative_clamped() {
let mut s = SlewF64::new(10.0).unwrap();
assert_eq!(s.update(100.0).unwrap(), 100.0);
assert_eq!(s.update(50.0).unwrap(), 90.0); }
#[test]
fn i64_basic() {
let mut s = SlewI64::new(5).unwrap();
assert_eq!(s.update(100), 100);
assert_eq!(s.update(200), 105);
}
#[test]
#[allow(clippy::float_cmp)]
fn reset() {
let mut s = SlewF64::new(10.0).unwrap();
s.update(100.0).unwrap();
s.reset();
assert_eq!(s.update(50.0).unwrap(), 50.0); }
#[test]
fn rejects_zero_max_rate() {
assert!(matches!(
SlewF64::new(0.0),
Err(crate::ConfigError::Invalid(_))
));
assert!(matches!(
SlewI64::new(0),
Err(crate::ConfigError::Invalid(_))
));
}
#[test]
fn rejects_nan_and_inf() {
let mut s = SlewF64::new(10.0).unwrap();
assert!(matches!(
s.update(f64::NAN),
Err(crate::DataError::NotANumber)
));
assert!(matches!(
s.update(f64::INFINITY),
Err(crate::DataError::Infinite)
));
assert!(matches!(
s.update(f64::NEG_INFINITY),
Err(crate::DataError::Infinite)
));
}
}