代码之家  ›  专栏  ›  技术社区  ›  Eamon Nerbonne

浮点加法:精度损失问题

  •  9
  • Eamon Nerbonne  · 技术社区  · 16 年前

    简而言之:我如何执行 a+b 这样,由于截断而导致的精度损失 远离零 而不是接近零?

    Long Story

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

    到现在为止,一直都还不错。

    但是,这是绝对必要的 e(x) (e)(e) 由于浮点精度,情况并非总是如此。在伪代码中,问题在于:

    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) (e)(e) ,导致计算出的方差为负,这违反了使用代码的预期。

    现在,我知道卡汉总结,这不是一个有吸引力的解决方案。首先,它使代码容易受到优化变数的影响(取决于优化标志,代码可能会或可能不会显示此问题),其次,问题不是 真的? 由于精确性-这足够好-这是因为添加 系统的 接近零的错误。如果我能执行命令

    sumOfSquares += sqrVal;
    

    为了确保sqrval向上而不是向下四舍五入到sumofsquares的精度,我将有一个数值上合理的解决方案。但我怎样才能做到呢?

    编辑: 完成的问题-为什么在标签字段的下拉列表中按Enter键提交问题?

    3 回复  |  直到 15 年前
        1
  •  6
  •   Jim Lewis    16 年前

    还有另一个单通算法,它可以稍微重新安排计算。在 pseudocode:

    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
  •  6
  •   RBerteig Keith Adler    15 年前

    IEEE提供四种舍入模式(朝向-inf,朝向+inf,朝向0,tonarest)。朝向+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);
    

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

        3
  •  2
  •   erikkallen    16 年前

    如果你不担心精确性,只关心负方差,为什么不简单地做呢? V(x) = Max(0, E(X^2) - E(X)^2)