0.1浮点数比0.1双精度浮点数大。我原本以为会是假的。

100

设:

double d = 0.1;
float f = 0.1;

是否应该表达

(f > d)

true还是false?

根据实证,答案是true。然而,我预期它应该是false

由于0.1无法在二进制中完全表示,而双精度浮点数的精度为1516位小数,单精度浮点数只有7位小数。因此,它们两者都比0.1要小,而双精度浮点数更接近0.1

我需要一个关于true的确切解释。


63
精度低并不意味着数字更少。 - zakinster
8
你为什么期望相反的结果? - user207421
11
唯一确定的事情是,双精度浮点数将更接近所需值。它可能会更小,也可能会更大。 - SinisterMJ
9
@heshameraqi: 答案(特别是Kenny的)很好地展示了发生了什么。我相信你阅读**计算机科学家应该知道的浮点数算术知识**会有所收益。 - Olivier Dulac
8
假设你正在计算三位小数和六位小数的1/9。 0.111 < 0.111111,对吗?现在假设你正在计算6/9。0.667 > 0.666667,对吗?你不能将6/9在三位小数中表示为 0.666,因为它不是最接近6/9的三位小数! - Eric Lippert
显示剩余11条评论
7个回答

173

我会说答案取决于将 double 转换为 float 时所使用的舍入模式。 float 有24个二进制位的精度,而 double 有53个。在二进制中,0.1是:

0.1₁₀ = 0.0001100110011001100110011001100110011001100110011…₂
             ^        ^         ^   ^ 
             1       10        20  24

所以,如果我们在第24位四舍五入向上,我们将得到:

0.1₁₀ ~ 0.000110011001100110011001101

该值大于精确值和53位数字下更精确的近似值。


15
准确的回答! - Barath Ravikumar
逻辑解释。 你认为Sven的答案怎么样? - Hesham Eraqi
换句话说,0.2d > 0.2f。 - ratchet freak
1
@ratchetfreak ...或者不是...这取决于语言是否实现了IEEE 754,以及它运行在哪个平台上... - woliveirajr
2
@HeshamERAQI 两个答案都是正确的 - 我猜这个答案更容易看出为什么会发生这种情况。不过,对于这两种解释,关键是要考虑在截止点处结果如何舍入。就像其他评论所说的那样,IEEE754是FP表示的事实标准,它是这样做的,但理论上可能存在FP实现只是截断而不是四舍五入。 - us2012

55

数字0.1将舍入为具有给定精度的最接近浮点表示。此近似值可能大于或小于0.1,因此如果不查看实际值,则无法预测单精度或双精度近似值更大。

以下是使用Python解释器时双精度值舍入后的结果:

>>> "%.55f" % 0.1
'0.1000000000000000055511151231257827021181583404541015625'

这里是单精度值:

>>> "%.55f" % numpy.float32("0.1")
'0.1000000014901161193847656250000000000000000000000000000'

因此,您可以看到单精度近似值更大。


4
但这将取决于具体的实现。我猜测您的结果针对 IEEE 的 "double" 和 "float" 类型,因为它们是目前最常用的,但在 IBM 或 Unisys 大型机上可能会得到不同的结果。 - James Kanze
3
这是在一台x86计算机上。Python使用本地浮点运算,而x86实现(或多或少)符合IEEE-754标准。 - Sven Marnach
这大概就是我所假设的(至少是IEEE-745):我认为几乎所有实现Python的机器都使用IEEE-754。我的观点只是相应的值在另一种架构上可能会有所不同(比如一些不使用IEEE的大型机)。 - James Kanze

19
如果您将.1转换为二进制,您会得到:
0.000110011001100110011001100110011001100110011001100...
永远重复
映射到数据类型,您将得到:
float(.1)  = %.00011001100110011001101
                                     ^--- 注意四舍五入
double(.1) = %.0001100110011001100110011001100110011001100110011010
将其转换为十进制:
float(.1)  = .10000002384185791015625
double(.1) = .100000000000000088817841970012523233890533447265625
这是Bruce Dawson撰写的一篇文章中的内容。可以在此处找到:
Doubles are not floats, so don’t compare them

你的链接页面标题已经给出了结论,所以我在你的链接中添加了它。 - Grijesh Chauhan

5

由于无法精确表示,将十进制中的1/10转换成二进制就像将十进制中的1/7进行比较一样。

1/7 = 0.142857142857... 但在不同的十进制精度下进行比较(3位小数与6位小数),我们有0.143 > 0.142857。


5

我认为Eric Lippert的评论对这个问题的解释是最清晰的,所以我会将其作为答案重新发布:

假设你正在使用三位小数和六位小数计算1/9。 0.111 < 0.111111,对吧?

现在假设你正在计算6/9。 0.667 > 0.666667,对吧?

你不能说6/9的三位小数是0.666,因为那不是最接近6/9的三位小数!


我同意,这是最清晰的解释。 - Hesham Eraqi

1
仅仅是为了补充其他关于IEEE-754和x86的答案:问题比他们所表述的更加复杂。在IEEE-754中,0.1没有“一种”表示法 - 有两种。将最后一位数字向下或向上舍入都是有效的。这种差异实际上可能会发生,因为x86并不使用64位进行内部浮点运算;它实际上使用80位!这被称为双扩展精度
因此,即使是在同一台x86编译器中,有时也会出现同一个数字以两种不同的方式表示,因为有些使用64位计算其二进制表示,而其他人则使用80位。
事实上,即使使用同一编译器,甚至在同一台机器上也可能发生这种情况!
#include <iostream>
#include <cmath>

void foo(double x, double y)
{
  if (std::cos(x) != std::cos(y)) {
    std::cout << "Huh?!?\n";  //← you might end up here when x == y!!
  }
}

int main()
{
  foo(1.0, 1.0);
  return 0;
}

请查看为什么cos(x) != cos(y)即使x == y以获取更多信息。


在文章http://arxiv.org/abs/cs/0701192中,David Monniaux提供了一个更好的问题描述,唯一的缺点是该文章描述的是Joseph S. Myers修复GCC之前的情况:http://gcc.gnu.org/ml/gcc-patches/2008-11/msg00105.html。他的帖子很好地解释了具有FLT_EVAL_METHOD> 0的编译器应该如何工作。使用这样的编译器,相同的计算会产生相同的结果。cos(1.0)cos(1.0)。这可能不是您在FLT_EVAL_METHOD = 0中得到的结果,但始终相同。 - Pascal Cuoq
一些x86编译器在某些模式下甚至会在标量数学运算中使用SSE硬件,因此中间精度将为32或64位,而不是x87 ALU的80位。 - Russell Borogove
1
我曾经因为一段代码而感到非常生气,这段代码被认为是计算函数所得到的最大值。由于这些意外的舍入问题,代码返回的是第一个最大值,而不是最后一个最大值(在平局的情况下)。花了我一段时间才意识到 x > x 可以成立! - user1196549

1

在转换中,double的等级高于float。通过进行逻辑比较,将f强制转换为double,可能会导致您正在使用的实现结果不一致。如果您以f为后缀,使编译器将其注册为float,则会得到0.00,这在double类型中是假的。未设置后缀的浮点数类型默认为double。

#include <stdio.h>
#include <float.h>

int main()
{
     double d = 0.1;
     float f = 0.1f;
     printf("%f\n", (f > d));

     return 0;
}

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