计算平均值时的舍入误差
Rounding error in computing average
我在 C++ 中遇到舍入错误的问题。如果我必须计算两个浮点数 a
和 b
的平均值,那么为什么 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_METHOD
为 0
。]
以下是首选 a + 0.5 * (b-a)
的一个可能原因:如果 a
和 b
非常大且符号相同,则表达式 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.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)
比 0.5 * (a + b)
可读性差(非常小); reader 需要多花点时间来弄清楚它在做什么。
鉴于上述情况,很难支持 a + 0.5 * (b - a)
优先于 0.5 * (a + b)
的一般性建议。
我在 C++ 中遇到舍入错误的问题。如果我必须计算两个浮点数 a
和 b
的平均值,那么为什么 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_METHOD
为 0
。]
以下是首选 a + 0.5 * (b-a)
的一个可能原因:如果 a
和 b
非常大且符号相同,则表达式 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.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)
比0.5 * (a + b)
可读性差(非常小); reader 需要多花点时间来弄清楚它在做什么。
鉴于上述情况,很难支持 a + 0.5 * (b - a)
优先于 0.5 * (a + b)
的一般性建议。