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;
但这可能不准确,并且可能会在重复使用过程中累积错误。)