rust
49 lines · 8 steps
A time-decayed moving average in Rust
An exponentially weighted moving average whose smoothing factor adapts to the real time elapsed between samples.
Explained by
highlit
1use std::time::{Duration, Instant};
2
3pub struct Ewma {
4 alpha: f64,
5 value: Option<f64>,
6 last_update: Option<Instant>,
7 half_life: Duration,
8}
9
10impl Ewma {
11 pub fn new(half_life: Duration) -> Self {
12 Ewma {
13 alpha: 0.0,
14 value: None,
15 last_update: None,
16 half_life,
17 }
18 }
19
20 pub fn observe(&mut self, sample: f64) {
21 self.observe_at(sample, Instant::now());
22 }
23
24 pub fn observe_at(&mut self, sample: f64, now: Instant) {
25 match (self.value, self.last_update) {
26 (Some(prev), Some(last)) => {
27 let elapsed = now.saturating_duration_since(last).as_secs_f64();
28 let decay = (-elapsed * std::f64::consts::LN_2
29 / self.half_life.as_secs_f64())
30 .exp();
31 self.alpha = 1.0 - decay;
32 self.value = Some(prev * decay + sample * self.alpha);
33 }
34 _ => {
35 self.value = Some(sample);
36 }
37 }
38 self.last_update = Some(now);
39 }
40
41 pub fn value(&self) -> Option<f64> {
42 self.value
43 }
44
45 pub fn reset(&mut self) {
46 self.value = None;
47 self.last_update = None;
48 }
49}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Deriving the decay from elapsed time and a half-life makes the average robust to irregular sampling intervals.
- 2Using Option to represent an uninitialized average lets the first sample seed the state cleanly.
- 3Splitting observe from observe_at isolates the wall clock, making the core logic deterministic and testable.
Related explainers
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
rust
use std::cmp::{Ordering, Reverse}; use std::collections::BinaryHeap; use std::fs::File; use std::io::{self, BufRead, BufReader, BufWriter, Lines, Write};
K-way merge of sorted logs in Rust
binary-heap
k-way-merge
streaming-io
Intermediate
8 steps
rust
use axum::{ extract::{Path, State}, response::sse::{Event, KeepAlive, Sse}, };
Streaming import progress with SSE in Axum
server-sent-events
streams
watch-channel
Advanced
7 steps
rust
use std::f64::consts::PI; #[derive(Debug, Clone, Copy)] pub struct LatLng {
Building geographic bounding boxes in Rust
geospatial
value-types
option
Intermediate
7 steps
rust
use chrono::{Duration, NaiveDate}; #[derive(Debug)] pub struct DateRange {
Parsing and iterating date ranges in Rust
error-handling
iterators
parsing
Intermediate
7 steps
rust
use std::collections::VecDeque; use std::sync::{Arc, Condvar, Mutex}; use std::time::{Duration, Instant};
Building a counting semaphore in Rust
concurrency
synchronization
condition-variable
Advanced
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/a-time-decayed-moving-average-in-rust-explained-rust-f4d8/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.