我在c ++中舍入错误时遇到了一些问题。如果我必须计算两个浮点a
和b
的平均值,那么为什么a+0.5*(b-a)
比(a+b)/2
更好?我无法理解为什么计算它的两种方式会有任何不同。
答案 0 :(得分:3)
[免责声明:此答案采用IEEE 754格式和语义。具体来说,我们假设float
是IEEE 754 binary32格式,我们使用默认的round-ties-to-even舍入模式,并且中间表达式不是用扩展精度计算的 - 例如,因为{{ 3}}是0
。]
以下是选择a + 0.5 * (b-a)
的一个可能原因:如果 a
和b
非常大并且具有相同的符号,那么中间数量{{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 + b
和0.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