C浮点数精度

5
可能重复:

可能重复:
浮点数比较

我在 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

我认为浮点数应该使用32位,因为足以存储101.1。为什么呢?
6个回答

17
您只能在IEEE754中(至少针对单精度和双精度二进制格式)使用相加倒数的方式构建数字(例如,2-n,如11/21/41/65536等),以满足可用于精度的位数,才能准确表示数字。
在浮点数(23位精度)双精度浮点数(52位精度)提供的缩放范围内,没有任何倒数幂的组合可以使您准确达到101.1。
如果您想快速了解这种倒数幂的工作原理,请参见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开始向右增加),{1/2, 1/16, 1/64, 1/1024, 1/2048, 1/16384, 1/32768, 1/262144, 1/524288, 1/4194304, 1/8388608}
将所有这些相加,得到1.57968747615814208984375
当你把它乘以之前计算的乘数64时,你会得到101.09999847412109375

所有数字均使用100位小数计算,使用bc进行计算,因此会有很多末尾的零,因此数字应该非常准确。而且我还使用以下方式检查了结果:

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

这也让我得到了 101.09999847412109375000...


“…只有当数字可以从加法中构建出倒数幂的二进制表示时,它们才能在IEEE754中精确表示数字。”这似乎是不完整的,因为IEEE754还定义了十进制倒数幂的浮点数。当然,IEEE754二进制格式更为常见。 - chux - Reinstate Monica
@chux,你说得很有道理,我调整了答案以使其更加清晰明了。 - paxdiablo

4
你需要更多地了解浮点数如何工作,特别是关于可表示数字的部分。
你没有给出太多关于为什么你认为“32位应该足够101.1”的解释,所以很难反驳。
二进制浮点数并不适用于所有十进制数,因为它们基本上是按照二进制存储数字。
这是一个众所周知的事实,这就是为什么比如钱在浮点数中永远不应该被处理的原因。

1
你能举个例子说明101.1在计算机中是如何存储的吗? - Jeremy
2
101.1可以用32位表示,只是不能使用硬件支持的任何常规浮点格式。 - James Kanze
@Jeremy 这取决于系统。我建议从维基百科文章“浮点数”开始,虽然它并不能为您提供足够的信息来实际使用它们。文章“计算机科学家应该了解的浮点运算知识”是我所知道的最好的介绍。 - James Kanze
例如,它可以用固定点999V9BCD格式表示为16位,如0001 0001 0001 0001 - paxdiablo

4

你的数字 101.1 在十进制下是 1100101.0(0011) ,在二进制下是 2。其中 0011 部分是重复的,因此无论有多少位数,计算机都无法精确表示该数字。

查看IEEE754浮点数标准,您可以找出为什么 double 版本似乎完全显示了它。

PS:将 101.1 转换为二进制:1100101.0(0011)

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的精确结果存储在十进制中,方法也是一样的。


3

如果你增加了double类型的输出位数,你会发现即使是double也无法被准确表示:

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

 b: 101.0999999999999943

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


2

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

  • IEEE754浮点表示法不能准确地表示整个有理数类和所有无理数
  • printf中的四舍五入效果(默认情况下为6位小数)。也就是说,在使用double时,误差发生在第6位小数点的右侧。

1

很遗憾,大多数十进制浮点数无法在(机器)浮点数中准确表示。这就是事实。

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

如果您想更深入地了解这个主题,我可以向您推荐这篇文章,尽管它很长且不太容易阅读。


更多是词汇问题,但我会说“大多数实数不能在(机器)浮点数中准确表示”,或者“大多数十进制浮点数不能在(机器)浮点数中准确表示”。(如果机器浮点数不是十进制,则后者显然是错误的。但是虽然我以前使用过带有十进制浮点数的机器,但我认为今天只有2、8和16进制仍然存在。) - James Kanze

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接