为什么 float 比它应该的更精确?
Why float is more precise than it ought to be?
#include <stdio.h>
#include <float.h>
int main(int argc, char** argv)
{
long double pival = 3.14159265358979323846264338327950288419716939937510582097494459230781640628620899L;
float pival_float = pival;
printf("%1.80f\n", pival_float);
return 0;
}
我在 gcc 上得到的输出是:
3.14159274101257324218750000000000000000000000000000000000000000000000000000000000
浮点数使用23位尾数。所以可以表示的最大分数是 2^23 = 8388608 = 7 位小数精度。
但上面的输出显示了 23 位十进制数字的精度 (3.14159274101257324218750)。我希望它打印 3.1415927000000000000....)
我错过了什么?
你只得到了7位精度。 Pi是
3.1415926535897932384626433832795028841971693993751058209...
但是将浮点数近似值打印到 Pi 得到的输出是
3.14159274101257324218750000...
如您所见,从小数点后的第 7 位开始,数值出现差异。
如果你要求 printf()
小数点后 80 位,它会打印出存储在浮点数中的二进制值的十进制表示的那么多位,即使那么多位远远超过浮点表示所允许的精度。
二进制浮点值不能精确表示 3.1415927(因为它不是精确的二进制小数)。它 可以 表示的最接近的值是 3.1415927410125732421875,所以这是您的 pival_float
的实际值。当你用八十位数字打印 pival_float
时,你会看到它的确切值,加上一串零作为衡量标准。
最接近 pi 的 float
值具有二进制编码...
0 10000000 10010010000111111011011
...我在符号、指数和尾数之间插入了空格。指数是有偏的,所以上面的位编码乘数 2^1 == 2,尾数编码大于 1 的小数,第一个位值一半,此后每一位值是该位的一半之前。
因此,上面的尾数位值:
1 x 0.5
0 x 0.25
0 x 0.125
1 x 0.0625
0 x 0.03125
0 x 0.015625
1 x 0.0078125
0 x 0.00390625
0 x 0.001953125
0 x 0.0009765625
0 x 0.00048828125
1 x 0.000244140625
1 x 0.0001220703125
1 x 0.00006103515625
1 x 0.000030517578125
1 x 0.0000152587890625
1 x 0.00000762939453125
0 x 0.000003814697265625
1 x 0.0000019073486328125
1 x 0.00000095367431640625
0 x 0.000000476837158203125
1 x 0.0000002384185791015625
1 x 0.00000011920928955078125
因此,乘以指数编码值“2”后的最低有效位值...
0.000 000 238 418 579 101 562 5
我添加了空格,以便更容易计算最后一个非 0 数字在 22nd 小数位。
问题显示的值 printf()
与尾数中最低有效位的贡献一起显示在下方:
3.14159274101257324218750000000000000000000000000000000000000000000000000000000000
0.0000002384185791015625
很明显,最低有效数字正确排列。如果您将上面的所有尾数加起来,加上隐含的 1,然后乘以 2,您将得到 exact 值 printf
显示。这解释了 float
值如何 精确 (在零随机性的数学意义上)printf
显示的值,但下面与 pi 的比较仅显示考虑到我们希望它存储的特定值,前 6 位小数是 准确的 。
3.14159274101257324218750000000000000000000000000000000000000000000000000000000000
3.14159265358979323846264338327950288419716939937510582097494459230781640628620899
^
在计算中,当我们真正对我们可以依赖的 精度 感兴趣时,通常会提到浮点类型的精度。我想你可能会争辩说,虽然孤立地看 float 和 double 的精度是无限的,但在使用它们来近似它们无法完美编码的数字时,必要的舍入对于大多数实际目的来说是随机的,从这个意义上说,它们提供了有限的显着性编码此类数字的精度位数。
所以,printf
显示这么多数字并没有错;某些应用程序可能正在使用 float
来编码 精确 数字(几乎可以肯定是因为应用程序计算的性质涉及 1/2^n 值的总和),但是那' d 是例外而不是规则。
继续 Tony 的回答,以实用的方式向自己证明这种小数精度限制的一种方法是简单地声明 pi
到任意多的小数点,同时将值分配给 float
。然后看看它是如何存储在内存中的。
你发现的是,无论你给它多少个小数点,内存中的 32-bit
值将始终等于 unsigned
值 1078530011
或 01000000010010010000111111011011
二进制。正如其他人所解释的那样,这是由于 IEEE-754 单精度浮点格式 下面是一段简单的代码,可让您向自己证明此限制意味着 pi
,作为浮点数,精度限制为六位小数:
#include <stdio.h>
#include <stdlib.h>
#if defined (__LP64__) || defined (_LP64)
# define BUILD_64 1
#endif
#ifdef BUILD_64
# define BITS_PER_LONG 64
#else
# define BITS_PER_LONG 32
#endif
char *binpad (unsigned long n, size_t sz);
int main (void) {
float fPi = 3.1415926535897932384626433;
printf ("\n fPi : %f, in memory : %s unsigned : %u\n\n",
fPi, binpad (*(unsigned*)&fPi, 32), *(unsigned*)&fPi);
return 0;
}
char *binpad (unsigned long n, size_t sz)
{
static char s[BITS_PER_LONG + 1] = {0};
char *p = s + BITS_PER_LONG;
register size_t i;
for (i = 0; i < sz; i++)
*(--p) = (n>>i & 1) ? '1' : '0';
return p;
}
输出
$ ./bin/ieee754_pi
fPi : 3.141593, in memory : 01000000010010010000111111011011 unsigned : 1078530011
#include <stdio.h>
#include <float.h>
int main(int argc, char** argv)
{
long double pival = 3.14159265358979323846264338327950288419716939937510582097494459230781640628620899L;
float pival_float = pival;
printf("%1.80f\n", pival_float);
return 0;
}
我在 gcc 上得到的输出是:
3.14159274101257324218750000000000000000000000000000000000000000000000000000000000
浮点数使用23位尾数。所以可以表示的最大分数是 2^23 = 8388608 = 7 位小数精度。
但上面的输出显示了 23 位十进制数字的精度 (3.14159274101257324218750)。我希望它打印 3.1415927000000000000....)
我错过了什么?
你只得到了7位精度。 Pi是
3.1415926535897932384626433832795028841971693993751058209...
但是将浮点数近似值打印到 Pi 得到的输出是
3.14159274101257324218750000...
如您所见,从小数点后的第 7 位开始,数值出现差异。
如果你要求 printf()
小数点后 80 位,它会打印出存储在浮点数中的二进制值的十进制表示的那么多位,即使那么多位远远超过浮点表示所允许的精度。
二进制浮点值不能精确表示 3.1415927(因为它不是精确的二进制小数)。它 可以 表示的最接近的值是 3.1415927410125732421875,所以这是您的 pival_float
的实际值。当你用八十位数字打印 pival_float
时,你会看到它的确切值,加上一串零作为衡量标准。
最接近 pi 的 float
值具有二进制编码...
0 10000000 10010010000111111011011
...我在符号、指数和尾数之间插入了空格。指数是有偏的,所以上面的位编码乘数 2^1 == 2,尾数编码大于 1 的小数,第一个位值一半,此后每一位值是该位的一半之前。
因此,上面的尾数位值:
1 x 0.5
0 x 0.25
0 x 0.125
1 x 0.0625
0 x 0.03125
0 x 0.015625
1 x 0.0078125
0 x 0.00390625
0 x 0.001953125
0 x 0.0009765625
0 x 0.00048828125
1 x 0.000244140625
1 x 0.0001220703125
1 x 0.00006103515625
1 x 0.000030517578125
1 x 0.0000152587890625
1 x 0.00000762939453125
0 x 0.000003814697265625
1 x 0.0000019073486328125
1 x 0.00000095367431640625
0 x 0.000000476837158203125
1 x 0.0000002384185791015625
1 x 0.00000011920928955078125
因此,乘以指数编码值“2”后的最低有效位值...
0.000 000 238 418 579 101 562 5
我添加了空格,以便更容易计算最后一个非 0 数字在 22nd 小数位。
问题显示的值 printf()
与尾数中最低有效位的贡献一起显示在下方:
3.14159274101257324218750000000000000000000000000000000000000000000000000000000000
0.0000002384185791015625
很明显,最低有效数字正确排列。如果您将上面的所有尾数加起来,加上隐含的 1,然后乘以 2,您将得到 exact 值 printf
显示。这解释了 float
值如何 精确 (在零随机性的数学意义上)printf
显示的值,但下面与 pi 的比较仅显示考虑到我们希望它存储的特定值,前 6 位小数是 准确的 。
3.14159274101257324218750000000000000000000000000000000000000000000000000000000000
3.14159265358979323846264338327950288419716939937510582097494459230781640628620899
^
在计算中,当我们真正对我们可以依赖的 精度 感兴趣时,通常会提到浮点类型的精度。我想你可能会争辩说,虽然孤立地看 float 和 double 的精度是无限的,但在使用它们来近似它们无法完美编码的数字时,必要的舍入对于大多数实际目的来说是随机的,从这个意义上说,它们提供了有限的显着性编码此类数字的精度位数。
所以,printf
显示这么多数字并没有错;某些应用程序可能正在使用 float
来编码 精确 数字(几乎可以肯定是因为应用程序计算的性质涉及 1/2^n 值的总和),但是那' d 是例外而不是规则。
继续 Tony 的回答,以实用的方式向自己证明这种小数精度限制的一种方法是简单地声明 pi
到任意多的小数点,同时将值分配给 float
。然后看看它是如何存储在内存中的。
你发现的是,无论你给它多少个小数点,内存中的 32-bit
值将始终等于 unsigned
值 1078530011
或 01000000010010010000111111011011
二进制。正如其他人所解释的那样,这是由于 IEEE-754 单精度浮点格式 下面是一段简单的代码,可让您向自己证明此限制意味着 pi
,作为浮点数,精度限制为六位小数:
#include <stdio.h>
#include <stdlib.h>
#if defined (__LP64__) || defined (_LP64)
# define BUILD_64 1
#endif
#ifdef BUILD_64
# define BITS_PER_LONG 64
#else
# define BITS_PER_LONG 32
#endif
char *binpad (unsigned long n, size_t sz);
int main (void) {
float fPi = 3.1415926535897932384626433;
printf ("\n fPi : %f, in memory : %s unsigned : %u\n\n",
fPi, binpad (*(unsigned*)&fPi, 32), *(unsigned*)&fPi);
return 0;
}
char *binpad (unsigned long n, size_t sz)
{
static char s[BITS_PER_LONG + 1] = {0};
char *p = s + BITS_PER_LONG;
register size_t i;
for (i = 0; i < sz; i++)
*(--p) = (n>>i & 1) ? '1' : '0';
return p;
}
输出
$ ./bin/ieee754_pi
fPi : 3.141593, in memory : 01000000010010010000111111011011 unsigned : 1078530011