【问题标题】:C floating point precision [duplicate]C浮点精度[重复]
【发布时间】:2021-09-16 00:17:24
【问题描述】:

可能重复:
Floating point comparison

我对 C/C++ 中浮点数的准确性有疑问。当我执行下面的程序时:

#include <stdio.h>

int main (void) {
    float a = 101.1;
    double b = 101.1;
    printf ("a: %f\n", a);
    printf ("b: %lf\n", b);
    return 0;
}

结果:

a: 101.099998
b: 101.100000

我相信 float 应该有 32 位所以应该足以存储 101.1 为什么?

【问题讨论】:

    标签: c++ c floating-point floating-point-conversion


    【解决方案1】:

    您需要阅读更多关于how floating-point numbers work 的信息,尤其是representable numbers 上的部分。

    对于为什么认为“32 位对 101.1 来说应该足够了”,你没有给出太多解释,所以很难反驳。

    二进制浮点数不适用于所有十进制数,因为它们基本上存储数字,等待它,以 2 为底。与二进制一样。

    这是众所周知的事实,这就是为什么例如钱永远不应该用浮点数处理。

    【讨论】:

    • 你能举例说明101.1是如何存储在计算机中的吗?
    • 101.1 当然可以用 32 位表示。只是没有硬件支持的任何常用浮点格式。
    • @Jeremy 这取决于系统。我会向初学者推荐 Wikipedia 文章“浮点数”,尽管它没有为您提供足够的信息来实际开始使用它们。文章 [What Every Computer Scientist Should Know About Floating-Point Arithmetic] (docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) 是我所知道的最好的介绍。
    • 例如可以用定点999V9BCD格式i 16位表示为0001 0001 0001 0001
    【解决方案2】:

    如果double 的打印数字更多,您会发现即使double 也无法准确表示:

     printf ("b: %.16f\n", b);
    
     b: 101.0999999999999943
    

    问题是floatdouble 使用二进制格式,并不是所有的浮点数都可以用二进制格式精确表示。

    【讨论】:

      【解决方案3】:

      很遗憾,大多数十进制浮点数无法用(机器)浮点精确表示。这就是事情的运作方式。

      例如,二进制中的数字 101.1 将表示为 1100101.0(0011)0011 部分将永远重复),因此无论您必须存储多少字节,它都永远不会准确。 Here 是一篇关于浮点二进制表示的小文章,here 你可以找到一些将浮点数转换为二进制的例子。

      如果你想了解更多关于这个主题的信息,我可以推荐你this article,虽然它很长而且不太容易阅读。

      【讨论】:

      • 更多是词汇问题,但我会说“大多数实数不能用(机器)浮点数准确表示”,或者“大多数十进制浮点数不能用(机器)准确表示浮点”。 (后者显然只有在机器浮点不是十进制的情况下才是正确的。但是虽然我过去使用过带十进制浮点的机器,但我认为今天只有基数 2、8 和 16 仍然存在。)
      • @James,编辑了答案,谢谢 ;)
      【解决方案4】:

      你只能用 IEEE754 精确地表示数字(至少对于单精度和双精度二进制格式),如果它们可以通过将两个的倒数相加来构造(即,2<sup>-n</sup>,如11/21/41/65536 等)取决于可用于精度的位数。

      在浮点数(23 位精度)双倍(52 位精度)提供的缩放比例范围内,没有任何组合可以让您精确到 101.1。 p>

      如果您想快速了解这种二次幂的工作原理,请参阅this answer

      将该答案中的知识应用于您的101.1 数字(作为单精度浮点数):

      s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm    1/n
      0 10000101 10010100011001100110011
                 |  | |   ||  ||  ||  |+- 8388608
                 |  | |   ||  ||  ||  +-- 4194304
                 |  | |   ||  ||  |+-----  524288
                 |  | |   ||  ||  +------  262144
                 |  | |   ||  |+---------   32768
                 |  | |   ||  +----------   16384
                 |  | |   |+-------------    2048
                 |  | |   +--------------    1024
                 |  | +------------------      64
                 |  +--------------------      16
                 +-----------------------       2
      

      其中的尾数部分实际上一直持续到 101.1

      mmmmmmmmm mmmm mmmm mmmm mm
      100101000 1100 1100 1100 11|00 1100 (and so on).
      

      因此,这不是精度问题,没有多少有限位可以准确地以 IEEE754 格式表示该数字。

      使用位来计算实际数(最接近的近似值),符号为正。指数为 128+4+1 = 133 - 127 偏差 = 6,因此乘数为 26 或 64。

      尾数由 1(隐式基数)加上(对于所有这些位,每个位值 1/(2n),因为 n 从 1 开始并向右增加),@987654332 @。

      当你把所有这些加起来,你会得到1.57968747615814208984375

      当您将其乘以之前计算的乘数 64 时,您会得到 101.09999847412109375

      所有数字都是用bc 使用 100 位小数的比例计算的,导致很多尾随零,因此数字应该非常准确。更重要的是,因为我检查了结果:

      #include <stdio.h>
      int main (void) {
          float f = 101.1f;
          printf ("%.50f\n", f);
          return 0;
      }
      

      给了我101.09999847412109375000...

      【讨论】:

      • "... 仅在 IEEE754 中精确表示数字,如果它们可以通过相加 ... 2 的倒数..." 似乎不完整,因为 IEEE754 还定义了带有倒数的浮点数十的幂。当然,IEEE754 二进制格式更常见。
      • @chux,这是一个非常有效的观点,调整了答案以明确这一点。
      【解决方案5】:

      您的号码101.1 在基础10 中是1100101.0(0011) 在基础2 中。 0011 部分正在重复。因此,无论您有多少位数,数字都无法在计算机中准确表示。

      查看IEE754 浮点标准,您会发现为什么double 版本似乎完全显示了它。

      PS:101.1 在基数 10 中的推导是 1100101.0(0011) 在基数 2 中:

      101 = 64 + 32 + 4 + 1
      101 -> 1100101
      
      .1 * 2 =  .2 -> 0
      .2 * 2 =  .4 -> 0
      .4 * 2 =  .8 -> 0
      .8 * 2 = 1.6 -> 1
      .6 * 2 = 1.2 -> 1
      .2 * 2 =  .4 -> 0
      .4 * 2 =  .8 -> 0
      .8 * 2 = 1.6 -> 1
      .6 * 2 = 1.2 -> 1
      .2 * 2 =  .4 -> 0
      .4 * 2 =  .8 -> 0
      .8 * 2 = 1.6 -> 1
      .6 * 2 = 1.2 -> 1
      .2 * 2 =  .4 -> 0
      .4 * 2 =  .8 -> 0
      .8 * 2 = 1.6 -> 1
      .6 * 2 = 1.2 -> 1
      .2 * 2 =  .4 -> 0
      .4 * 2 =  .8 -> 0
      .8 * 2 = 1.6 -> 1
      .6 * 2 = 1.2 -> 1
      .2 * 2....
      

      PPS:如果您想将1/3 的结果完全存储在基础10 中,也是一样的。

      【讨论】:

        【解决方案6】:

        您在这里看到的是两个因素的组合:

        • IEEE754 浮点表示法无法准确表示一整类有理数和所有无理数
        • printf 中舍入的效果(默认为 6 位小数)。也就是说,使用double 时的错误发生在第 6 个 DP 右侧的某处。

        【讨论】:

          猜你喜欢
          • 2011-02-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-11-13
          • 2021-02-11
          相关资源
          最近更新 更多