Calculating a Running Average

In a newsgroup I replied to a question about calculating average data throughput during data reception over the network. A simple average bytes per second calculation needs only a few lines of code:

int bytesTotal = 0;
int bytesPerSecond = 0;
DateTime startTime = DateTime.Now;

do {
  bytesRead = receiveStream.Read( ... );
  bytesTotal += bytesRead;
  bytesPerSecond = bytesTotal / (DateTime.Now - startTime).TotalSeconds;

  ...
} while(bytesRead > 0);

The problem with this is that it may not render very exact values over a longer download period because actual network throughput tends to change a lot over WAN connections. It would be more accurate to work out an average over a number of most recent measured values instead. As this requires some handling of a store of measured samples, I decided to create a reusable class that can hold a number of samples in a ring buffer and calculate the current average from these samples. The class takes a weighting of each sample into account, because in scenarios like the one above it’s not always guaranteed that new samples can be measured in an exact frequency. Here’s the class:

public class RunningAverage {
  public RunningAverage(int samplesCount) {
    samples = new Sample[samplesCount];
    for (int i = 0; i < samplesCount; i++)
      samples[i] = new Sample( );
    currentSample = 0;
  }

  class Sample {
    public Sample( ) { }

    public Sample(double weighting, double measuredValue) {
      Set(weighting, measuredValue);
    }

    public void Set(double weighting, double measuredValue) {
      this.weighting = weighting;
      this.measuredValue = measuredValue;
      isValid = true;
    }

    private double weighting;
    public double Weighting { get { return weighting; } }

    private double measuredValue;
    public double MeasuredValue { get { return measuredValue; } }

    private bool isValid;
    public bool IsValid { get { return isValid; } }
  }

  Sample[] samples;
  int currentSample;

  public void AddSample(double weighting, double measuredValue) {
    samples[currentSample++].Set(weighting, measuredValue);
    if (currentSample >= samples.Length)
      currentSample = 0;
  }

  public double GetCurrentAverage( ) {
    double totalValue = 0;
    double totalWeighting = 0;
    foreach (Sample sample in samples)
      if (sample.IsValid) {
        totalValue += sample.MeasuredValue;
        totalWeighting += sample.Weighting;
      }
    return totalValue / totalWeighting;
  }
}

Now the receive loop above could look like this:

RunningAverage average = new RunningAverage(100);
DateTime timeStamp = DateTime.Now;

do {
  bytesRead = receiveStream.Read( ... );
  DateTime newTimeStamp = DateTime.Now;
  average.AddSample(newTimeStamp.TotalSeconds - timeStamp.TotalSeconds, bytesRead);
  timeStamp = newTimeStamp;
  // access average.GetCurrentAverage() if needed

  ...
} while(bytesRead > 0);

Sorry, this blog does not support comments.

I used various blog hosting services since this blog was established in 2005, but unfortunately they turned out to be unreliable in the long term and comment threads were lost in unavoidable transitions. At this time I don't want to enable third-party services for comments since it has become obvious in recent years that these providers invariably monetize information about their visitors and users.

Please use the links in the page footer to get in touch with me. I'm available for conversations on Keybase, Matrix, Mastodon or Twitter, as well as via email.