计算平均值的舍入误差

时间:2018-02-12 05:46:13

标签: c++ floating-point rounding rounding-error

我在c ++中舍入错误时遇到了一些问题。如果我必须计算两个浮点ab的平均值,那么为什么a+0.5*(b-a)(a+b)/2更好?我无法理解为什么计算它的两种方式会有任何不同。

2 个答案:

答案 0 :(得分:3)

[免责声明:此答案采用IEEE 754格式和语义。具体来说,我们假设float是IEEE 754 binary32格式,我们使用默认的round-ties-to-even舍入模式,并且中间表达式不是用扩展精度计算的 - 例如,因为{{ 3}}是0。]

以下是选择a + 0.5 * (b-a)的一个可能原因:如果 ab非常大并且具有相同的符号,那么中间数量{{1在表达式a + b中可能会溢出,给出无限结果或浮点异常。相反,0.5 * (a + b)在这种情况下不会溢出。

然而,应该权衡以下小优势:

  • a + 0.5 * (b - a)需要三个浮点运算; a + 0.5 * (b - a)只需要两个。
  • 0.5 * (a + b) 溢出的情况下,a + b始终提供正确答案:即,它提供最佳在给定目标类型的可表示性约束的情况下,逼近实际均值。 (这不是完全明显,但不难证明:0.5 * (a + b)的幅度大于最小法线的两倍,在这种情况下,总和被正确舍入并乘以{{1确切地说,或者a + b本身是精确计算的,然后乘以0.5被正确舍入。无论哪种方式,两个算术运算中最多只有一个会引入错误。)但{{} 1}}将总是给出一个正确的舍入均值,实际上可能有数百万的ulps错误。考虑a + b0.5的情况。然后a + 0.5 * (b - a)提供a = -1.0。正确的均值为b = 1.0 + 2^-23
  • 如果a + 0.5 * (b - a)0.0非常大且相反符号而不是相同,则表达式2^-24可以溢出标志。在这种情况下,a + 0.5 * (b - a)不会溢出。
  • a(非常轻微)可读性低于b;读者需要花一点时间思考一下它在做什么。

鉴于上述情况,很难支持0.5 * (a + b)应优先使用a + 0.5 * (b - a)的一般性建议。

答案 1 :(得分:0)

如果您计算许多数字的平均值,则您的公式是正确的。在这种情况下,您可以执行以下操作:

μ n = 1 /nΣx i

但是在这里添加第101个数字时你需要将x 101 添加到μ 100 ,其中μ 100 可能相当于x 101 ,所以你会失去一些精确度。为了避免这个问题,你可以这样:

μ 101 100 + 1 / n(x 101 - μ 100

如果x i 具有相同的数量级,则此公式要好得多,因为您避免处理两个大数和x i 之间的算术运算。 / p>

您可能需要阅读文章Numerically stable computation of arithmetic means

让我们看看数字在IEEE浮点中的表示方式。考虑C ++ float

区间[1,2]与步骤2 -23 一致,因此您可以表示数字1 + n * 2 -23 ,其中n属于{0, ......,2 23 }。

区间[2 j ,2 j + 1 ]与[1,2]类似,但乘以2 j

要查看精度丢失的原因,您可以运行此程序:

#include <iostream>
#include <iomanip>
int main() {
    float d = pow(2,-23);
    std::cout << d << std::endl;
    std::cout << std::setprecision(8) << d + 1 << std::endl;
    std::cout << std::setprecision(8) << d + 2 << std::endl; // the precision has been lost
    system("pause");
}

输出

  

1.19209e-07
  1.0000001
  2