传递0.0时,减去浮点数会出错。

| 以下程序:
#include <stdio.h>

int main()
{
    double val = 1.0;
    int i;

    for (i = 0; i < 10; i++)
    {
        val -= 0.2;
        printf(\"%g %s\\n\", val, (val == 0.0 ? \"zero\" : \"non-zero\"));
    }

    return 0;
}
产生以下输出:
0.8 non-zero
0.6 non-zero
0.4 non-zero
0.2 non-zero
5.55112e-17 non-zero
-0.2 non-zero
-0.4 non-zero
-0.6 non-zero
-0.8 non-zero
-1 non-zero
有人能告诉我从0.2减去0.2时是什么引起错误的吗?这是舍入错误还是其他?最重要的是,如何避免该错误? 编辑:鉴于5.55112e-17非常接近于零(感谢@therefromhere提供了该信息),看来结论是不必担心它。     
已邀请:
这是因为不能以精确值将浮点数存储在内存中。因此,在浮点值中使用ѭ2绝对不安全。使用double可以提高精度,但同样会不够精确。比较浮点值的正确方法是执行以下操作: val ==目标; // 不安全 //而是这样做 // EPS是一些合适的低值,例如1e-7 晶圆厂(val-target)&lt; EPS; 编辑:正如评论中指出的那样,该问题的主要原因是无法精确存储0.2。因此,当您从某个值中减去它时,每次都会导致一些错误。如果您反复进行这种浮点计算,则在某些时候该错误将很明显。我要说的是,所有浮点值不能存储,因为它们有无限个。轻微的错误值通常不会引起注意,但是使用该错误值是连续计算会导致更高的累积错误。     
0.2不是双精度浮点数,因此将四舍五入为最接近的双精度数,即:
            0.200000000000000011102230246251565404236316680908203125
相当笨拙,所以让我们以十六进制形式查看它:
          0x0.33333333333334
现在,让我们来看看从1.0重复减去该值会发生什么:
          0x1.00000000000000
        - 0x0.33333333333334
        --------------------
          0x0.cccccccccccccc
精确结果无法用双精度表示,因此将其四舍五入,得出:
          0x0.ccccccccccccd
以十进制表示,这恰好是:
            0.8000000000000000444089209850062616169452667236328125
现在我们重复该过程:
          0x0.ccccccccccccd
        - 0x0.33333333333334
        --------------------
          0x0.9999999999999c
rounds to 0x0.999999999999a
           (0.600000000000000088817841970012523233890533447265625 in decimal)

          0x0.999999999999a
        - 0x0.33333333333334
        --------------------
          0x0.6666666666666c
rounds to 0x0.6666666666666c
           (0.400000000000000077715611723760957829654216766357421875 in decimal)

          0x0.6666666666666c
        - 0x0.33333333333334
        --------------------
          0x0.33333333333338
rounds to 0x0.33333333333338
           (0.20000000000000006661338147750939242541790008544921875 in decimal)

          0x0.33333333333338
        - 0x0.33333333333334
        --------------------
          0x0.00000000000004
rounds to 0x0.00000000000004
           (0.000000000000000055511151231257827021181583404541015625 in decimal)
因此,我们看到浮点算术所需的累积舍入产生了您正在观察的非常小的非零结果。舍入是微妙的,但它是确定性的,不是魔术,也不是错误。值得花时间学习。     
浮点算术不能完全表示所有数字。因此,像您观察到的舍入错误是不可避免的。 一种可能的策略是使用定点格式,例如十进制或货币数据类型。这样的类型仍然不能代表所有数字,但是会像您在本示例中预期的那样运行。     
详细说明一下:如果浮点数的尾数是二进制编码的(大多数现代FPU就是这种情况),那么仅数字(1 / 2、1 / 4、1 / 8)的和(整数), 1/16,...可以精确地表示为尾数。值0.2近似为1/8 + 1/16 + ....一些甚至更小的数字,但是有限的尾数无法达到0.2的精确值。 您可以尝试以下方法:
 printf(\"%.20f\", 0.2);
并且您(很可能)会发现您认为0.2的值不是0.2,而是一个数量相差很小的数字(实际上,在我的计算机上,它打印出0.20000000000000001110)。现在您了解了为什么永远无法达到0。 但是,如果让val = 12.5并在循环中减去0.125,则可能会达到零。     

要回复问题请先登录注册