当我添加三个浮点值并将它们与 1 进行比较时,我遇到了问题。
cout << ((0.7 + 0.2 + 0.1)==1)<<endl; //output is 0
cout << ((0.7 + 0.1 + 0.2)==1)<<endl; //output is 1
为什么这些值会不同?
当我添加三个浮点值并将它们与 1 进行比较时,我遇到了问题。
cout << ((0.7 + 0.2 + 0.1)==1)<<endl; //output is 0
cout << ((0.7 + 0.1 + 0.2)==1)<<endl; //output is 1
为什么这些值会不同?
浮点加法不一定是关联的。如果您更改添加的顺序,这可能会改变结果。
关于该主题的标准论文是What Every Computer Scientist Should Know about Floating Point Arithmetic。它给出了以下示例:
另一个灰色地带涉及括号的解释。由于舍入误差,代数的结合定律不一定适用于浮点数。例如,当 x = 1e30、y = -1e30 和 z = 1 时,表达式 (x+y)+z 与 x+(y+z) 的答案完全不同(前者为 1,后者为 0 )。
对于当前流行的机器和软件,可能的情况是:
编译器编码.7
为 0x1.6666666666666p-1(这是十六进制数字 1.6666666666666 乘以 2 的 -1 次方)、.2
0x1.999999999999ap-3 和.1
0x1.999999999999ap-4。这些中的每一个都是以浮点表示的最接近您编写的十进制数字的数字。
请注意,这些十六进制浮点常量中的每一个都在其有效数字中恰好有 53 位(“分数”部分,通常不准确地称为尾数)。有效数字的十六进制数字有一个“1”和另外 13 个十六进制数字(每个 4 位,总共 52 个,包括“1”),这是 IEEE-754 标准为 64 位二进制浮点提供的 -点数。
让我们添加数字.7
和.2
:0x1.6666666666666p-1 和 0x1.999999999999ap-3。首先,缩放第二个数字的指数以匹配第一个。为此,我们将指数乘以 4(将“p-3”更改为“p-1”)并将有效位乘以 1/4,得到 0x0.66666666666668p-1。然后添加 0x1.6666666666666p-1 和 0x0.66666666666668p-1,得到 0x1.ccccccccccccc8p-1。请注意,该数字的有效位超过 53 位:“8”是句点后的第 14 位。浮点数不能返回这么多位的结果,所以它必须四舍五入到最接近的可表示数字。在这种情况下,有两个相同的数字,0x1.cccccccccccccp-1 和 0x1.ccccccccccccdp-1。当出现平局时,使用有效数字最低位为零的数字。“c”是偶数,“d”是奇数,所以“
接下来,将.1
(0x1.999999999999ap-4) 的数字添加到其中。同样,我们缩放以使指数匹配,因此 0x1.999999999999ap-4 变为 0x.33333333333334p-1。然后将其添加到 0x1.cccccccccccccp-1,得到 0x1.fffffffffffff4p-1。将其四舍五入为 53 位得到 0x1.fffffffffffffp-1,这就是.7+.2+.1
.
现在考虑.7+.1+.2
。对于.7+.1
,添加 0x1.6666666666666p-1 和 0x1.999999999999ap-4。回想一下,后者被缩放到 0x.33333333333334p-1。那么确切的总和是 0x1.99999999999994p-1。将其四舍五入为 53 位得到 0x1.9999999999999p-1。
然后添加.2
(0x1.999999999999ap-3) 的数字,缩放为 0x0.66666666666668p-1。确切的总和是 0x2.00000000000008p-1。浮点有效数总是从 1 开始缩放(除了特殊情况:零、无穷大和可表示范围底部的非常小的数字),因此我们将其调整为 0x1.00000000000004p0。最后,我们四舍五入到 53 位,得到 0x1.0000000000000p0。
因此,由于舍入时发生的错误,.7+.2+.1
返回 0x1.fffffffffffffp-1(略小于 1),并.7+.1+.2
返回 0x1.0000000000000p0(正好是 1)。
浮点乘法在 C 或 C++ 中不具有关联性。
证明:
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
using namespace std;
int main() {
int counter = 0;
srand(time(NULL));
while(counter++ < 10){
float a = rand() / 100000;
float b = rand() / 100000;
float c = rand() / 100000;
if (a*(b*c) != (a*b)*c){
printf("Not equal\n");
}
}
printf("DONE");
return 0;
}
在这个程序中,大约 30% 的时间,(a*b)*c
不等于a*(b*c)
.
加法和乘法都与 IEEE 743 双精度(64 位)数字无关。以下是每个示例(使用 Python 3.9.7 评估):
>>> (.1 + .2) + .3
0.6000000000000001
>>> .1 + (.2 + .3)
0.6
>>> (.1 * .2) * .3
0.006000000000000001
>>> .1 * (.2 * .3)
0.006
与 Eric 的答案类似,但需要添加,并使用 Python。
import random
random.seed(0)
n = 1000
a = [random.random() for i in range(n)]
b = [random.random() for i in range(n)]
c = [random.random() for i in range(n)]
sum(1 if (a[i] + b[i]) + c[i] != a[i] + (b[i] + c[i]) else 0 for i in range(n))