在恒定时间内更新连续数字序列的平均值
Posted
技术标签:
【中文标题】在恒定时间内更新连续数字序列的平均值【英文标题】:Update the average of a continuous sequence of numbers in constant time 【发布时间】:2014-05-24 20:03:12 【问题描述】:如何在不必遍历整个列表的情况下平均加减数字?
这在许多情况下都非常有用。例如,连续计算流中最后 X 个值的平均值,将两个平均值相加,并根据新用户投票更新评分。
【问题讨论】:
这被称为incremental averaging,并在 Math.SE 上得到了回答。 【参考方案1】:确实有可能在恒定时间 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);
您可能会考虑返回 0
作为一组大小为 0 的平均值,这样添加一个值就会将该值作为平均值。但是,如果您想将集合减小到 0 大小视为错误,则返回 NAN
会将其传播到将来的使用中,使其更加可见。但是请参阅What is the arithmetic mean of an empty sequence? - 您可能只想当场大声报告错误,或者抛出 C++ 异常(不只是引发 FP 异常),如果这是一个错误的话。
如果你没有特殊情况,你可能会从x / 0.
和非零x
中得到 + 或 -Inf,除非你删除的值正好等于当前平均值;然后你会得到0. / 0.
=> NaN。
您还可以组合这些功能来轻松替换数字。如果您要计算数组/流中最后 X 个数字的平均值,这将非常方便。
double replaceInAverage(double average, int size, double oldValue, double newValue)
return (size * average - oldvalue + newValue) / size;
也可以在恒定时间内计算两个平均值的总平均值:
double addAveragesTogether(double averageA, int sizeA, double averageB, int sizeB)
return (sizeA * averageA + sizeB * averageB) / (sizeA + sizeB);
【讨论】:
虽然addToAverage
是正确的,但请注意,使用此alternative formula 时,精度误差可能会更小。
如果size
是1
,subtractFromAverage
会抛出错误。我会添加if (oldSize == 1) return 0;
@Yousif:我不确定静默返回0
是否适合所有用例。如果有的话,NaN 会更合适。 (当前代码实际上将返回+-Inf
,这也不好,除非average == value
得到0. / 0.
=> NaN)。我想返回0
的好处是增加平均值会将平均值设置为该值。
还要注意FP除法相当昂贵;这通常仍然值得,但不如加法和乘法那么便宜。 (如果 size
是编译时常量,您可以使用 double inverse = 1. / size;
但这可能不准确,并且可能会在重复使用时累积错误。)【参考方案2】:
已经提到的典型方式是:
( n * a + v ) / (n + 1);
n
是我们的旧计数,a
是我们的旧平均值,v
是我们的新值。
但是,n * a
部分最终会溢出,因为n
变得更大,尤其是在a
本身很大的情况下。为避免这种使用:
a + ( v - a ) / (n + 1)
随着n
的增加,我们确实会损失一些精度 - 自然,我们正在将a
修改为连续更小的数量。批处理值可以缓解问题,但对于大多数任务来说可能是多余的。
【讨论】:
如果有人对为什么第二个等式也有效,你可以在这里找到一个很好的解释:math.stackexchange.com/a/1836447/709688 但是是否也有替代方法来移除和替换? 请注意,浮点数在所有尺度上都保持相同的相对精度,因此乘以然后除以相似大小的数字不会损失太多精度;只有当它实际上溢出超过 DBL_MAX 时才会出现问题,大约是1.79769e+308
,这是非常巨大的。另一个主要的数值问题是用n*a + v
或a + v/n
将一个小数加到一个大数上。如果v/n
小于a
的1ULP,则添加它甚至不会翻转a
的尾数的低位。即如果|v| < |a|/2^53
左右。即使v
不是那么小,您仍然可能会丢失其大部分精度。
@PeterCordes 是的,这将等式 2 与从头开始重新计算平均值进行比较。不过,等式 1 仍然存在同样的问题 - 当 n*a
接近 MAX
然后 n*a + v = n*a
时。使用合适的数据类型重新计算平均值总是会更好,但并不总是可能的(或必要的),就像在 OP 的情况下一样。
@Barnack 要从平均值中删除一个项目,请从当前平均值中删除该项目的影响,即a-(v-a)/(n-1)
。 (其中n
和a
表示删除v
之前的项目数和平均值)。以上是关于在恒定时间内更新连续数字序列的平均值的主要内容,如果未能解决你的问题,请参考以下文章