All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.diffplug.common.debug.RunningStats Maven / Gradle / Ivy

/*
 * Copyright 2016 DiffPlug
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.diffplug.common.debug;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

import com.diffplug.common.base.Unhandled;
import com.diffplug.common.primitives.Doubles;

/** Calculates the running mean/median/min/max of a sampled signal. */
public class RunningStats {
	double[] samples = new double[16];
	int num = 0;

	double min = Double.POSITIVE_INFINITY;
	double max = Double.NEGATIVE_INFINITY;
	double total = 0;
	int numNanOrInfinite = 0;

	/** Adds the given sample to the running stats. */
	public synchronized void add(double sample) {
		if (Double.isFinite(sample)) {
			samples = Doubles.ensureCapacity(samples, num + 1, samples.length);
			samples[num] = sample;
			total += sample;
			if (sample < min) {
				min = sample;
			}
			if (sample > max) {
				max = sample;
			}
			++num;
		} else {
			++numNanOrInfinite;
		}
	}

	/** Returns these stats at this time. */
	public synchronized Stat getStat() {
		if (num == 0) {
			return new Stat(numNanOrInfinite);
		} else {
			double mean = total / num;
			Arrays.sort(samples, 0, num);
			int middle = Math.floorDiv(num, 2);
			double median;
			if (num % 2 == 0) {
				median = (samples[middle - 1] + samples[middle]) / 2.0;
			} else {
				median = samples[middle];
			}
			return new Stat(min, max, mean, median, total, num, numNanOrInfinite);
		}
	}

	/** The stats at a given instant. */
	public static class Stat {
		public final double min, max, mean, median, total;
		public final int num, numNanOrInfinite;

		public Stat(int numNanOrInfinite) {
			this(0, 0, 0, 0, 0, 0, numNanOrInfinite);
		}

		public Stat(double min, double max, double mean, double median, double total, int num, int numNanOrInfinite) {
			this.min = min;
			this.max = max;
			this.mean = mean;
			this.median = median;
			this.total = total;
			this.num = num;
			this.numNanOrInfinite = numNanOrInfinite;
		}

		@Override
		public String toString() {
			return toString(TimeUnit.MILLISECONDS);
		}

		public String toString(TimeUnit unit) {
			if (num == 0) {
				if (numNanOrInfinite == 0) {
					return "No samples yet";
				} else {
					return "No valid samples, " + numNanOrInfinite + " were NaN or infinite";
				}
			} else {
				StringBuilder builder = new StringBuilder(256);
				builder.append("median=").append(formatUnit(median, unit));
				builder.append(" mean=").append(formatUnit(mean, unit));
				builder.append(" min=").append(formatUnit(min, unit));
				builder.append(" max=").append(formatUnit(max, unit));
				builder.append(" num=").append(num);
				if (numNanOrInfinite > 0) {
					builder.append(" numNANorINFINITE=" + numNanOrInfinite);
				}
				return builder.toString();
			}
		}
	}

	/** Formats the given elapsed time in seconds with the given precision. */
	public static String formatUnit(double elapsedSec, TimeUnit precision) {
		long multiplier = precision.convert(1, TimeUnit.SECONDS);
		int elapsedUnits = (int) Math.round(elapsedSec * multiplier);
		return Integer.toString(elapsedUnits) + units(precision);
	}

	private static String units(TimeUnit unit) {
		switch (unit) {
		case NANOSECONDS:
			return "ns";
		case MICROSECONDS:
			return "us";
		case MILLISECONDS:
			return "ms";
		case SECONDS:
			return "s";
		case MINUTES:
			return "m";
		case HOURS:
			return "h";
		case DAYS:
			return "d";
		default:
			throw Unhandled.enumException(unit);
		}
	}

	/** Adds a time to the running stats. */
	public synchronized Stat addAndGetStat(double time) {
		add(time);
		return getStat();
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy