1use core::{
2 cmp::PartialOrd,
3 fmt::Debug,
4 mem::size_of,
5 ops::{Add, Div, Mul, Sub},
6};
7use heapless::HistoryBuffer;
8
9pub struct MovingAverage<T, TCALC, const N: usize>
88where
89 T: Sized + PartialEq + TryFrom<TCALC, Error: Debug> + Clone + Copy,
90 TCALC: Sized
91 + Add<TCALC, Output = TCALC>
92 + Sub<TCALC, Output = TCALC>
93 + Div<Output = TCALC>
94 + Mul<Output = TCALC>
95 + PartialEq
96 + PartialOrd
97 + From<T>
98 + TryFrom<usize, Error: Debug>
99 + Clone
100 + Copy,
101{
102 num: TCALC,
103 sum: Option<TCALC>,
104 buffer: HistoryBuffer<T, N>,
105}
106
107#[expect(clippy::unwrap_used, reason = "Made safe by compile-time asserts")]
115impl<T, TCALC, const N: usize> Default for MovingAverage<T, TCALC, N>
116where
117 T: Sized + PartialEq + TryFrom<TCALC, Error: Debug> + Clone + Copy,
118 TCALC: Sized
119 + Add<TCALC, Output = TCALC>
120 + Sub<TCALC, Output = TCALC>
121 + Div<Output = TCALC>
122 + Mul<Output = TCALC>
123 + PartialEq
124 + PartialOrd
125 + From<T>
126 + TryFrom<usize, Error: Debug>
127 + Clone
128 + Copy,
129{
130 #[expect(
131 clippy::cast_possible_truncation,
132 reason = "no size_of return bigger than u32"
133 )]
134 fn default() -> Self {
135 const {
136 assert!(
137 size_of::<TCALC>() > size_of::<T>(),
138 "TCALC must be larger than T"
139 );
140 assert!(N > 0, "N must be non-zero");
141 }
142 debug_assert!(
143 (2_u128.pow((size_of::<T>() as u32) * 8) * u128::try_from(N).unwrap())
144 <= 2_u128.pow((size_of::<TCALC>() as u32) * 8),
145 "N * T.max() must fit in TCALC"
146 );
147 Self {
148 num: TCALC::try_from(N).unwrap(),
149 sum: None,
150 buffer: HistoryBuffer::new(),
151 }
152 }
153}
154
155impl<T, TCALC, const N: usize> MovingAverage<T, TCALC, N>
156where
157 T: Sized + PartialEq + TryFrom<TCALC, Error: Debug> + Clone + Copy,
158 TCALC: Sized
159 + Add<TCALC, Output = TCALC>
160 + Sub<TCALC, Output = TCALC>
161 + Div<Output = TCALC>
162 + Mul<Output = TCALC>
163 + PartialEq
164 + PartialOrd
165 + From<T>
166 + TryFrom<usize, Error: Debug>
167 + Clone
168 + Copy,
169{
170 #[must_use]
171 pub fn new() -> Self {
172 Self::default()
173 }
174
175 #[must_use]
179 pub fn average(&mut self, input: T) -> T {
180 let new_value = TCALC::from(input);
181 let prev_sum = self.get_or_init_and_get_sum(input);
182 let remove = self.insert_new_value_pop_oldest_value(input);
183 self.create_average(new_value, prev_sum, remove)
184 }
185
186 fn get_or_init_and_get_sum(&mut self, input: T) -> TCALC {
187 let new_value = TCALC::from(input);
188 if let Some(sum) = self.sum {
189 sum
190 } else {
191 for _ in 0..N {
192 self.buffer.write(input);
193 }
194 self.num * new_value
195 }
196 }
197
198 fn insert_new_value_pop_oldest_value(&mut self, input: T) -> TCALC {
199 let remove = self.get_remove_value();
200 self.buffer.write(input);
201 remove
202 }
203
204 #[expect(clippy::expect_used, reason = "Made safe by compile-time asserts")]
205 fn create_average(&mut self, new_value: TCALC, prev_sum: TCALC, remove: TCALC) -> T {
206 let new_sum = prev_sum + new_value - remove;
207 self.sum = Some(new_sum);
208 let average_as_tcalc = new_sum / self.num;
209 T::try_from(average_as_tcalc).expect("Converting from TCALC to T should be safe")
210 }
211
212 #[expect(clippy::expect_used, reason = "Made safe by compile-time asserts")]
213 fn get_remove_value(&self) -> TCALC {
214 #[cfg(test)]
215 assert!(
216 self.buffer.len() == N,
217 "Buffer len {} different than capacity {N}.",
218 self.buffer.len()
219 );
220
221 TCALC::from(
222 *self
223 .buffer
224 .oldest_ordered()
225 .next()
226 .expect("Buffer should be full"),
227 )
228 }
229}
230
231#[expect(clippy::let_underscore_must_use, reason = "Desirable in tests")]
232#[expect(clippy::let_underscore_untyped, reason = "Desirable in tests")]
233#[expect(clippy::cast_possible_truncation, reason = "Desirable in tests")]
234#[expect(clippy::cast_possible_wrap, reason = "Desirable in tests")]
235#[cfg(test)]
236mod tests {
237 use super::MovingAverage;
238
239 #[test]
240 fn given_new_moving_average_when_average_value_then_return_same_value() {
241 let mut sut = MovingAverage::<u32, u64, 1>::new();
242 let expected: u32 = 44;
243 assert_eq!(expected, sut.average(expected));
244 }
245
246 #[test]
247 fn given_two_item_moving_average_when_average_twice_value_then_return_average_of_those_values()
248 {
249 let mut sut = MovingAverage::<u32, u64, 2>::new();
250 let first: u32 = 22;
251 let second: u32 = 44;
252 let expected = (first + second) / 2;
253 let _ = sut.average(first);
254 assert_eq!(expected, sut.average(second));
255 }
256
257 #[test]
258 fn given_two_item_moving_average_when_average_called_thrice_then_return_average_of_the_last_two_values()
259 {
260 let mut sut = MovingAverage::<u32, u64, 2>::new();
261 let first: u32 = 22;
262 let second: u32 = 44;
263 let third: u32 = 66;
264 let expected = (second + third) / 2;
265 let _ = sut.average(first);
266 let _ = sut.average(second);
267 assert_eq!(expected, sut.average(third));
268 }
269
270 #[test]
271 fn given_two_signed_item_moving_average_when_average_called_thrice_then_return_average_of_the_last_two_values()
272 {
273 let mut sut = MovingAverage::<i32, i64, 2>::new();
274 let first: i32 = -22;
275 let second: i32 = 44;
276 let third: i32 = -66;
277 let expected = (second + third) / 2_i32;
278 let _ = sut.average(first);
279 let _ = sut.average(second);
280 assert_eq!(expected, sut.average(third));
281 }
282
283 #[test]
284 fn given_large_item_moving_average_when_average_called_thrice_then_return_average_of_the_last_two_values()
285 {
286 const DEPTH: usize = 128;
287 let mut sut = MovingAverage::<i32, i64, DEPTH>::new();
288 let first: i32 = -22;
289 let second: i32 = 44;
290 let third: i32 = -66;
291 let expected = (first + second + third + (((DEPTH as i32) - 3_i32) * first)) / DEPTH as i32;
292 let _ = sut.average(first);
293 let _ = sut.average(second);
294 assert_eq!(expected, sut.average(third));
295 }
296
297 #[test]
300 fn given_enough_items_to_roll_over_when_average_called_then_return_average_of_the_correct_set_of_values()
301 {
302 let mut sut = MovingAverage::<u16, u32, 4>::new();
303 let sequence = [
304 100_u16, 200_u16, 300_u16, 400_u16, 100_u16, 200_u16, 300_u16, 400_u16, 100_u16,
305 200_u16, 300_u16, 400_u16,
306 ];
307 let expected = [
308 100_u16, 125_u16, 175_u16, 250_u16, 250_u16, 250_u16, 250_u16, 250_u16, 250_u16,
309 250_u16, 250_u16, 250_u16,
310 ];
311
312 for (i, val) in sequence.iter().enumerate() {
313 let avg = sut.average(*val);
314 assert_eq!(
315 expected[i], avg,
316 "Failed at {i}, is {avg}, should be {}",
317 expected[i]
318 );
319 }
320 }
321
322 #[test]
323 #[should_panic(expected = "N * T.max() must fit in TCALC")]
324 fn confirm_n_times_t_max_fits_in_tcalc() {
325 let _sut = MovingAverage::<u8, u16, 512>::new();
326 }
327
328 }