关于c ++:为什么比较double和float导致意外结果?

Why comparing double and float leads to unexpected result?

本问题已经有最佳答案,请猛点这里访问。

Possible Duplicate:
strange output in comparision of float with float literal

1
2
3
float f = 1.1;
double d = 1.1;
if(f == d) // returns false!

为什么会这样?


考虑到floatdouble数字的重要因素有:精度和舍入

精度:浮点数的精度是指在不丢失所包含的任何信息的情况下,浮点数可以表示的位数。

考虑分数1/3。这个数字的十进制表示是0.33333333333333…,其中3表示无穷大。一个无限长的数需要无限的内存以精确的精度来描述,但是floatdouble数据类型通常只有48字节。因此,浮点数和双位数只能存储一定数量的数字,其余的数字一定会丢失。因此,没有确切准确的方法用比变量更精确的数字来表示浮点数或双位数。

舍入:binarydecimal (base 10)之间的数字差异不明显。考虑分数1/10。在decimal中,这可以很容易地表示为0.1,而0.1可以被认为是一个容易表示的数字。然而,在二进制中,0.1由无限序列表示:0.00011001100110011…

一个例子:

1
2
3
4
5
6
7
8
#include <iomanip>
int main()
{
    using namespace std;
    cout << setprecision(17);
    double dValue = 0.1;
    cout << dValue << endl;
}

这个输出是:

1
0.10000000000000001

而不是

1
0.1.

这是因为double由于内存有限而必须截断近似值,从而导致一个不完全是0.1的数字。这种情况称为舍入误差。

每当比较两个接近浮点数和双精度数时,这样的舍入误差就会产生错误的结果,这就是为什么您不应该使用==比较浮点数或双精度数的原因。

你所能做的最好的就是利用它们的差异,检查它是否小于一个ε。

1
abs(x - y) < epsilon


尝试运行此代码,结果会使原因变得明显。

1
2
3
4
5
6
7
8
9
#include <iomanip>
#include <iostream>

int main()
{
  std::cout << std::setprecision(100) << (double)1.1 << std::endl;
  std::cout << std::setprecision(100) << (float)1.1 << std::endl;
  std::cout << std::setprecision(100) << (double)((float)1.1) << std::endl;
}

输出:

1
2
3
1.100000000000000088817841970012523233890533447265625
1.10000002384185791015625
1.10000002384185791015625

floatdouble都不能准确地表示1.1。当您尝试进行比较时,浮点数会隐式地向上转换为double。double数据类型可以准确地表示float的内容,因此比较结果会产生错误。


一般来说,不应该使用==比较float与float、double与double或float与double。

最佳实践是减去它们,然后检查差值的绝对值是否小于一个小的epsilon。

1
2
3
4
if(std::fabs(f - d) < std::numeric_limits<float>::epsilon())
{
    // ...
}

一个原因是因为浮点数是(或多或少)二进制分数,并且只能近似许多十进制数字。许多十进制数必须转换为重复的二进制"小数",或无理数。这将引入舍入误差。

维基百科:

For instance, 1/5 cannot be represented exactly as a floating point number using a binary base but can be represented exactly using a decimal base.

在您的特定情况下,对于非理性/重复分数,float和double将具有不同的舍入,而非理性/重复分数必须用二进制表示1.1。在相应的转换引入不同水平的舍入误差后,很难让它们"相等"。

我上面给出的代码通过简单地检查值是否在非常短的delta内来解决这个问题。您的比较从"这些值相等吗?""这些值彼此之间的误差是否在很小的范围内?"

另外,请看这个问题:什么是最有效的浮动和双重比较方法?

关于浮点数还有很多其他的奇怪之处,它们打破了简单的相等比较。查看本文了解其中一些内容的描述:

http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm


ieee 754 32位float可以存储:1.1000000238...。ieee 754 64位double可以存储:1.1000000000000000888...

明白他们为什么不"平等"?

在IEEE754中,分数以2的幂存储:

1
2
2^(-1), 2^(-2), 2^(-3), ...
1/2,    1/4,    1/8,    ...

现在我们需要一种方法来代表0.1。这是(简化版)32位IEEE754表示(float):

1
2
3
2^(-4) + 2^(-5) + 2^(-8) + 2^(-9) + 2^(-12) + 2^(-13) + ... + 2^(-24) + 2^(-25) + 2^(-27)
00011001100110011001101
1.10000002384185791015625

对于64位的double,它更精确。它不会停在江户十一号(24号),它会持续运行大约两倍。(可能是2^(-48) + 2^(-49) + 2^(-51))

Resources

IEEE 754 Converter (32-bit)


浮点数和双精度数以二进制格式存储,不能精确表示每个数字(在有限空间中不可能表示无限多个可能的不同数字)。

因此,他们进行四舍五入。浮点必须舍入超过两倍,因为它较小,所以1.1舍入到最近的有效浮点不同于1.1舍入到最近的valud double。

要查看哪些数字是有效的浮点和双精度,请参见浮点