#[derive(Debug, Clone, Copy)]
struct Sample<T: Copy> {
timestamp: u64,
value: T,
}
#[derive(Debug, Clone)]
pub struct WindowedMaxF64 {
window: u64,
samples: [Sample<f64>; 3],
count: u64,
}
impl WindowedMaxF64 {
#[inline]
pub fn new(window: u64) -> Result<Self, crate::ConfigError> {
if window == 0 {
return Err(crate::ConfigError::Invalid("window must be positive"));
}
let init = Sample {
timestamp: 0,
value: f64::MIN,
};
Ok(Self {
window,
samples: [init; 3],
count: 0,
})
}
#[inline]
pub fn update(&mut self, timestamp: u64, value: f64) -> Result<f64, crate::DataError> {
check_finite!(value);
self.count += 1;
let win = self.window;
let s = &mut self.samples;
if value >= s[0].value || timestamp.wrapping_sub(s[2].timestamp) > win {
s[0] = Sample { timestamp, value };
s[1] = s[0];
s[2] = s[0];
return Ok(s[0].value);
}
if timestamp.wrapping_sub(s[1].timestamp) > win / 3 {
s[1] = Sample { timestamp, value };
s[2] = s[1];
} else if timestamp.wrapping_sub(s[2].timestamp) > win / 3 {
s[2] = Sample { timestamp, value };
}
if value >= s[1].value {
s[1] = Sample { timestamp, value };
s[2] = s[1];
} else if value >= s[2].value {
s[2] = Sample { timestamp, value };
}
if timestamp.wrapping_sub(s[0].timestamp) > win {
s[0] = s[1];
s[1] = s[2];
s[2] = Sample { timestamp, value };
} else if timestamp.wrapping_sub(s[1].timestamp) > win / 3 {
s[1] = s[2];
s[2] = Sample { timestamp, value };
}
Ok(s[0].value)
}
#[inline]
pub fn update_i64(&mut self, timestamp: i64, value: f64) -> Result<f64, crate::DataError> {
debug_assert!(timestamp >= 0, "negative timestamp: {timestamp}");
self.update(timestamp as u64, value)
}
#[inline]
#[must_use]
pub fn max(&self) -> Option<f64> {
if self.count == 0 {
None
} else {
Some(self.samples[0].value)
}
}
#[inline]
#[must_use]
pub fn window(&self) -> u64 {
self.window
}
#[inline]
#[must_use]
pub fn count(&self) -> u64 {
self.count
}
#[inline]
pub fn reset(&mut self) {
let init = Sample {
timestamp: 0,
value: f64::MIN,
};
self.samples = [init; 3];
self.count = 0;
}
}
#[derive(Debug, Clone)]
pub struct WindowedMaxI64 {
window: u64,
samples: [Sample<i64>; 3],
count: u64,
}
impl WindowedMaxI64 {
#[inline]
pub fn new(window: u64) -> Result<Self, crate::ConfigError> {
if window == 0 {
return Err(crate::ConfigError::Invalid("window must be positive"));
}
let init = Sample {
timestamp: 0,
value: i64::MIN,
};
Ok(Self {
window,
samples: [init; 3],
count: 0,
})
}
#[inline]
#[must_use]
pub fn update(&mut self, timestamp: u64, value: i64) -> i64 {
self.count += 1;
let win = self.window;
let s = &mut self.samples;
if value >= s[0].value || timestamp.wrapping_sub(s[2].timestamp) > win {
s[0] = Sample { timestamp, value };
s[1] = s[0];
s[2] = s[0];
return s[0].value;
}
if timestamp.wrapping_sub(s[1].timestamp) > win / 3 {
s[1] = Sample { timestamp, value };
s[2] = s[1];
} else if timestamp.wrapping_sub(s[2].timestamp) > win / 3 {
s[2] = Sample { timestamp, value };
}
if value >= s[1].value {
s[1] = Sample { timestamp, value };
s[2] = s[1];
} else if value >= s[2].value {
s[2] = Sample { timestamp, value };
}
if timestamp.wrapping_sub(s[0].timestamp) > win {
s[0] = s[1];
s[1] = s[2];
s[2] = Sample { timestamp, value };
} else if timestamp.wrapping_sub(s[1].timestamp) > win / 3 {
s[1] = s[2];
s[2] = Sample { timestamp, value };
}
s[0].value
}
#[inline]
#[must_use]
pub fn update_i64(&mut self, timestamp: i64, value: i64) -> i64 {
debug_assert!(timestamp >= 0, "negative timestamp: {timestamp}");
self.update(timestamp as u64, value)
}
#[inline]
#[must_use]
pub fn max(&self) -> Option<i64> {
if self.count == 0 {
None
} else {
Some(self.samples[0].value)
}
}
#[inline]
#[must_use]
pub fn window(&self) -> u64 {
self.window
}
#[inline]
#[must_use]
pub fn count(&self) -> u64 {
self.count
}
#[inline]
pub fn reset(&mut self) {
let init = Sample {
timestamp: 0,
value: i64::MIN,
};
self.samples = [init; 3];
self.count = 0;
}
}
#[derive(Debug, Clone)]
pub struct WindowedMinF64 {
window: u64,
samples: [Sample<f64>; 3],
count: u64,
}
impl WindowedMinF64 {
#[inline]
pub fn new(window: u64) -> Result<Self, crate::ConfigError> {
if window == 0 {
return Err(crate::ConfigError::Invalid("window must be positive"));
}
let init = Sample {
timestamp: 0,
value: f64::MAX,
};
Ok(Self {
window,
samples: [init; 3],
count: 0,
})
}
#[inline]
pub fn update(&mut self, timestamp: u64, value: f64) -> Result<f64, crate::DataError> {
check_finite!(value);
self.count += 1;
let win = self.window;
let s = &mut self.samples;
if value <= s[0].value || timestamp.wrapping_sub(s[2].timestamp) > win {
s[0] = Sample { timestamp, value };
s[1] = s[0];
s[2] = s[0];
return Ok(s[0].value);
}
if timestamp.wrapping_sub(s[1].timestamp) > win / 3 {
s[1] = Sample { timestamp, value };
s[2] = s[1];
} else if timestamp.wrapping_sub(s[2].timestamp) > win / 3 {
s[2] = Sample { timestamp, value };
}
if value <= s[1].value {
s[1] = Sample { timestamp, value };
s[2] = s[1];
} else if value <= s[2].value {
s[2] = Sample { timestamp, value };
}
if timestamp.wrapping_sub(s[0].timestamp) > win {
s[0] = s[1];
s[1] = s[2];
s[2] = Sample { timestamp, value };
} else if timestamp.wrapping_sub(s[1].timestamp) > win / 3 {
s[1] = s[2];
s[2] = Sample { timestamp, value };
}
Ok(s[0].value)
}
#[inline]
pub fn update_i64(&mut self, timestamp: i64, value: f64) -> Result<f64, crate::DataError> {
debug_assert!(timestamp >= 0, "negative timestamp: {timestamp}");
self.update(timestamp as u64, value)
}
#[inline]
#[must_use]
pub fn min(&self) -> Option<f64> {
if self.count == 0 {
None
} else {
Some(self.samples[0].value)
}
}
#[inline]
#[must_use]
pub fn window(&self) -> u64 {
self.window
}
#[inline]
#[must_use]
pub fn count(&self) -> u64 {
self.count
}
#[inline]
pub fn reset(&mut self) {
let init = Sample {
timestamp: 0,
value: f64::MAX,
};
self.samples = [init; 3];
self.count = 0;
}
}
#[derive(Debug, Clone)]
pub struct WindowedMinI64 {
window: u64,
samples: [Sample<i64>; 3],
count: u64,
}
impl WindowedMinI64 {
#[inline]
pub fn new(window: u64) -> Result<Self, crate::ConfigError> {
if window == 0 {
return Err(crate::ConfigError::Invalid("window must be positive"));
}
let init = Sample {
timestamp: 0,
value: i64::MAX,
};
Ok(Self {
window,
samples: [init; 3],
count: 0,
})
}
#[inline]
#[must_use]
pub fn update(&mut self, timestamp: u64, value: i64) -> i64 {
self.count += 1;
let win = self.window;
let s = &mut self.samples;
if value <= s[0].value || timestamp.wrapping_sub(s[2].timestamp) > win {
s[0] = Sample { timestamp, value };
s[1] = s[0];
s[2] = s[0];
return s[0].value;
}
if timestamp.wrapping_sub(s[1].timestamp) > win / 3 {
s[1] = Sample { timestamp, value };
s[2] = s[1];
} else if timestamp.wrapping_sub(s[2].timestamp) > win / 3 {
s[2] = Sample { timestamp, value };
}
if value <= s[1].value {
s[1] = Sample { timestamp, value };
s[2] = s[1];
} else if value <= s[2].value {
s[2] = Sample { timestamp, value };
}
if timestamp.wrapping_sub(s[0].timestamp) > win {
s[0] = s[1];
s[1] = s[2];
s[2] = Sample { timestamp, value };
} else if timestamp.wrapping_sub(s[1].timestamp) > win / 3 {
s[1] = s[2];
s[2] = Sample { timestamp, value };
}
s[0].value
}
#[inline]
#[must_use]
pub fn update_i64(&mut self, timestamp: i64, value: i64) -> i64 {
debug_assert!(timestamp >= 0, "negative timestamp: {timestamp}");
self.update(timestamp as u64, value)
}
#[inline]
#[must_use]
pub fn min(&self) -> Option<i64> {
if self.count == 0 {
None
} else {
Some(self.samples[0].value)
}
}
#[inline]
#[must_use]
pub fn window(&self) -> u64 {
self.window
}
#[inline]
#[must_use]
pub fn count(&self) -> u64 {
self.count
}
#[inline]
pub fn reset(&mut self) {
let init = Sample {
timestamp: 0,
value: i64::MAX,
};
self.samples = [init; 3];
self.count = 0;
}
}
#[derive(Debug, Clone)]
pub(crate) struct WindowedMaxF32 {
window: u64,
samples: [Sample<f32>; 3],
count: u64,
}
impl WindowedMaxF32 {
#[inline]
pub(crate) fn new(window: u64) -> Result<Self, crate::ConfigError> {
if window == 0 {
return Err(crate::ConfigError::Invalid("window must be positive"));
}
let init = Sample {
timestamp: 0,
value: f32::MIN,
};
Ok(Self {
window,
samples: [init; 3],
count: 0,
})
}
#[inline]
pub(crate) fn update(&mut self, timestamp: u64, value: f32) -> Result<f32, crate::DataError> {
check_finite!(value);
self.count += 1;
let win = self.window;
let s = &mut self.samples;
if value >= s[0].value || timestamp.wrapping_sub(s[2].timestamp) > win {
s[0] = Sample { timestamp, value };
s[1] = s[0];
s[2] = s[0];
return Ok(s[0].value);
}
if timestamp.wrapping_sub(s[1].timestamp) > win / 3 {
s[1] = Sample { timestamp, value };
s[2] = s[1];
} else if timestamp.wrapping_sub(s[2].timestamp) > win / 3 {
s[2] = Sample { timestamp, value };
}
if value >= s[1].value {
s[1] = Sample { timestamp, value };
s[2] = s[1];
} else if value >= s[2].value {
s[2] = Sample { timestamp, value };
}
if timestamp.wrapping_sub(s[0].timestamp) > win {
s[0] = s[1];
s[1] = s[2];
s[2] = Sample { timestamp, value };
} else if timestamp.wrapping_sub(s[1].timestamp) > win / 3 {
s[1] = s[2];
s[2] = Sample { timestamp, value };
}
Ok(s[0].value)
}
#[inline]
pub(crate) fn max(&self) -> Option<f32> {
if self.count == 0 {
None
} else {
Some(self.samples[0].value)
}
}
#[inline]
pub(crate) fn count(&self) -> u64 {
self.count
}
#[inline]
pub(crate) fn reset(&mut self) {
let init = Sample {
timestamp: 0,
value: f32::MIN,
};
self.samples = [init; 3];
self.count = 0;
}
}
#[derive(Debug, Clone)]
pub(crate) struct WindowedMinF32 {
window: u64,
samples: [Sample<f32>; 3],
count: u64,
}
impl WindowedMinF32 {
#[inline]
pub(crate) fn new(window: u64) -> Result<Self, crate::ConfigError> {
if window == 0 {
return Err(crate::ConfigError::Invalid("window must be positive"));
}
let init = Sample {
timestamp: 0,
value: f32::MAX,
};
Ok(Self {
window,
samples: [init; 3],
count: 0,
})
}
#[inline]
pub(crate) fn update(&mut self, timestamp: u64, value: f32) -> Result<f32, crate::DataError> {
check_finite!(value);
self.count += 1;
let win = self.window;
let s = &mut self.samples;
if value <= s[0].value || timestamp.wrapping_sub(s[2].timestamp) > win {
s[0] = Sample { timestamp, value };
s[1] = s[0];
s[2] = s[0];
return Ok(s[0].value);
}
if timestamp.wrapping_sub(s[1].timestamp) > win / 3 {
s[1] = Sample { timestamp, value };
s[2] = s[1];
} else if timestamp.wrapping_sub(s[2].timestamp) > win / 3 {
s[2] = Sample { timestamp, value };
}
if value <= s[1].value {
s[1] = Sample { timestamp, value };
s[2] = s[1];
} else if value <= s[2].value {
s[2] = Sample { timestamp, value };
}
if timestamp.wrapping_sub(s[0].timestamp) > win {
s[0] = s[1];
s[1] = s[2];
s[2] = Sample { timestamp, value };
} else if timestamp.wrapping_sub(s[1].timestamp) > win / 3 {
s[1] = s[2];
s[2] = Sample { timestamp, value };
}
Ok(s[0].value)
}
#[inline]
pub(crate) fn min(&self) -> Option<f32> {
if self.count == 0 {
None
} else {
Some(self.samples[0].value)
}
}
#[inline]
pub(crate) fn count(&self) -> u64 {
self.count
}
#[inline]
pub(crate) fn reset(&mut self) {
let init = Sample {
timestamp: 0,
value: f32::MAX,
};
self.samples = [init; 3];
self.count = 0;
}
}
#[cfg(test)]
mod raw_tests {
use super::*;
#[test]
fn raw_max_basic() {
let mut wm = WindowedMaxF64::new(100).unwrap();
assert_eq!(wm.update(0, 10.0).unwrap(), 10.0);
assert_eq!(wm.update(50, 20.0).unwrap(), 20.0);
}
#[test]
fn raw_max_expires() {
let mut wm = WindowedMaxF64::new(10).unwrap();
let _ = wm.update(0, 100.0).unwrap();
let _ = wm.update(5, 50.0).unwrap();
let result = wm.update(11, 60.0).unwrap();
assert!(result <= 60.0);
}
#[test]
fn raw_min_basic() {
let mut wm = WindowedMinI64::new(100).unwrap();
assert_eq!(wm.update(0, 100), 100);
assert_eq!(wm.update(1, 50), 50);
}
#[test]
fn raw_max_i64_convenience() {
let mut wm = WindowedMaxF64::new(1000).unwrap();
assert_eq!(wm.update_i64(100i64, 42.0).unwrap(), 42.0);
}
#[test]
fn raw_rejects_zero_window() {
assert!(WindowedMaxF64::new(0).is_err());
}
#[test]
fn rejects_nan_and_inf() {
let mut wm = WindowedMaxF64::new(100).unwrap();
assert!(matches!(
wm.update(0, f64::NAN),
Err(crate::DataError::NotANumber)
));
assert!(matches!(
wm.update(0, f64::INFINITY),
Err(crate::DataError::Infinite)
));
let mut wn = WindowedMinF64::new(100).unwrap();
assert!(matches!(
wn.update(0, f64::NAN),
Err(crate::DataError::NotANumber)
));
assert!(matches!(
wn.update(0, f64::NEG_INFINITY),
Err(crate::DataError::Infinite)
));
}
}