Performance 在恒定时间内更新连续数字序列的平均值
如何在平均数中进行加减,而不必遍历整个列表Performance 在恒定时间内更新连续数字序列的平均值,performance,math,iteration,time-complexity,average,Performance,Math,Iteration,Time Complexity,Average,如何在平均数中进行加减,而不必遍历整个列表 这在许多情况下都非常有用。例如,要连续计算流中最后X个值的平均值,将两个平均值相加,并根据新用户投票更新评分。确实可以在恒定时间O(1)的平均值中操纵单个值 下面的函数将数字添加到平均值average是当前平均值,size是平均值中当前值的数量,value是要添加到平均值中的数字: double addToAverage(double average, int size, double value) { return (size * avera
这在许多情况下都非常有用。例如,要连续计算流中最后X个值的平均值,将两个平均值相加,并根据新用户投票更新评分。确实可以在恒定时间O(1)的平均值中操纵单个值 下面的函数将数字添加到平均值
average
是当前平均值,size
是平均值中当前值的数量,value
是要添加到平均值中的数字:
double addToAverage(double average, int size, double value)
{
return (size * average + value) / (size + 1);
}
double subtractFromAverage(double average, int size, double value)
{
// if (size == 1) return 0; // wrong but then adding a value "works"
// if (size == 1) return NAN; // mathematically proper
// assert(size > 1); // debug-mode check
// if(size < 2) throw(...) // always check
return (size * average - value) / (size - 1);
}
同样,以下函数从平均值中删除一个数字:
double addToAverage(double average, int size, double value)
{
return (size * average + value) / (size + 1);
}
double subtractFromAverage(double average, int size, double value)
{
// if (size == 1) return 0; // wrong but then adding a value "works"
// if (size == 1) return NAN; // mathematically proper
// assert(size > 1); // debug-mode check
// if(size < 2) throw(...) // always check
return (size * average - value) / (size - 1);
}
也可以计算恒定时间内两个平均值的总平均值:
double addAveragesTogether(double averageA, int sizeA, double averageB, int sizeB)
{
return (sizeA * averageA + sizeB * averageB) / (sizeA + sizeB);
}
前面提到的典型方法是:
( n * a + v ) / n + 1;
其中,n
是我们的新计数,a
是我们的旧平均值,v
是我们的新值
但是,n*a
部分最终会随着n
变大而溢出,特别是当a
本身变大时。要避免这种使用:
a + ( v - a ) / n
随着
n
的增加,我们确实会失去一些精度-自然地,我们会依次以较小的数量修改a
。批处理值可以缓解这个问题,但对于大多数任务来说可能过于复杂。如果有人对第二个等式为什么也适用感兴趣,您可以在这里找到一个很好的解释:但是是否也有其他方法可以移除和替换?请注意,浮点在所有刻度上保持相同的相对精度,因此,用相似大小的数字进行乘法和除法不会损失太多精度;只有当它实际溢出超过DBL_MAX时才有问题,大约1.79769e+308
,这是非常巨大的。另一个主要的数值问题是用n*a+v
或a+v/n
将一个小数字加到一个大数字上。如果v/n
小于a
的1LP,添加它甚至不会翻转a
尾数的低位。i、 e.如果|v |<| a |/2^53
左右。即使v
不是很小,你仍然可能会失去它的大部分精度。Math.SE调用并回答了这一问题。虽然addToAverage
是正确的,但请注意,使用它时精度误差可能会更小。如果size
为1
,则减去平均值将抛出错误。如果(oldSize==1)返回0,我将添加
@Yousif:我不确定静默返回0
对所有用例都更好。如果有什么不同的话,NaN会更合适。(当前代码实际上将返回+-Inf
,这也不好,除非平均==value
得到0./0.
=>NaN)。我猜返回0
的好处是,将平均值相加将使平均值达到该值;一般来说,这仍然是值得的,但不如简单的加法和乘法那么便宜。(如果size
是编译时常量,则可以执行double inverse=1./size;
但这可能不准确,并且可能会在重复使用过程中累积错误。)