【问题标题】:Why float is more precise than it ought to be?为什么 float 比它应该的更精确?
【发布时间】:2015-03-31 04:39:37
【问题描述】:
#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....)

我错过了什么?

【问题讨论】:

  • 浮点数是二进制。他们没有“精确的十进制数字”。
  • 其实float有24位有效位(23位尾数+1个隐藏位)
  • 试试printf("%a\n", pival_float)

标签: c linux gcc floating-point


【解决方案1】:

您只有 7 位精度。圆周率是

3.1415926535897932384626433832795028841971693993751058209...

但是您将浮点近似值打印到 Pi 得到的输出是

3.14159274101257324218750000...

如您所见,数值从小数点后的第 7 位开始出现差异。

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

【讨论】:

    【解决方案2】:

    二进制浮点值不能准确表示 3.1415927(因为这不是准确的二进制小数)。它可以表示的最接近的值是 3.1415927410125732421875,所以这是您的pival_float 的实际值。当您打印带有八十位数字的pival_float 时,您会看到它的确切值,加上一堆零以表示很好的衡量。

    【讨论】:

      【解决方案3】:

      最接近 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 数字在 第 22 位 小数位。

      问题中显示的值printf() 显示在下方,与尾数中最低有效位的贡献一起出现:

      3.14159274101257324218750000000000000000000000000000000000000000000000000000000000
      0.0000002384185791015625
      

      显然,最低有效数字排列正确。如果你把上面所有的尾数贡献加起来,加上隐含的 1,然后乘以 2,你会得到 exactprintf 显示。这解释了float 值如何精确地(在零随机性的数学意义上)printf 显示的值,但下面与 pi 的比较显示只有前 6 个小数位是 准确给定我们希望它存储的特定值。

      3.14159274101257324218750000000000000000000000000000000000000000000000000000000000
      3.14159265358979323846264338327950288419716939937510582097494459230781640628620899
              ^
      

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

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

      【讨论】:

        【解决方案4】:

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

        你发现,无论你给它多少个小数点,内存中的32-bit 值总是等于unsigned107853001101000000010010010000111111011011 二进制。正如其他人所解释的那样,这是由于 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
        

        【讨论】:

          猜你喜欢
          • 2013-06-17
          • 2023-02-22
          • 1970-01-01
          • 1970-01-01
          • 2016-01-30
          • 2016-02-23
          • 2020-01-28
          • 2014-02-09
          • 2015-09-24
          相关资源
          最近更新 更多