浮点加法:精度损失问题

时间:2009-08-10 07:58:18

标签: c# c++ floating-point ieee-754

简而言之:我如何执行a+b,以便因截断而导致的任何精度损失远离零而不是零?

长篇故事

我正在计算一系列浮点值的总和,以便计算集合的样本均值和方差。由于 Var(X)= E(X 2 ) - E(X) 2 ,它足以维持所有数字的运行计数,到目前为止所有数字的总和,以及到目前为止所有数字的平方和。

到目前为止一切顺利。

然而,绝对需要 E(X 2 )> E(X) 2 ,由于浮点精度并非总是如此。在伪代码中,问题是:

int count;
double sum, sumOfSquares;
...
double value = <current-value>;
double sqrVal = value*value; 

count++;
sum += value; //slightly rounded down since value is truncated to fit into sum
sumOfSquares += sqrVal; //rounded down MORE since the order-of-magnitude 
//difference between sqrVal and sumOfSquares is twice that between value and sum;

对于变量序列,这不是一个大问题 - 你最终会略微低估方差,但这通常不是一个大问题。然而,对于具有非零均值的常数或几乎常数的集合,它可以表示 E(X 2 )&lt; E(X) 2 ,导致负计算方差,这违反了消费代码的预期。

现在,我知道Kahan Summation,这不是一个有吸引力的解决方案。首先,它使代码容易受到优化变化(取决于优化标志,代码可能会或可能不会出现此问题),其次,由于精度问题,问题不是真正 - 这很好足够 - 这是因为加法将系统错误引入零。如果我可以执行该行

sumOfSquares += sqrVal;

以确保sqrVal向上舍入而不是向下舍入到sumOfSquares的精度的方式,我有一个数值上合理的解决方案。但是我怎么能实现呢?

<子> 编辑:完成的问题 - 为什么按下输入标签字段的下拉列表无论如何提交问题?

3 个答案:

答案 0 :(得分:6)

IEEE提供四种舍入模式,(朝向-inf,朝向+ inf,朝向0,色调)。 Toward + inf就是你想要的。 C90或C ++中没有标准控件。 C99添加了标头<fenv.h>,它也是某些C90和C ++实现中的扩展。要尊重C99标准,您必须编写如下内容:

#include <fenv.h>
#pragma STDC FENV_ACCESS ON

int old_round_mode = fegetround();
int set_round_ok = fesetround(FE_UPWARD);
assert(set_round_ok == 0);
...
int set_round_ok = fesetround(old_round_mode);
assert(set_round_ok == 0);

众所周知,您使用的算法在数值上不稳定并且存在精度问题。精确度最好对数据进行两次传递。

答案 1 :(得分:6)

还有另一种单程算法可以重新排列计算。在 伪代码:

n = 0
mean = 0
M2 = 0

for x in data:
    n = n + 1
    delta = x - mean
    mean = mean + delta/n
    M2 = M2 + delta*(x - mean)  # This expression uses the new value of mean

variance_n = M2/n         # Sample variance
variance = M2/(n - 1)     # Unbiased estimate of population variance

(资料来源:http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance

对于您指出的问题,这似乎表现得更好 用通常的算法。

答案 2 :(得分:2)

如果您不担心精确度,但只是负面差异,为什么不简单地做V(x) = Max(0, E(X^2) - E(X)^2)