Skip to main content

autocore_std/fb/
running_average.rs

1use std::fmt::Debug;
2use std::ops::{AddAssign, Div};
3
4/// Trait for types that can be used with [`RunningAverage`].
5///
6/// Provides conversion from a `usize` count, needed to compute the average.
7/// Implemented for all standard numeric primitives.
8pub trait Averageable: Copy + Default + Debug + AddAssign + Div<Output = Self> {
9    /// Convert a `usize` count to `Self`.
10    fn from_count(count: usize) -> Self;
11}
12
13macro_rules! impl_averageable {
14    ($($t:ty),*) => {
15        $(
16            impl Averageable for $t {
17                fn from_count(count: usize) -> Self {
18                    count as $t
19                }
20            }
21        )*
22    };
23}
24
25impl_averageable!(f32, f64, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, isize, usize);
26
27/// Running Average (FB_RunningAverage)
28///
29/// Accumulates values and computes their arithmetic mean.
30/// Stores only the sum and count internally — no heap allocation.
31///
32/// Generic over any numeric type that implements [`Averageable`]
33/// (all standard primitives: `f32`, `f64`, `i32`, `u64`, etc.).
34///
35/// # Example
36///
37/// ```
38/// use autocore_std::fb::RunningAverage;
39///
40/// let mut avg = RunningAverage::<f64>::new();
41///
42/// avg.append(10.0);
43/// avg.append(20.0);
44/// avg.append(30.0);
45///
46/// assert_eq!(avg.count(), 3);
47/// assert!((avg.average() - 20.0).abs() < f64::EPSILON);
48///
49/// avg.reset();
50/// assert_eq!(avg.count(), 0);
51/// assert_eq!(avg.average(), 0.0);
52/// ```
53///
54/// # Use Cases
55///
56/// - Smoothing noisy sensor readings
57/// - Computing mean cycle times
58/// - Averaging analog input samples over N scans
59#[derive(Debug, Clone)]
60pub struct RunningAverage<T: Averageable> {
61    sum: T,
62    count: usize,
63}
64
65impl<T: Averageable> RunningAverage<T> {
66    /// Creates a new running average with zero samples.
67    ///
68    /// # Example
69    ///
70    /// ```
71    /// use autocore_std::fb::RunningAverage;
72    ///
73    /// let avg = RunningAverage::<f64>::new();
74    /// assert_eq!(avg.count(), 0);
75    /// assert_eq!(avg.average(), 0.0);
76    /// ```
77    pub fn new() -> Self {
78        Self {
79            sum: T::default(),
80            count: 0,
81        }
82    }
83
84    /// Appends a value to the running average.
85    ///
86    /// # Arguments
87    ///
88    /// * `value` - The value to include in the average
89    ///
90    /// # Example
91    ///
92    /// ```
93    /// use autocore_std::fb::RunningAverage;
94    ///
95    /// let mut avg = RunningAverage::<f64>::new();
96    /// avg.append(5.0);
97    /// avg.append(15.0);
98    /// assert_eq!(avg.count(), 2);
99    /// assert!((avg.average() - 10.0).abs() < f64::EPSILON);
100    /// ```
101    pub fn append(&mut self, value: T) {
102        self.sum += value;
103        self.count += 1;
104    }
105
106    /// Resets the running average, clearing all accumulated data.
107    ///
108    /// # Example
109    ///
110    /// ```
111    /// use autocore_std::fb::RunningAverage;
112    ///
113    /// let mut avg = RunningAverage::<f64>::new();
114    /// avg.append(42.0);
115    /// assert_eq!(avg.count(), 1);
116    ///
117    /// avg.reset();
118    /// assert_eq!(avg.count(), 0);
119    /// assert_eq!(avg.average(), 0.0);
120    /// ```
121    pub fn reset(&mut self) {
122        self.sum = T::default();
123        self.count = 0;
124    }
125
126    /// Returns the arithmetic mean of all appended values.
127    ///
128    /// Returns the default value for `T` (zero) when no values have been
129    /// appended. For integer types, the result is truncated (integer division).
130    ///
131    /// # Example
132    ///
133    /// ```
134    /// use autocore_std::fb::RunningAverage;
135    ///
136    /// let mut avg = RunningAverage::<f64>::new();
137    /// assert_eq!(avg.average(), 0.0); // No values yet
138    ///
139    /// avg.append(10.0);
140    /// avg.append(20.0);
141    /// assert!((avg.average() - 15.0).abs() < f64::EPSILON);
142    /// ```
143    pub fn average(&self) -> T {
144        if self.count == 0 {
145            return T::default();
146        }
147        self.sum / T::from_count(self.count)
148    }
149
150    /// Returns the number of values appended since creation or the last reset.
151    ///
152    /// # Example
153    ///
154    /// ```
155    /// use autocore_std::fb::RunningAverage;
156    ///
157    /// let mut avg = RunningAverage::<f32>::new();
158    /// assert_eq!(avg.count(), 0);
159    ///
160    /// avg.append(1.0);
161    /// avg.append(2.0);
162    /// assert_eq!(avg.count(), 2);
163    /// ```
164    pub fn count(&self) -> usize {
165        self.count
166    }
167}
168
169impl<T: Averageable> Default for RunningAverage<T> {
170    fn default() -> Self {
171        Self::new()
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn test_empty_average() {
181        let avg = RunningAverage::<f64>::new();
182        assert_eq!(avg.count(), 0);
183        assert_eq!(avg.average(), 0.0);
184    }
185
186    #[test]
187    fn test_single_value() {
188        let mut avg = RunningAverage::<f64>::new();
189        avg.append(42.0);
190        assert_eq!(avg.count(), 1);
191        assert!((avg.average() - 42.0).abs() < f64::EPSILON);
192    }
193
194    #[test]
195    fn test_multiple_values() {
196        let mut avg = RunningAverage::<f64>::new();
197        avg.append(10.0);
198        avg.append(20.0);
199        avg.append(30.0);
200        assert_eq!(avg.count(), 3);
201        assert!((avg.average() - 20.0).abs() < f64::EPSILON);
202    }
203
204    #[test]
205    fn test_reset() {
206        let mut avg = RunningAverage::<f64>::new();
207        avg.append(100.0);
208        avg.append(200.0);
209        assert_eq!(avg.count(), 2);
210
211        avg.reset();
212        assert_eq!(avg.count(), 0);
213        assert_eq!(avg.average(), 0.0);
214
215        avg.append(50.0);
216        assert_eq!(avg.count(), 1);
217        assert!((avg.average() - 50.0).abs() < f64::EPSILON);
218    }
219
220    #[test]
221    fn test_f32() {
222        let mut avg = RunningAverage::<f32>::new();
223        avg.append(1.0);
224        avg.append(2.0);
225        avg.append(3.0);
226        assert!((avg.average() - 2.0).abs() < f32::EPSILON);
227    }
228
229    #[test]
230    fn test_integer_truncation() {
231        let mut avg = RunningAverage::<i32>::new();
232        avg.append(1);
233        avg.append(2);
234        // Integer division: (1 + 2) / 2 = 1
235        assert_eq!(avg.average(), 1);
236    }
237
238    #[test]
239    fn test_default_trait() {
240        let avg = RunningAverage::<f64>::default();
241        assert_eq!(avg.count(), 0);
242        assert_eq!(avg.average(), 0.0);
243    }
244}