embedded_stats_f32/
lib.rs1#![no_std]
41#![forbid(unsafe_code)]
42#![warn(missing_docs)]
43
44use embedded_f32_sqrt::sqrt;
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum StatsError {
49 EmptySlice,
51 NonFiniteValue,
53}
54
55#[inline]
61fn ensure_finite(x: f32) -> Result<f32, StatsError> {
62 if x.is_finite() {
63 Ok(x)
64 } else {
65 Err(StatsError::NonFiniteValue)
66 }
67}
68
69pub fn mean(data: &[f32]) -> Result<f32, StatsError> {
92 if data.is_empty() {
93 return Err(StatsError::EmptySlice);
94 }
95 let sum = kahan_sum_checked(data)?;
96 Ok(sum / data.len() as f32)
97}
98
99pub fn variance(data: &[f32]) -> Result<f32, StatsError> {
118 if data.is_empty() {
119 return Err(StatsError::EmptySlice);
120 }
121
122 let m = kahan_sum_checked(data)? / data.len() as f32;
123
124 let mut sum = 0.0_f32;
125 let mut comp = 0.0_f32;
126
127 for &x in data {
128 let x = ensure_finite(x)?;
129 let d = x - m;
130 let y = d * d - comp;
131 let t = sum + y;
132 comp = (t - sum) - y;
133 sum = t;
134 }
135
136 let result = sum / data.len() as f32;
137 ensure_finite(result)
138}
139
140pub fn std_dev(data: &[f32]) -> Result<f32, StatsError> {
160 let v = variance(data)?;
161
162 let v = if v < 0.0 { 0.0 } else { v };
164 let s = sqrt(v).map_err(|_| StatsError::NonFiniteValue)?;
166 ensure_finite(s)
167}
168
169#[derive(Debug, Clone, Copy)]
196pub struct StreamingStats {
197 count: u32,
198 mean: f32,
199 m2: f32,
200}
201
202impl StreamingStats {
203 #[inline]
205 pub const fn new() -> Self {
206 Self { count: 0, mean: 0.0 , m2: 0.0 }
207 }
208
209 #[inline]
214 pub fn update(&mut self, x: f32) -> Result<(), StatsError> {
215 let x = ensure_finite(x)?;
216
217 self.count += 1;
218
219 let delta = x - self.mean;
220 self.mean += delta / self.count as f32;
221 let delta2 = x - self.mean;
222
223 self.m2 += delta * delta2;
224
225 Ok(())
226 }
227
228 #[inline]
236 pub fn mean(&self) -> Result<f32, StatsError> {
237 if self.count == 0 {
238 return Err(StatsError::EmptySlice);
239 }
240 self.check_state()?;
241 Ok(self.mean)
242 }
243
244 #[inline]
246 pub const fn count(&self) -> u32 {
247 self.count
248 }
249
250 #[inline]
252 pub fn reset(&mut self) {
253 self.count = 0;
254 self.mean = 0.0;
255 self.m2 = 0.0;
257 }
258
259 #[inline]
261 fn check_state(&self) -> Result<(), StatsError> {
262 if self.mean.is_finite() && self.m2.is_finite() {
263 Ok(())
264 } else {
265 Err(StatsError::NonFiniteValue)
266 }
267 }
268 #[inline]
270 pub fn running_variance(&self) -> Result<f32, StatsError> {
271 if self.count == 0 {
272 return Err(StatsError::EmptySlice);
273 }
274
275 self.check_state()?;
276
277 let v = self.m2 / self.count as f32;
278 let v = if v < 0.0 { 0.0 } else { v };
279
280 ensure_finite(v)
281 }
282
283#[inline]
285pub fn running_std_dev(&self) -> Result<f32, StatsError> {
286 let v = self.running_variance()?;
287 let s = sqrt(v).map_err(|_| StatsError::NonFiniteValue)?;
288 ensure_finite(s)
289}
290
291
292}
293
294impl Default for StreamingStats {
295 fn default() -> Self {
296 Self::new()
297 }
298}
299
300#[inline]
307fn kahan_sum_checked(data: &[f32]) -> Result<f32, StatsError> {
308 let mut sum = 0.0_f32;
309 let mut comp = 0.0_f32;
310
311 for &x in data {
312 let x = ensure_finite(x)?;
313 let y = x - comp;
314 let t = sum + y;
315 comp = (t - sum) - y;
316 sum = t;
317 }
318
319 Ok(sum)
320}
321
322
323#[cfg(test)]
326mod tests {
327 use super::*;
328
329 const DATA: [f32; 8] = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
331
332 #[test]
335 fn test_mean_reference() {
336 let m = mean(&DATA).unwrap();
337 assert!((m - 5.0).abs() < 1e-5, "mean = {m}");
338 }
339
340 #[test]
341 fn test_mean_single() {
342 assert!((mean(&[42.0_f32]).unwrap() - 42.0).abs() < 1e-6);
343 }
344
345 #[test]
346 fn test_mean_empty() {
347 assert_eq!(mean(&[] as &[f32]), Err(StatsError::EmptySlice));
348 }
349
350 #[test]
351 fn test_mean_nan() {
352 assert_eq!(mean(&[1.0, f32::NAN, 3.0]), Err(StatsError::NonFiniteValue));
353 }
354
355 #[test]
356 fn test_mean_inf() {
357 assert_eq!(mean(&[1.0, f32::INFINITY]), Err(StatsError::NonFiniteValue));
358 }
359
360 #[test]
361 fn test_mean_neg_inf() {
362 assert_eq!(mean(&[f32::NEG_INFINITY]), Err(StatsError::NonFiniteValue));
363 }
364
365 #[test]
368 fn test_variance_reference() {
369 let v = variance(&DATA).unwrap();
370 assert!((v - 4.0).abs() < 1e-4, "variance = {v}");
371 }
372
373 #[test]
374 fn test_variance_constant() {
375 let v = variance(&[3.0_f32; 100]).unwrap();
376 assert!(v.abs() < 1e-5, "variance constante = {v}");
377 }
378
379 #[test]
380 fn test_variance_empty() {
381 assert_eq!(variance(&[] as &[f32]), Err(StatsError::EmptySlice));
382 }
383
384 #[test]
385 fn test_variance_nan() {
386 assert_eq!(variance(&[1.0, f32::NAN]), Err(StatsError::NonFiniteValue));
387 }
388
389 #[test]
390 fn test_variance_inf() {
391 assert_eq!(variance(&[f32::INFINITY, 1.0]), Err(StatsError::NonFiniteValue));
392 }
393
394 #[test]
397 fn test_std_dev_reference() {
398 let s = std_dev(&DATA).unwrap();
399 assert!((s - 2.0).abs() < 1e-4, "std_dev = {s}");
400 }
401
402 #[test]
403 fn test_std_dev_empty() {
404 assert_eq!(std_dev(&[] as &[f32]), Err(StatsError::EmptySlice));
405 }
406
407 #[test]
408 fn test_std_dev_nan() {
409 assert_eq!(std_dev(&[f32::NAN]), Err(StatsError::NonFiniteValue));
410 }
411
412 #[test]
415 fn test_streaming_mean_reference() {
416 let mut acc = StreamingStats::new();
417 for &x in &DATA { acc.update(x).unwrap(); }
418 let m = acc.mean().unwrap();
419 assert!((m - 5.0).abs() < 1e-5, "streaming mean = {m}");
420 assert_eq!(acc.count(), 8);
421 }
422
423 #[test]
424 fn test_streaming_mean_empty() {
425 assert_eq!(StreamingStats::new().mean(), Err(StatsError::EmptySlice));
426 }
427
428 #[test]
429 fn test_streaming_nan_rejected_state_preserved() {
430 let mut acc = StreamingStats::new();
431 acc.update(1.0).unwrap();
432 acc.update(2.0).unwrap();
433 assert_eq!(acc.update(f32::NAN), Err(StatsError::NonFiniteValue));
435 assert_eq!(acc.count(), 2);
437 assert!((acc.mean().unwrap() - 1.5).abs() < 1e-6);
438 }
439
440 #[test]
441 fn test_streaming_inf_rejected() {
442 let mut acc = StreamingStats::new();
443 acc.update(5.0).unwrap();
444 assert_eq!(acc.update(f32::INFINITY), Err(StatsError::NonFiniteValue));
445 assert_eq!(acc.count(), 1);
446 }
447
448 #[test]
449 fn test_streaming_reset() {
450 let mut acc = StreamingStats::new();
451 for &x in &DATA { acc.update(x).unwrap(); }
452 acc.reset();
453 assert_eq!(acc.count(), 0);
454 assert_eq!(acc.mean(), Err(StatsError::EmptySlice));
455 }
456
457 #[test]
458 fn test_streaming_matches_batch() {
459 let mut acc = StreamingStats::new();
460 for &x in &DATA { acc.update(x).unwrap(); }
461 let batch = mean(&DATA).unwrap();
462 let stream = acc.mean().unwrap();
463 assert!((batch - stream).abs() < 1e-5, "batch={batch} stream={stream}");
464 }
465
466
467 #[test]
468 fn test_streaming_variance_matches_batch() {
469 let mut acc = StreamingStats::new();
470 for &x in &DATA {
471 acc.update(x).unwrap();
472 }
473
474 let batch = variance(&DATA).unwrap();
475 let stream = acc.running_variance().unwrap();
476
477 assert!((batch - stream).abs() < 1e-4,
478 "batch={batch}, stream={stream}");
479 }
480
481 #[test]
482 fn test_streaming_std_dev_matches_batch() {
483 let mut acc = StreamingStats::new();
484 for &x in &DATA {
485 acc.update(x).unwrap();
486 }
487
488 let batch = std_dev(&DATA).unwrap();
489 let stream = acc.running_std_dev().unwrap();
490
491 assert!((batch - stream).abs() < 1e-4,
492 "batch={batch}, stream={stream}");
493 }
494
495 #[test]
496 fn test_streaming_large_stability() {
497 let mut acc = StreamingStats::new();
498
499 for i in 0..1_000 {
500 acc.update(i as f32).unwrap();
501 }
502
503 let mean = acc.mean().unwrap();
504
505 assert!((mean - 499.5).abs() < 1e-2);
507 }
508
509
510 #[test]
511 fn test_nan_does_not_corrupt_state() {
512 let mut acc = StreamingStats::new();
513
514 acc.update(10.0).unwrap();
515 acc.update(20.0).unwrap();
516
517 let before = acc.mean().unwrap();
518
519 assert_eq!(acc.update(f32::NAN), Err(StatsError::NonFiniteValue));
520
521 let after = acc.mean().unwrap();
522
523 assert!((before - after).abs() < 1e-6);
524 assert_eq!(acc.count(), 2);
525 }
526
527
528 #[test]
529 fn test_count_monotonic() {
530 let mut acc = StreamingStats::new();
531
532 for i in 1..100 {
533 acc.update(i as f32).unwrap();
534 assert_eq!(acc.count(), i);
535 }
536 }
537
538
539
540 #[test]
541 fn test_constant_values_zero_variance() {
542 let mut acc = StreamingStats::new();
543
544 for _ in 0..100 {
545 acc.update(5.0).unwrap();
546 }
547
548 let v = acc.running_variance().unwrap();
549 assert!(v.abs() < 1e-6);
550 }
551
552
553 #[test]
554 fn test_inf_inputs_rejected() {
555 let mut acc = StreamingStats::new();
556
557 assert_eq!(acc.update(f32::INFINITY), Err(StatsError::NonFiniteValue));
558 assert_eq!(acc.update(f32::NEG_INFINITY), Err(StatsError::NonFiniteValue));
559
560 assert_eq!(acc.count(), 0);
561 }
562
563
564 #[test]
565 fn test_streaming_long_run_stability() {
566 let mut acc = StreamingStats::new();
567
568 let mut sum = 0.0;
569 for i in 1..10_000 {
570 let x = (i as f32).sin() * 100.0;
571 acc.update(x).unwrap();
572 sum += x;
573 }
574
575 let mean_manual = sum / 10_000.0;
576 let mean_stream = acc.mean().unwrap();
577
578 assert!((mean_manual - mean_stream).abs() < 1e-3);
579 }
580}