Skip to main content

rill_ml/stats/
sum.rs

1//! Online sum of observations.
2//!
3//! Time complexity per update: `O(1)`. Space complexity: `O(1)`.
4
5use crate::error::{RillError, checked_finite_add, checked_increment, ensure_finite};
6use crate::traits::OnlineStatistic;
7
8/// A running sum.
9#[derive(Debug, Clone, Default)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11pub struct Sum {
12    sum: f64,
13    count: u64,
14}
15
16impl Sum {
17    /// Create a new empty sum accumulator.
18    pub const fn new() -> Self {
19        Self { sum: 0.0, count: 0 }
20    }
21
22    /// Current sum.
23    pub const fn value(&self) -> f64 {
24        self.sum
25    }
26
27    /// Number of observations.
28    pub const fn count(&self) -> u64 {
29        self.count
30    }
31}
32
33impl OnlineStatistic for Sum {
34    fn update(&mut self, value: f64) -> Result<(), RillError> {
35        ensure_finite("value", value)?;
36        let next_sum = checked_finite_add(self.sum, value, "sum")?;
37        let next_count = checked_increment(self.count, "sum sample")?;
38        self.sum = next_sum;
39        self.count = next_count;
40        Ok(())
41    }
42
43    fn samples_seen(&self) -> u64 {
44        self.count
45    }
46
47    fn reset(&mut self) {
48        self.sum = 0.0;
49        self.count = 0;
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn sum_accumulates() {
59        let mut s = Sum::new();
60        s.update(1.5).unwrap();
61        s.update(2.5).unwrap();
62        assert_eq!(s.value(), 4.0);
63    }
64
65    #[test]
66    fn sum_rejects_overflow_without_mutating_state() {
67        let mut s = Sum::new();
68        s.update(f64::MAX).unwrap();
69        assert!(s.update(f64::MAX).is_err());
70        assert_eq!(s.value(), f64::MAX);
71        assert_eq!(s.count(), 1);
72    }
73}