#![no_std]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
use embedded_f32_sqrt::sqrt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StatsError {
EmptySlice,
NonFiniteValue,
}
#[inline]
fn ensure_finite(x: f32) -> Result<f32, StatsError> {
if x.is_finite() {
Ok(x)
} else {
Err(StatsError::NonFiniteValue)
}
}
pub fn mean(data: &[f32]) -> Result<f32, StatsError> {
if data.is_empty() {
return Err(StatsError::EmptySlice);
}
let sum = kahan_sum_checked(data)?;
Ok(sum / data.len() as f32)
}
pub fn variance(data: &[f32]) -> Result<f32, StatsError> {
if data.is_empty() {
return Err(StatsError::EmptySlice);
}
let m = kahan_sum_checked(data)? / data.len() as f32;
let mut sum = 0.0_f32;
let mut comp = 0.0_f32;
for &x in data {
let x = ensure_finite(x)?;
let d = x - m;
let y = d * d - comp;
let t = sum + y;
comp = (t - sum) - y;
sum = t;
}
let result = sum / data.len() as f32;
ensure_finite(result)
}
pub fn std_dev(data: &[f32]) -> Result<f32, StatsError> {
let v = variance(data)?;
let v = if v < 0.0 { 0.0 } else { v };
let s = sqrt(v).map_err(|_| StatsError::NonFiniteValue)?;
ensure_finite(s)
}
#[derive(Debug, Clone, Copy)]
pub struct StreamingStats {
count: u32,
mean: f32,
m2: f32,
}
impl StreamingStats {
#[inline]
pub const fn new() -> Self {
Self { count: 0, mean: 0.0 , m2: 0.0 }
}
#[inline]
pub fn update(&mut self, x: f32) -> Result<(), StatsError> {
let x = ensure_finite(x)?;
self.count += 1;
let delta = x - self.mean;
self.mean += delta / self.count as f32;
let delta2 = x - self.mean;
self.m2 += delta * delta2;
Ok(())
}
#[inline]
pub fn mean(&self) -> Result<f32, StatsError> {
if self.count == 0 {
return Err(StatsError::EmptySlice);
}
self.check_state()?;
Ok(self.mean)
}
#[inline]
pub const fn count(&self) -> u32 {
self.count
}
#[inline]
pub fn reset(&mut self) {
self.count = 0;
self.mean = 0.0;
self.m2 = 0.0;
}
#[inline]
fn check_state(&self) -> Result<(), StatsError> {
if self.mean.is_finite() && self.m2.is_finite() {
Ok(())
} else {
Err(StatsError::NonFiniteValue)
}
}
#[inline]
pub fn running_variance(&self) -> Result<f32, StatsError> {
if self.count == 0 {
return Err(StatsError::EmptySlice);
}
self.check_state()?;
let v = self.m2 / self.count as f32;
let v = if v < 0.0 { 0.0 } else { v };
ensure_finite(v)
}
#[inline]
pub fn running_std_dev(&self) -> Result<f32, StatsError> {
let v = self.running_variance()?;
let s = sqrt(v).map_err(|_| StatsError::NonFiniteValue)?;
ensure_finite(s)
}
}
impl Default for StreamingStats {
fn default() -> Self {
Self::new()
}
}
#[inline]
fn kahan_sum_checked(data: &[f32]) -> Result<f32, StatsError> {
let mut sum = 0.0_f32;
let mut comp = 0.0_f32;
for &x in data {
let x = ensure_finite(x)?;
let y = x - comp;
let t = sum + y;
comp = (t - sum) - y;
sum = t;
}
Ok(sum)
}
#[cfg(test)]
mod tests {
use super::*;
const DATA: [f32; 8] = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
#[test]
fn test_mean_reference() {
let m = mean(&DATA).unwrap();
assert!((m - 5.0).abs() < 1e-5, "mean = {m}");
}
#[test]
fn test_mean_single() {
assert!((mean(&[42.0_f32]).unwrap() - 42.0).abs() < 1e-6);
}
#[test]
fn test_mean_empty() {
assert_eq!(mean(&[] as &[f32]), Err(StatsError::EmptySlice));
}
#[test]
fn test_mean_nan() {
assert_eq!(mean(&[1.0, f32::NAN, 3.0]), Err(StatsError::NonFiniteValue));
}
#[test]
fn test_mean_inf() {
assert_eq!(mean(&[1.0, f32::INFINITY]), Err(StatsError::NonFiniteValue));
}
#[test]
fn test_mean_neg_inf() {
assert_eq!(mean(&[f32::NEG_INFINITY]), Err(StatsError::NonFiniteValue));
}
#[test]
fn test_variance_reference() {
let v = variance(&DATA).unwrap();
assert!((v - 4.0).abs() < 1e-4, "variance = {v}");
}
#[test]
fn test_variance_constant() {
let v = variance(&[3.0_f32; 100]).unwrap();
assert!(v.abs() < 1e-5, "variance constante = {v}");
}
#[test]
fn test_variance_empty() {
assert_eq!(variance(&[] as &[f32]), Err(StatsError::EmptySlice));
}
#[test]
fn test_variance_nan() {
assert_eq!(variance(&[1.0, f32::NAN]), Err(StatsError::NonFiniteValue));
}
#[test]
fn test_variance_inf() {
assert_eq!(variance(&[f32::INFINITY, 1.0]), Err(StatsError::NonFiniteValue));
}
#[test]
fn test_std_dev_reference() {
let s = std_dev(&DATA).unwrap();
assert!((s - 2.0).abs() < 1e-4, "std_dev = {s}");
}
#[test]
fn test_std_dev_empty() {
assert_eq!(std_dev(&[] as &[f32]), Err(StatsError::EmptySlice));
}
#[test]
fn test_std_dev_nan() {
assert_eq!(std_dev(&[f32::NAN]), Err(StatsError::NonFiniteValue));
}
#[test]
fn test_streaming_mean_reference() {
let mut acc = StreamingStats::new();
for &x in &DATA { acc.update(x).unwrap(); }
let m = acc.mean().unwrap();
assert!((m - 5.0).abs() < 1e-5, "streaming mean = {m}");
assert_eq!(acc.count(), 8);
}
#[test]
fn test_streaming_mean_empty() {
assert_eq!(StreamingStats::new().mean(), Err(StatsError::EmptySlice));
}
#[test]
fn test_streaming_nan_rejected_state_preserved() {
let mut acc = StreamingStats::new();
acc.update(1.0).unwrap();
acc.update(2.0).unwrap();
assert_eq!(acc.update(f32::NAN), Err(StatsError::NonFiniteValue));
assert_eq!(acc.count(), 2);
assert!((acc.mean().unwrap() - 1.5).abs() < 1e-6);
}
#[test]
fn test_streaming_inf_rejected() {
let mut acc = StreamingStats::new();
acc.update(5.0).unwrap();
assert_eq!(acc.update(f32::INFINITY), Err(StatsError::NonFiniteValue));
assert_eq!(acc.count(), 1);
}
#[test]
fn test_streaming_reset() {
let mut acc = StreamingStats::new();
for &x in &DATA { acc.update(x).unwrap(); }
acc.reset();
assert_eq!(acc.count(), 0);
assert_eq!(acc.mean(), Err(StatsError::EmptySlice));
}
#[test]
fn test_streaming_matches_batch() {
let mut acc = StreamingStats::new();
for &x in &DATA { acc.update(x).unwrap(); }
let batch = mean(&DATA).unwrap();
let stream = acc.mean().unwrap();
assert!((batch - stream).abs() < 1e-5, "batch={batch} stream={stream}");
}
#[test]
fn test_streaming_variance_matches_batch() {
let mut acc = StreamingStats::new();
for &x in &DATA {
acc.update(x).unwrap();
}
let batch = variance(&DATA).unwrap();
let stream = acc.running_variance().unwrap();
assert!((batch - stream).abs() < 1e-4,
"batch={batch}, stream={stream}");
}
#[test]
fn test_streaming_std_dev_matches_batch() {
let mut acc = StreamingStats::new();
for &x in &DATA {
acc.update(x).unwrap();
}
let batch = std_dev(&DATA).unwrap();
let stream = acc.running_std_dev().unwrap();
assert!((batch - stream).abs() < 1e-4,
"batch={batch}, stream={stream}");
}
#[test]
fn test_streaming_large_stability() {
let mut acc = StreamingStats::new();
for i in 0..1_000 {
acc.update(i as f32).unwrap();
}
let mean = acc.mean().unwrap();
assert!((mean - 499.5).abs() < 1e-2);
}
#[test]
fn test_nan_does_not_corrupt_state() {
let mut acc = StreamingStats::new();
acc.update(10.0).unwrap();
acc.update(20.0).unwrap();
let before = acc.mean().unwrap();
assert_eq!(acc.update(f32::NAN), Err(StatsError::NonFiniteValue));
let after = acc.mean().unwrap();
assert!((before - after).abs() < 1e-6);
assert_eq!(acc.count(), 2);
}
#[test]
fn test_count_monotonic() {
let mut acc = StreamingStats::new();
for i in 1..100 {
acc.update(i as f32).unwrap();
assert_eq!(acc.count(), i);
}
}
#[test]
fn test_constant_values_zero_variance() {
let mut acc = StreamingStats::new();
for _ in 0..100 {
acc.update(5.0).unwrap();
}
let v = acc.running_variance().unwrap();
assert!(v.abs() < 1e-6);
}
#[test]
fn test_inf_inputs_rejected() {
let mut acc = StreamingStats::new();
assert_eq!(acc.update(f32::INFINITY), Err(StatsError::NonFiniteValue));
assert_eq!(acc.update(f32::NEG_INFINITY), Err(StatsError::NonFiniteValue));
assert_eq!(acc.count(), 0);
}
#[test]
fn test_streaming_long_run_stability() {
let mut acc = StreamingStats::new();
let mut sum = 0.0;
for i in 1..10_000 {
let x = (i as f32).sin() * 100.0;
acc.update(x).unwrap();
sum += x;
}
let mean_manual = sum / 10_000.0;
let mean_stream = acc.mean().unwrap();
assert!((mean_manual - mean_stream).abs() < 1e-3);
}
}