通过0.0时减去浮点数出错
Error subtracting floating point numbers when passing through 0.0
以下程序:
#include <stdio.h>
int main()
{
double val = 1.0;
int i;
for (i = 0; i < 10; i++)
{
val -= 0.2;
printf("%g %sn", 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提供的信息)。
这是因为浮点数不能以精确值存储在内存中。因此,在浮点值中使用==
是不安全的。使用double将提高精度,但同样不是精确的。比较浮点值的正确方法是这样做:
编辑:正如在评论中指出的,问题的主要原因是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,则可以得到0
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 如何修复此错误:未定义对"距离(浮点数,浮点数,浮点数,浮点数,浮点数)"的引用
- C++浮点数据类型和字符串数据类型无法子到模板函数中
- 使用提升将数据从 PyObject 复制到浮点数 *
- 使用浮点数和双精度数的非常小数字的数学
- 使用英特尔内联函数将打包的 8 位整数乘以浮点数向量
- 如何在 c++ 中将小数点后两位数的浮点数分配给另一个浮点数
- 返回浮点数的小数位数
- txt 文件中浮点数的最大和最小值
- 为什么 std::cout 打印浮点数、双精度和长双精度到相同的小数精度?
- 将浮点数转换为无符号字符数组并打印出来
- 如何将时间字符串 (M:SS) 转换为浮点数
- 如何将浮点数(*)[6]转换为浮点**类型?
- C++将浮点数乘以分数
- 如何将浮点数转换为uint8_t?
- 浮点到整数转换出错(即使浮点数已经是整数)
- 对浮点数 -2.584877722073e-33 使用 log10() 时出错
- 从无符号 int 转换为浮点数时出错
- 使用EXPECT_EQ计算双精度或浮点数的和时出错
- 通过0.0时减去浮点数出错