繁体   English   中英

浮点加法和乘法是结合的吗?

[英]Is floating-point addition and multiplication associative?

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

为什么这些值会不同?

浮点加法不一定是结合的。 如果您更改添加内容的顺序,这可能会改变结果。

关于这个主题的标准论文是 每个计算机科学家应该知道的关于浮点运算的知识 它给出了以下示例:

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

使用当前流行的机器和软件,可能的是:

编译器编码的.7作为0x1.6666666666666p-1(这是乘以2至-1的功率十六进制数字1.6666666666666), .2作为0x1.999999999999ap-3和.1作为0x1.999999999999ap-4。 其中每一个都是可表示为最接近您所写的十进制数字的浮点数。

观察到这些十六进制浮点常量中的每一个在其有效数(“分数”部分,通常不准确地称为尾数)中都恰好有 53 位。 有效数的十六进制数字有一个“1”和另外 13 个十六进制数字(每个 4 位,总共 52 位,包括“1”在内的 53 位),这是 IEEE-754 标准规定的,用于 64 位二进制浮点数。点数。

让我们添加.7.2的数字:0x1.6666666666666p-1 和 0x1.999999999999ap-3。 首先,缩放第二个数字的指数以匹配第一个数字。 为此,我们将指数乘以 4(将“p-3”更改为“p-1”)并将有效数乘以 1/4,得到 0x0.666666666666668p-1。 然后添加 0x1.6666666666666p-1 和 0x0.66666666666668p-1,得到 0x1.ccccccccccccc8p-1。 请注意,此数字的有效数位超过 53 位:“8”是句点后的第 14 位。 浮点不能返回这么多位的结果,所以它必须四舍五入到最接近的可表示数。 在这种情况下,有两个同样接近的数字,0x1.cccccccccccccp-1 和 0x1.ccccccccccccdp-1。 当出现平局时,使用在有效数的最低位中带有零的数字。 “c”是偶数,“d”是奇数,所以使用“c”。 添加的最终结果是0x1.cccccccccccccp-1。

接下来,将.1 (0x1.999999999999ap-4) 的数字添加到其中。 同样,我们缩放以使指数匹配,因此 0x1.999999999999ap-4 变为 0x.33333333333334p-1。 然后将其添加到 0x1.cccccccccccccp-1,给出 0x1.ffffffffffffff4p-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)

与 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))

加法和乘法都不与 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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM