C++ 浮点加法和乘法有关联吗?

C++ 浮点加法和乘法有关联吗?,c++,floating-point,C++,Floating Point,我在添加三个浮点值并将它们与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 cout浮点加法不一定是关联的。如果您更改了添加内容的顺序,则可能会更改结果 关于这一主题的标准论文是。它给出了以下示例: 另一个灰色区域涉及括号的解释。由于舍入误差,代

我在添加三个浮点值并将它们与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

cout浮点加法不一定是关联的。如果您更改了添加内容的顺序,则可能会更改结果

关于这一主题的标准论文是。它给出了以下示例:

另一个灰色区域涉及括号的解释。由于舍入误差,代数的结合定律不一定适用于浮点数。例如,当x=1e30、y=-1e30和z=1(前者为1,后者为0)时,表达式(x+y)+z的答案与x+(y+z)的答案完全不同


对于目前流行的机器和软件,可能的情况是:

编译器将
.7
编码为0x1.6666666p-1(这是十六进制数字1.6666666乘以2的-1次方),
.2
编码为0x1.9999999999ap-3,
.1
编码为0x1.9999999999999ap-4。这些数字中的每一个都是最接近您所写的十进制数字的浮点数字

请注意,这些十六进制浮点常量的有效位(分数部分,通常被错误地称为尾数)中都有53位。有效位的十六进制数字有一个“1”和十三个以上的十六进制数字(每个四位,总共52位,53位包括“1”),这是IEEE-754标准为64位二进制浮点数提供的

让我们为
.7
.2
添加数字:0x1.6666666P-1和0x1.999999999AP-3。首先,缩放第二个数字的指数以匹配第一个数字。为此,我们将指数乘以4(将“p-3”更改为“p-1”),并将有效位乘以1/4,得到0x0.6668p-1。然后添加0x1.66666P-1和0x0.666668P-1,得到0x1.CCCC8P-1。请注意,该数字的有效位超过53位:“8”是周期后的第14位。浮点不能返回这么多位的结果,因此必须将其四舍五入到最接近的可表示数字。在这种情况下,有两个数字相等接近,0x1.cccp-1和0x1.ccdp-1。当出现平局时,将使用有效位最低位为零的数字。“c”是偶数,“d”是奇数,所以使用“c”。添加的最终结果为0x1.cccp-1

接下来,将
.1
的编号(0x1.9999999999999 AP-4)添加到该编号中。我们再次缩放以使指数匹配,因此0x1.9999999999ap-4变为0x.33333333334p-1。然后将其添加到0x1.cccccccp-1,得到0x1.fffffffffff4p-1。将其舍入到53位将得到0x1.fffffffffffp-1,这是
.7+.2+.1
的最终结果

现在考虑<代码> . 7 + . 1 + . 2 < /代码>。对于

.7+.1
,添加0x1.6666666P-1和0x1.99999999AP-4。回想一下,后者的比例为0x.33334p-1。那么精确的和就是0x1.9999999994p-1。将其舍入到53位,得到0x1.999999999P-1

然后为
.2
(0x1.9999999999999 AP-3)添加数字,该数字缩放为0x0.66666668P-1。精确总和为0x2.00000000000008p-1。浮点有效位总是以1开始缩放(特殊情况除外:零、无穷大和可表示范围底部的非常小的数字),因此我们将其调整为0x1.00000000000004p0。最后,我们四舍五入到53位,得到0x1.0000000000000p0


这样,因为舍入时发生错误,<代码> 7 + 2 + 1 .<代码>返回0x1.ffffffffffffffp-1(非常小于1),而<代码> 7 + 1 + 2 > <代码>返回0x1.00万,000 000 p0(正好1)。< /P> < P> <强>浮点乘法在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;
}
#包括
#包括
#包括
使用名称空间std;
int main(){
int计数器=0;
srand(时间(空));
while(计数器+++<10){
浮动a=rand()/100000;
浮动b=rand()/100000;
浮点数c=rand()/100000;
如果(a*(b*c)!=(a*b)*c){
printf(“不相等\n”);
}
}
printf(“完成”);
返回0;
}

在这个程序中,大约30%的时间,
(a*b)*c
不等于
a*(b*c)

您的示例代码在可交换性而非关联性方面有所不同。演示关联性的版本应该是
(0.7+(0.1+0.2))
@MattMcNabb:+是一个二进制操作。对于浮点操作数,它是可交换的,但不是关联的。因此,如果有两个表达式产生不同的结果,则不能通过仅应用交换性来形成另一个表达式。@tmyklebu OK,因此只有当且仅当已知交换性成立时,才会检查关联性。(C++标准似乎不保证交换性),或者如果代码< RANDMAX<100000</Cal码>0%的时间。Evg:我在文本中的数字是数学数字,不是源代码或浮点数。不应将它们放入源代码样式中。我用引文编写的各种东西,例如“.0+.++.0”,都代表浮点运算所做的计算,可以用源代码格式而不是引用来显示,所以我会考虑把它当作一个编辑。@ EVG:把一些“计算”的数字放进去不是一个坏主意。与精确的数学数字相反,在源代码风格中,所以我将研究它。