为什么浮动比它应该更精确?

时间:2015-03-31 04:39:38

标签: c linux gcc floating-point

#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位mantisa。因此,可以表示的最大分数是2 ^ 23 = 8388608 = 7个精度的十进制数字。

但是上面的输出显示了23个十进制数字的精度( 3.14159274101257324218750 )。我预计它会打印 3.1415927 000000000000 ....)

我错过了什么?

4 个答案:

答案 0 :(得分:10)

你只有7位数的精度。皮是

<强> 3.141592 6535897932384626433832795028841971693993751058209 ...

但是你将浮动近似值打印到Pi的输出是

<强> 3.141592 74101257324218750000 ...

正如您所看到的,值从小数点后的第7位开始偏离。

如果你在小数位后面询问printf() 80位数,它会打印出存储在float中的二进制值的十进制表示的多个数字,即使这个数字远远超过精度浮动表示允许。

答案 1 :(得分:4)

二进制浮点值不能精确地表示3.1415927(因为它不是精确的二进制分数)。它可以表示的最近值是3.1415927410125732421875,因此它是pival_float的实际值。当您使用八十位数字打印pival_float时,您会看到它的确切值,再加上一堆零值。

答案 2 :(得分:4)

与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,您将显示完全printf。这解释了float精确地(在零随机性的数学意义上)printf显示的值,但下面与pi的比较仅显示前6个小数鉴于我们希望它存储的特定值,地点准确

3.14159274101257324218750000000000000000000000000000000000000000000000000000000000
3.14159265358979323846264338327950288419716939937510582097494459230781640628620899
        ^

在计算中,当我们真正对我们可以依赖的准确度感兴趣时,通常会引用浮点类型的精度。我想你可以争辩说,虽然浮动和双重的精度是无限的,但是当使用它们来近似它们不能完美编码的数字时所需的舍入对于大多数实际目的是随机的,并且从这个意义上它们提供有限的显着性编码这些数字的精度数字。

所以,printf显示这么多数字并没有错;某些应用程序可能正在使用float完全编号进行编码(几乎可以肯定,因为应用程序计算的性质涉及1/2 ^ n值的总和),但那就是例外而不是规则。

答案 3 :(得分:2)

继承Tony的回答,一种方法是以实用的方式向自己证明对小数精度的这种限制,只需将pi声明为任意多个小数点,同时将值分配给一个float。然后看看它是如何存储在内存中的。

你发现的是,无论你提供多少小数点,内存中的32-bit值总是相当于unsigned1078530011或{{1}在二进制文件中。正如其他人所解释的那样,这是因为 IEEE-754单精度浮点格式下面是一段简单的代码,可以让你自己证明这个限制意味着01000000010010010000111111011011,作为一个浮点数,限制为六位小数:

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;
}
相关问题