// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; namespace EpicCommonUtilities { /// /// The kinds of statistics that the StatisticalFloat class tracks (or can optionally track, in the case of Median) /// public enum EStatType { Minimum, Maximum, Average, StDev, Median, Count }; /// /// Keeps a running set of first-order statistics for a single variable /// public class StatisticalFloat { /// /// Should newly constructed StatisticalFloat instances track the median value? /// This requires O(N) storage instead of O(1) storage and it is only checked on construction. /// public static bool bSupportMedian = true; public double GetByType(EStatType Type) { switch (Type) { case EStatType.Minimum: return Min; case EStatType.Maximum: return Max; case EStatType.Average: return Average; case EStatType.StDev: return StandardDeviation; case EStatType.Median: return Median; case EStatType.Count: return MyCount; default: return 0.0; } } public double Average { get { return MySum / MyCount; } } public double Min { get { return MyMin; } } public double Max { get { return MyMax; } } public double Value { get { return Average; } set { MyCount = 0; AddSample(value); } } /// /// Returns the median if bSupportMedian was true at construction and NaN otherwise /// public double Median { get { if (MySamples != null) { MySamples.Sort(); if (MySamples.Count > 0) { return MySamples[MySamples.Count / 2]; } } return Math.Sqrt(-1.0); } } public double StandardDeviation { get { double numerator = (MyCount * MySumOfSquares - MySum * MySum); double denominator = MyCount * (MyCount - 1); return (MyCount > 1) ? Math.Sqrt(numerator / denominator) : 0.0; } } public double AggregateSum { get { return MySum; } } public StatisticalFloat() { if (bSupportMedian) { MySamples = new List(); } } public StatisticalFloat Clone() { StatisticalFloat Result = new StatisticalFloat(); Result.AddSamples(this); return Result; } public StatisticalFloat(double InValue) { if (bSupportMedian) { MySamples = new List(); } MyCount = 0; AddSample(InValue); } public void AddSample(double InValue) { if (MyCount == 0) { MySum = InValue; MySumOfSquares = InValue * InValue; MyMin = InValue; MyMax = InValue; } else { MySum += InValue; MySumOfSquares += InValue * InValue; MyMin = Math.Min(MyMin, InValue); MyMax = Math.Max(MyMax, InValue); } if (MySamples != null) { MySamples.Add(InValue); } MyCount++; } public void AddSamples(StatisticalFloat Samples) { if (MyCount == 0) { MySum = Samples.MySum; MySumOfSquares = Samples.MySumOfSquares; MyMin = Samples.MyMin; MyMax = Samples.MyMax; MyCount = Samples.MyCount; } else { MySum += Samples.MySum; MySumOfSquares += Samples.MySumOfSquares; MyMin = Math.Min(MyMin, Samples.MyMin); MyMax = Math.Max(MyMax, Samples.MyMax); MyCount += Samples.MyCount; } if (MySamples != null) { MySamples.AddRange(Samples.MySamples); } } public void AddDistribution(StatisticalFloat Samples) { if (MyCount == 0) { AddSamples(Samples); } else if (Samples.MyCount > 0) { MyMin = MyMin + Samples.MyMin; MyMax = MyMax + Samples.MyMax; MySum = Average + Samples.Average; MySumOfSquares = MySum * MySum; MyCount = 1; if (MySamples != null) { MySamples.Clear(); MySamples.Add(MySum); } } } public void ScaleBy(double ScaleFactor) { MySum *= ScaleFactor; MySumOfSquares *= Math.Abs(ScaleFactor); MyMin *= ScaleFactor; MyMax *= ScaleFactor; if (MySamples != null) { for (int i = 0; i < MySamples.Count; ++i) { MySamples[i] = MySamples[i] * ScaleFactor; } } } public string ToStringSizesInKB() { return String.Format("avg={0:N1} KB [{1:N1}..{2:N1}]", Average/1024.0, MyMin/1024.0, MyMax/1024.0); } public string Min_ToStringInt() { return ((long)MyMin).ToString(); } public string Max_ToStringInt() { return ((long)MyMax).ToString(); } double MySumOfSquares = 0.0; double MySum = 0.0; double MyMin = 0.0; double MyMax = 0.0; long MyCount = 0; // Simple wasteful implementation; Tool is for offline anaysis and N is only on the order of hundreds typically // but the median tracking can be disabled by setting bSupportMedian to false before construction. List MySamples = null; } }