/* This code is part of Freenet. It is distributed under the GNU General * Public License, version 2 (or at your option any later version). See * http://www.gnu.org/ for further details of the GPL. */ package freenet.support.math; import java.io.DataOutputStream; import freenet.support.Logger; import freenet.support.Logger.LogLevel; /** * Simple running average: linear mean of the last N reports. * @author amphibian */ public final class SimpleRunningAverage implements RunningAverage, Cloneable { private static final long serialVersionUID = -1; final double[] refs; int nextSlotPtr=0; int curLen=0; double total=0; int totalReports = 0; final double initValue; private boolean logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this); @Override public final SimpleRunningAverage clone() { // Deep copy needed. Implement Cloneable to shut up findbugs. return new SimpleRunningAverage(this); } /** * Clear the SRA */ public synchronized void clear() { nextSlotPtr = 0; curLen = 0; totalReports = 0; total = 0; for(int i=0;i<refs.length;i++) refs[i] = 0.0; } /** * * @param length * @param initValue */ public SimpleRunningAverage(int length, double initValue) { refs = new double[length]; this.initValue = initValue; totalReports = 0; } /** * * @param a */ public SimpleRunningAverage(SimpleRunningAverage a) { this.curLen = a.curLen; this.initValue = a.initValue; this.nextSlotPtr = a.nextSlotPtr; this.refs = a.refs.clone(); this.total = a.total; this.totalReports = a.totalReports; } /** * * @return */ @Override public synchronized double currentValue() { if(curLen == 0) return initValue; return total/curLen; } @Override public synchronized double valueIfReported(double r) { if(curLen < refs.length) { return (total+r)/(curLen+1); } else { // Don't increment curLen because it won't be incremented. return (total+r-refs[nextSlotPtr])/curLen; } } /** * * @param d */ @Override public synchronized void report(double d) { totalReports++; if (logDEBUG) Logger.debug(this, "report(" + d + ") on " + this); if (curLen < refs.length) curLen++; else total -= popValue(); pushValue(d); total += d; } /** * * @param value */ protected synchronized void pushValue(double value){ refs[nextSlotPtr] = value; nextSlotPtr++; if(nextSlotPtr >= refs.length) nextSlotPtr = 0; } /** * * @return */ protected synchronized double popValue(){ return refs[nextSlotPtr]; } @Override public synchronized String toString() { return super.toString() + ": curLen="+curLen+", ptr="+nextSlotPtr+", total="+ total+", average="+total/curLen; } /** * * @param d */ @Override public void report(long d) { report((double)d); } /** * * @param out */ public void writeDataTo(DataOutputStream out) { throw new UnsupportedOperationException(); } @Override public synchronized long countReports() { return totalReports; } /** * * @param targetValue * @return */ public synchronized double minReportForValue(double targetValue) { if(curLen < refs.length) { /** Don't need to remove any values before reporting, * so is slightly simpler. * (total + report) / (curLen + 1) >= targetValue => * report / (curLen + 1) >= targetValue - total/(curLen+1) * => report >= (targetValue - total/(curLen + 1)) * (curLen+1) * => report >= targetValue * (curLen + 1) - total * EXAMPLE: * Mean (5, 5, 5, 5, 5, X) = 10 * X = 10 * 6 - 25 = 35 * => Mean = (25 + 35) / 6 = 60/6 = 10 */ return targetValue * (curLen + 1) - total; } else { /** Essentially the same, but: * 1) Length will be curLen, not curLen+1, because is full. * 2) Take off the value that will be taken off first. */ return targetValue * curLen - (total - refs[nextSlotPtr]); } } }