1#![allow(missing_docs)]
2
3mod ewma_gauge;
4mod time_ewma;
5
6pub use ewma_gauge::{EwmaGauge, TimeEwmaGauge};
7pub use time_ewma::TimeEwma;
8
9use std::sync::atomic::Ordering;
10
11use crate::atomic::AtomicF64;
12
13pub const DEFAULT_EWMA_ALPHA: f64 = 0.9;
15
16#[derive(Clone, Copy, Debug)]
18pub struct Ewma {
19 average: Option<f64>,
20 alpha: f64,
21}
22
23impl Ewma {
24 #[must_use]
25 pub const fn new(alpha: f64) -> Self {
26 let average = None;
27 Self { average, alpha }
28 }
29
30 #[must_use]
31 pub const fn average(&self) -> Option<f64> {
32 self.average
33 }
34
35 pub fn update(&mut self, point: f64) -> f64 {
37 let average = match self.average {
38 None => point,
39 Some(avg) => point.mul_add(self.alpha, avg * (1.0 - self.alpha)),
40 };
41 self.average = Some(average);
42 average
43 }
44}
45
46#[derive(Clone, Copy, Debug)]
48pub struct EwmaDefault {
49 average: f64,
50 alpha: f64,
51}
52
53impl EwmaDefault {
54 #[must_use]
55 pub const fn new(alpha: f64, initial_value: f64) -> Self {
56 Self {
57 average: initial_value,
58 alpha,
59 }
60 }
61
62 #[must_use]
63 pub const fn average(&self) -> f64 {
64 self.average
65 }
66
67 pub fn update(&mut self, point: f64) -> f64 {
69 self.average = point.mul_add(self.alpha, self.average * (1.0 - self.alpha));
70 self.average
71 }
72}
73
74#[derive(Clone, Copy, Debug)]
76pub struct EwmaVar {
77 state: Option<MeanVariance>,
78 alpha: f64,
79}
80
81#[derive(Clone, Copy, Debug, PartialEq)]
82pub struct MeanVariance {
83 pub mean: f64,
84 pub variance: f64,
85}
86
87impl EwmaVar {
88 #[must_use]
89 pub const fn new(alpha: f64) -> Self {
90 let state = None;
91 Self { state, alpha }
92 }
93
94 #[must_use]
95 pub const fn state(&self) -> Option<MeanVariance> {
96 self.state
97 }
98
99 #[cfg(test)]
100 #[must_use]
101 pub fn average(&self) -> Option<f64> {
102 self.state.map(|state| state.mean)
103 }
104
105 #[cfg(test)]
106 #[must_use]
107 pub fn variance(&self) -> Option<f64> {
108 self.state.map(|state| state.variance)
109 }
110
111 pub fn update(&mut self, point: f64) -> MeanVariance {
113 let (mean, variance) = match self.state {
114 None => (point, 0.0),
115 Some(state) => {
116 let difference = point - state.mean;
117 let increment = self.alpha * difference;
118 (
119 state.mean + increment,
120 (1.0 - self.alpha) * difference.mul_add(increment, state.variance),
121 )
122 }
123 };
124 let state = MeanVariance { mean, variance };
125 self.state = Some(state);
126 state
127 }
128}
129
130#[derive(Clone, Copy, Debug, Default)]
132pub struct Mean {
133 mean: f64,
134 count: usize,
135}
136
137impl Mean {
138 #[expect(
140 clippy::cast_precision_loss,
141 reason = "We have to convert count to f64 for the calculation, it's okay to lose precision for very large counts."
142 )]
143 pub fn update(&mut self, point: f64) {
144 self.count += 1;
145 self.mean += (point - self.mean) / self.count as f64;
146 }
147
148 #[must_use]
149 pub const fn average(&self) -> Option<f64> {
150 match self.count {
151 0 => None,
152 _ => Some(self.mean),
153 }
154 }
155}
156
157#[derive(Debug)]
159pub struct AtomicEwma {
160 average: AtomicF64,
161 alpha: f64,
162}
163
164impl AtomicEwma {
165 #[must_use]
166 pub fn new(alpha: f64) -> Self {
167 Self {
168 average: AtomicF64::new(f64::NAN),
169 alpha,
170 }
171 }
172
173 pub fn update(&self, point: f64) -> f64 {
174 let mut result = f64::NAN;
175 self.average
176 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
177 let average = if current.is_nan() {
178 point
179 } else {
180 point.mul_add(self.alpha, current * (1.0 - self.alpha))
181 };
182 result = average;
183 average
184 });
185 result
186 }
187
188 pub fn average(&self) -> Option<f64> {
189 let value = self.average.load(Ordering::Relaxed);
190 if value.is_nan() { None } else { Some(value) }
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn mean_update_works() {
200 let mut mean = Mean::default();
201 assert_eq!(mean.average(), None);
202 mean.update(0.0);
203 assert_eq!(mean.average(), Some(0.0));
204 mean.update(2.0);
205 assert_eq!(mean.average(), Some(1.0));
206 mean.update(4.0);
207 assert_eq!(mean.average(), Some(2.0));
208 }
209
210 #[test]
211 #[expect(clippy::float_cmp, reason = "none of the values will be rounded")]
212 fn ewma_update_works() {
213 let mut mean = Ewma::new(0.5);
214 assert_eq!(mean.average(), None);
215 assert_eq!(mean.update(2.0), 2.0);
216 assert_eq!(mean.average(), Some(2.0));
217 assert_eq!(mean.update(2.0), 2.0);
218 assert_eq!(mean.average(), Some(2.0));
219 assert_eq!(mean.update(1.0), 1.5);
220 assert_eq!(mean.average(), Some(1.5));
221 assert_eq!(mean.update(2.0), 1.75);
222 assert_eq!(mean.average(), Some(1.75));
223
224 assert_eq!(mean.average, Some(1.75));
225 }
226
227 #[test]
228 fn ewma_variance_update_works() {
229 let mut mean = EwmaVar::new(0.5);
230 assert_eq!(mean.average(), None);
231 assert_eq!(mean.variance(), None);
232 mean.update(2.0);
233 assert_eq!(mean.average(), Some(2.0));
234 assert_eq!(mean.variance(), Some(0.0));
235 mean.update(2.0);
236 assert_eq!(mean.average(), Some(2.0));
237 assert_eq!(mean.variance(), Some(0.0));
238 mean.update(1.0);
239 assert_eq!(mean.average(), Some(1.5));
240 assert_eq!(mean.variance(), Some(0.25));
241 mean.update(2.0);
242 assert_eq!(mean.average(), Some(1.75));
243 assert_eq!(mean.variance(), Some(0.1875));
244
245 assert_eq!(
246 mean.state,
247 Some(MeanVariance {
248 mean: 1.75,
249 variance: 0.1875
250 })
251 );
252 }
253
254 #[test]
255 #[expect(clippy::float_cmp, reason = "none of the values will be rounded")]
256 fn atomic_ewma_update_works() {
257 let ewma = AtomicEwma::new(0.5);
258 assert_eq!(ewma.average(), None);
259 assert_eq!(ewma.update(2.0), 2.0);
260 assert_eq!(ewma.average(), Some(2.0));
261 assert_eq!(ewma.update(1.0), 1.5);
262 assert_eq!(ewma.average(), Some(1.5));
263 }
264}