如何在不必遍历整个列表的情况下对平均数中的数字进行加减?
这在许多情况下非常有用。例如,连续计算流中最后X个值的平均值、将两个平均值相加以及基于新用户投票更新评级。
如何在不必遍历整个列表的情况下对平均数中的数字进行加减?
这在许多情况下非常有用。例如,连续计算流中最后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);
}
当考虑对一个大小为0的集合求平均值时,你可以返回0
,这样将一个值添加回去时,它作为平均值。但是如果认为将集合减少到大小0是一个错误,那么返回NAN
将传播到未来的使用中,使其更加明显。但请查看什么是空序列的算术平均数? - 你可能希望在现场嘈杂地报告错误,或者如果这种情况是错误的,就抛出C++异常(不仅仅是引发FP异常)。
如果没有特殊处理它,你可能会得到+或-Inf,因为x / 0.
非零x
,除非移除的值恰好等于当前平均值;那么你将得到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);
}
subtractFromAverage
would throw an error if size
is 1
. I would add if (oldSize == 1) return 0;
- Yousif0
对于所有用例来说是否更好。如果有什么问题,NaN可能更合适。 (当前代码实际上将返回+-Inf
,这也不好,除非average == value
以获得0. / 0.
=> NaN)。我想返回0
的优点是将其添加到平均值中将设置平均值为该值。 - Peter Cordessize
是一个编译时常量,您可以执行 double inverse = 1. / size;
但这可能不是精确的,并且随着重复使用会积累误差。) - Peter Cordes已经提到的典型方法是:
( n * a + v ) / (n + 1);
其中n
是我们的旧计数,a
是我们的旧平均值,而v
则是我们的新值。
但是,n * a
部分随着n
的增大,特别是当a
本身较大时,最终会溢出。为了避免这种情况,请使用:
a + ( v - a ) / (n + 1)
随着n
的增加,我们会失去一些精度 - 自然而然地,我们正在通过连续较小的量来修改a
。批处理值可以缓解这个问题,但对于大多数任务来说可能过于复杂。
1.79769e+308
)时才会出现问题,这是极其巨大的。另一个主要的数值问题是将小数加到大数中,使用n*a + v
或a + v/n
。如果v/n
小于a
的1ULP,则添加它甚至不会翻转a
的尾数的低位。即如果|v| < |a|/2^53
左右。即使v
不是那么小,你仍然可能失去大部分精度。 - Peter Cordesn*a
接近MAX
时,n*a + v = n*a
。使用适当的数据类型重新计算平均值总是更好的选择,但并不总是可能的(或必要的),就像在OP的情况下一样。 - c za-(v-a)/(n-1)
(其中 n
和 a
分别表示移除 v
之前的项目数量和平均值)。 - c z