I review a lot of code, and I keep seeing the same mistake over and over. Someone needs to smooth out a noisy signal (maybe sensor data, maybe user metrics, whatever) and they immediately reach for a sliding window moving average, a.k.a. the SMA filter.
Here’s an example you’ve probably seen:
#define WINDOW_SIZE 10 typedef struct { float buffer[WINDOW_SIZE]; int index; int count; float sum; } MovingAverage; // Initialize the moving average structure void average_init(MovingAverage *ma) { ma->index = 0; ma->count = 0; ma->sum = 0.0f; for (int i = 0; i < WINDOW_SIZE; i++) { ma->buffer[i] = 0.0f; } } // Add new value and return current average float average(MovingAverage *ma, float new_value) { // Subtract the value being replaced from sum ma->sum -= ma->buffer[ma->index]; // Add new value to buffer and sum ma->buffer[ma->index] = new_value; ma->sum += new_value; // Update circular index ma->index = (ma->index + 1) % WINDOW_SIZE; // Track how many values we've added (up to WINDOW_SIZE) if (ma->count < WINDOW_SIZE) { ma->count++; } // Return average return ma->sum / ma->count; } // Example usage int main() { MovingAverage ma; average_init(&ma); printf("Adding values and calculating moving average:\n"); printf("Value: %.1f, Average: %.2f\n", 10.0f, average(&ma, 10.0f)); printf("Value: %.1f, Average: %.2f\n", 20.0f, average(&ma, 20.0f)); printf("Value: %.1f, Average: %.2f\n", 30.0f, average(&ma, 30.0f)); printf("Value: %.1f, Average: %.2f\n", 40.0f, average(&ma, 40.0f)); printf("Value: %.1f, Average: %.2f\n", 50.0f, average(&ma, 50.0f)); return 0; } There’s a lot of unnecessary complexity here to maintain the circular buffer (~35 lines of code), not to mention the amount of memory required grows linearly with the window size (see FIR filters) . Don’t be fooled into thinking this is a “C problem” either. Higher level languages with better semantics for buffers can reduce the lines of code, but the compiler still must implements all of this under the hood and cannot avoid performance/memory/program-size costs.
...