如何稳健地计算平均值(平均值)?

时间:2014-05-22 17:14:43

标签: c++ algorithm floating-point

如果我们天真地计算平均值:

std::vector<double> values;
double sum = std::accumulate(begin(values), end(values), 0.0);
double mean = sum / values.size();

values.size()很大,我们可能会得到不准确的结果,因为浮点数在较高范围内分辨率较低。或者更糟糕的是,如果我理解正确,我们可以获得无限的结果。

当我们有偶数数量的值时,我们可以计算上半部分的平均值,然后计算第二部分的平均值,并找到这两种均值的平均值

这似乎不是一个新问题,但我无法找到资源。我认为权衡 更复杂的技术

  • 鲁棒性
  • 计算复杂性
  • 难以实施

我想知道是否有人将它们总结到某个地方,如果它们在某些中可用,那就更好了。

5 个答案:

答案 0 :(得分:8)

您可以使用here所述的在线算法。

基本上(在pythonish伪代码中):

n = 0
mean = 0

for value in data:
    n += 1
    mean += (value - mean)/n

该算法在数值上比天真的实现更稳定。

答案 1 :(得分:8)

这里可能会发生很多愚蠢的事情。一个问题是溢出的东西。另一个例子如下:(1e100 + 1) - 1e100) == 0。另一个是刚刚累积的结果。

对于规模较大的数据,

Kahan summation可以很好地处理累积的舍入。使用Kahan求和求和,然后除以数据的数量。

为了处理数据不佳的数据,您可以按指数(比如50个不同的桶,每个桶覆盖大约20个不同的指数)和Kahan-sum以递减的桶顺序存储数据。

当然,这都是大规模的矫枉过正,而且速度相当慢。在实践中,使用向量指令和类似的东西可以精确地帮助提高速度

答案 2 :(得分:4)

如果您愿意在此过程中使用values,那么一个简单而强大的方案就是首先对其进行排序:

struct fabs_less
{
    bool
    operator()(const double x0, const double x1) const
    {
        return fabs(x0)<fabs(x1);
    }
};

std::sort(values.begin(), values.end(), fabs_less());
const double sum = std::accumulate(values.begin(), values.end(), 0.0);
const double mean = sum / double(values.size());

这会将计算复杂度增加到N log N,但会导致最小的舍入误差。

编辑:tmyklebu对退化案例提出了非常好的观点(诅咒我错过了它)。而是按照增加的顺序单独累积负面和正面术语:

std::sort(values.begin(), values.end());
std::vector<double>::const_iterator mid = std::upper_bound(values.begin(), values.end(), 0.0);
std::reverse_iterator<std::vector<double>::const_iterator> rmid(mid);
const double neg = std::accumulate(rmid, values.rend(), 0.0);
const double pos = std::accumulate(mid, values.end(), 0.0);
const double mean = (neg+pos) / double(values.size());

这引入了neg+pos中取消错误的可能性,但相对于values元素的绝对值之和仍然会有一个小错误,我认为这是最好的希望没有一些严重复杂的逻辑...

答案 3 :(得分:2)

通常,分而治之的技术(两部分的递归分割)是健壮的。

请参阅我对Precise sum of floating point numbers的回答,其中我以递归形式演示了它。

请注意,在C / C ++中没有递归尾调用消除,因此这种实现不一定有效(它会导致深层堆栈)。

答案 4 :(得分:1)

请原谅,由于长度的原因,不要将此作为评论。 double值通常具有超过50位的精度。你谈论的是万亿分之一以上的一部分。

在整个范围内,浮点数的分辨率在小数基础上保持不变。

但是,如果您将1234E40添加到1234E-040,那么您将获得1234E40。通过平均关闭来添加不同数量级的值。但是,它关闭的数量通常很小(万亿分之一),很少引人注目。

几乎在所有情况下,您只需添加并除以计数即可进行平均,并获得非常精确的答案。

您甚至可以在系统上进行长时间的双击。

如果你有一些数据集不是这种情况,也许你可以描述这个数据集及其出现的问题。由此,我们可以为您的特定问题找到解决方案。