浮点数精度
floating point number precision
我观看了 computerphile 的视频 https://www.youtube.com/watch?v=PZRI1IfStY0 并尝试理解浮点不精确的概念。我知道 0.1 不能用二进制形式精确表示。
我尝试自己做实验,我用一个float类型的变量来存储一个数字4.2。
这些是代码:
#include <stdio.h>
int main(void)
{
float m = 4.2;
printf("%f\n", m * 1);
printf("%f\n", m * 10);
printf("%f\n", m * 100);
printf("%f\n", m * 1000);
}
输出是:
4.200000
42.000000
419.999969
4200.000000
为什么只有4.2乘以100不准确?
首先,用%f
换算时,printf
四舍五入到小数点后六位。要查看本例中的完整值,您可以使用 %.20f
:
#include <stdio.h>
int main(void)
{
float m = 4.2;
printf("%.20f\n", m * 1);
printf("%.20f\n", m * 10);
printf("%.20f\n", m * 100);
printf("%.20f\n", m * 1000);
}
输出:
4.19999980926513671875
42.00000000000000000000
419.99996948242187500000
4200.00000000000000000000
要了解发生了什么,请考虑 m
的实际值。在您的 C 实现中,float
是使用 24 位的有效数字(floating-point 数字的小数部分)实现的。这通常被描述为 24 个二进制数字,在第一个位之后有一个小数点(小数点的一般版本),例如 1.000011001100110011001102。使用符号和指数,floating-point 形式将是 +1.000011001100110011001102•22.
然而,我们也可以通过相应地调整指数,将有效数字缩放为小于 224 的整数。 +1.000011001100110011001102•22 = +1000011001100110011001102•2−21。在十进制中,1000011001100110011001102 是 8,808,038,2−21 是 1/2,097,152。 8,808,038 / 2,097,152 = 4.19999980926513671875。这种使用小于 224 的整数的表示在数学上等同于带小数点的形式,但它让我们更容易看到一些舍入效果,我们将在下面看到。
当我们使用普通 real-number 算术乘以 10 时,结果将是 88,080,380 / 2,097,152 = 88,080,380 / 221。但是,该分子不适合您的 C 实现使用的 float
格式的 24 位。我们必须调整使其低于 224 = 16,777,216。通过调整指数进行调整,指数将有效数乘以或除以 2 的幂。我们可以将指数调整为三,并将分子除以 23,得到 11,010,047.5 / 218。但是现在分子不是整数。为了适应格式,它被四舍五入到最接近的整数。 11,010,047 和 11,010,048 与 11,010,047.5 的距离相同。平局的规则是使用偶数低位的选项,因此使用 11,010,048。
所以m * 10
的结果是11,010,048 / 218 = 11,010,048 / 262,144 = 42.
现在考虑乘以 100。real-number 结果为 880,803,800 / 221。为了使分子小于 16,777,216,我们将指数调整为 6,将分子除以 64。结果为 13,762,559.375 / 215。我们再次将分子四舍五入为整数,得到 13,762,559 / 215。请注意,在这种情况下,我们碰巧向下取整而不是向上取整。碰巧该分数低于 ½,因此我们向下舍入。 13,762,559 / 215 = 13,762,559 / 32,768 = 419.999969482421875.
这里发生的是乘以 10 的各种次方——1、10、100、1000(二进制:1、10102、1100100 2, 11111010002)—在这些分数中产生各种结果。由于我们从刚好低于 4.2 (4.19999980926513671875) 的数字开始,当向上舍入时,结果达到 4.2 的倍数。当有向下舍入时,它不会。
我观看了 computerphile 的视频 https://www.youtube.com/watch?v=PZRI1IfStY0 并尝试理解浮点不精确的概念。我知道 0.1 不能用二进制形式精确表示。 我尝试自己做实验,我用一个float类型的变量来存储一个数字4.2。
这些是代码:
#include <stdio.h>
int main(void)
{
float m = 4.2;
printf("%f\n", m * 1);
printf("%f\n", m * 10);
printf("%f\n", m * 100);
printf("%f\n", m * 1000);
}
输出是:
4.200000
42.000000
419.999969
4200.000000
为什么只有4.2乘以100不准确?
首先,用%f
换算时,printf
四舍五入到小数点后六位。要查看本例中的完整值,您可以使用 %.20f
:
#include <stdio.h>
int main(void)
{
float m = 4.2;
printf("%.20f\n", m * 1);
printf("%.20f\n", m * 10);
printf("%.20f\n", m * 100);
printf("%.20f\n", m * 1000);
}
输出:
4.19999980926513671875 42.00000000000000000000 419.99996948242187500000 4200.00000000000000000000
要了解发生了什么,请考虑 m
的实际值。在您的 C 实现中,float
是使用 24 位的有效数字(floating-point 数字的小数部分)实现的。这通常被描述为 24 个二进制数字,在第一个位之后有一个小数点(小数点的一般版本),例如 1.000011001100110011001102。使用符号和指数,floating-point 形式将是 +1.000011001100110011001102•22.
然而,我们也可以通过相应地调整指数,将有效数字缩放为小于 224 的整数。 +1.000011001100110011001102•22 = +1000011001100110011001102•2−21。在十进制中,1000011001100110011001102 是 8,808,038,2−21 是 1/2,097,152。 8,808,038 / 2,097,152 = 4.19999980926513671875。这种使用小于 224 的整数的表示在数学上等同于带小数点的形式,但它让我们更容易看到一些舍入效果,我们将在下面看到。
当我们使用普通 real-number 算术乘以 10 时,结果将是 88,080,380 / 2,097,152 = 88,080,380 / 221。但是,该分子不适合您的 C 实现使用的 float
格式的 24 位。我们必须调整使其低于 224 = 16,777,216。通过调整指数进行调整,指数将有效数乘以或除以 2 的幂。我们可以将指数调整为三,并将分子除以 23,得到 11,010,047.5 / 218。但是现在分子不是整数。为了适应格式,它被四舍五入到最接近的整数。 11,010,047 和 11,010,048 与 11,010,047.5 的距离相同。平局的规则是使用偶数低位的选项,因此使用 11,010,048。
所以m * 10
的结果是11,010,048 / 218 = 11,010,048 / 262,144 = 42.
现在考虑乘以 100。real-number 结果为 880,803,800 / 221。为了使分子小于 16,777,216,我们将指数调整为 6,将分子除以 64。结果为 13,762,559.375 / 215。我们再次将分子四舍五入为整数,得到 13,762,559 / 215。请注意,在这种情况下,我们碰巧向下取整而不是向上取整。碰巧该分数低于 ½,因此我们向下舍入。 13,762,559 / 215 = 13,762,559 / 32,768 = 419.999969482421875.
这里发生的是乘以 10 的各种次方——1、10、100、1000(二进制:1、10102、1100100 2, 11111010002)—在这些分数中产生各种结果。由于我们从刚好低于 4.2 (4.19999980926513671875) 的数字开始,当向上舍入时,结果达到 4.2 的倍数。当有向下舍入时,它不会。