计算平均值时的舍入误差

Rounding error in computing average

我在 C++ 中遇到舍入错误的问题。如果我必须计算两个浮点数 ab 的平均值,那么为什么 a+0.5*(b-a)(a+b)/2 更好?我不明白为什么这两种计算方式会有什么不同。

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

μn = 1/nΣxi

但这里在添加第 101 个数字时,您需要将 x101 添加到 μ100,其中 μ100 与 x101 相比可能相当大,因此您会损失一些精度。为了避免这个问题,你可以这样:

μ101 = μ100 + 1/n(x101 - μ100)

如果你的 xi 是同一个数量级,这个公式会好很多,因为你避免处理两个大数和 x 之间的算术运算我.

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

让我们看看数字在 IEEE 浮点数中是如何表示的。考虑 C++ float:

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

Interval [2j, 2j+1] 和 [1,2] 一样但是乘以 2j.

要看看精度是如何丢失的运行这个程序:

#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

[免责声明:此答案假定 IEEE 754 format and semantics. Specifically, we assume that float is the IEEE 754 binary32 format, that we're using the default round-ties-to-even rounding mode, and that intermediate expressions are not computed with extended precision - e.g., because FLT_EVAL_METHOD0。]

以下是首选 a + 0.5 * (b-a) 的一个可能原因:如果 ab 非常大且符号相同,则表达式 0.5 * (a + b) 中的中间量 a + b 可能会溢出,从而给出无限结果或浮点异常。相比之下,a + 0.5 * (b - a)在那种情况下不会溢出。

但是,应该权衡以下几点:

  • a + 0.5 * (b - a)需要三个浮点运算; 0.5 * (a + b) 只需要两个。
  • a + b 确实 而不是 溢出的情况下,0.5 * (a + b) 总是提供一个正确的四舍五入的答案:也就是说,它给出了 给定目标类型的可表示性约束,与实际平均值的最佳可能 近似值。 (这不是 完全 显而易见,但不难证明:要么 a + b 的幅度大于最小法线的两倍,在这种情况下,总和被正确舍入并且乘以0.5 是精确的,或者 a + b 本身是精确计算的,然后与 0.5 的乘法是正确舍入的。无论哪种方式,两个算术运算中最多有一个会引入错误。)但是 a + 0.5 * (b - a) 而不是 总能给出一个正确舍入的平均值,实际上可能有数百万 ulp 的错误。考虑 a = -1.0b = 1.0 + 2^-23 的情况。然后 a + 0.5 * (b - a) 给出 0.0。正确的平均值是 2^-24.
  • 表达式a + 0.5 * (b - a)可以溢出,如果ab非常大相反符号而不是相同的符号。在那种情况下,0.5 * (a + b) 不会溢出。
  • a + 0.5 * (b - a)0.5 * (a + b) 可读性差(非常小); reader 需要多花点时间来弄清楚它在做什么。

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