Why comparing double and float leads to unexpected

2019-01-03 17:18发布

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

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

Why is it so?

5条回答
我欲成王,谁敢阻挡
2楼-- · 2019-01-03 17:26

The IEEE 754 32-bit float can store: 1.1000000238...
The IEEE 754 64-bit double can store: 1.1000000000000000888...

See why they're not "equal"?


In IEEE 754, fractions are stored in powers of 2:

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

Now we need a way to represent 0.1. This is (a simplified version of) the 32-bit IEEE 754 representation (float):

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

With 64-bit double, it's even more accurate. It doesn't stop at 2^(-25), it keeps going for about twice as much. (2^(-48) + 2^(-49) + 2^(-51), maybe?)


Resources

IEEE 754 Converter (32-bit)

查看更多
我想做一个坏孩纸
3楼-- · 2019-01-03 17:34

Floats and doubles are stored in a binary format that can not represent every number exactly (it's impossible to represent the infinitely many possible different numbers in a finite space).

As a result they do rounding. Float has to round more than double, because it is smaller, so 1.1 rounded to the nearest valid Float is different to 1.1 rounded to the nearest valud Double.

To see what numbers are valid floats and doubles see Floating Point

查看更多
聊天终结者
4楼-- · 2019-01-03 17:35

Try running this code, the results will make the reason obvious.

#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;
}

The output:

1.100000000000000088817841970012523233890533447265625
1.10000002384185791015625
1.10000002384185791015625

Neither float nor double can represent 1.1 accurately. When you try to do the comparison the float number is implicitly upconverted to a double. The double data type can accurately represent the contents of the float, so the comparison yields false.

查看更多
Lonely孤独者°
5楼-- · 2019-01-03 17:35

Generally you shouldn't compare floats to floats, doubles to doubles, or floats to doubles using ==.

The best practice is to subtract them, and check if the absolute value of the difference is less than a small epsilon.

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

One reason is because floating point numbers are (more or less) binary fractions, and can only approximate many decimal numbers. Many decimal numbers must necessarily be converted to repeating binary "decimals", or irrational numbers. This will introduce a rounding error.

From wikipedia:

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.

In your particular case, a float and double will have different rounding for the irrational/repeating fraction that must be used to represent 1.1 in binary. You will be hard pressed to get them to be "equal" after their corresponding conversions have introduced different levels of rounding error.

The code I gave above solves this by simply checking if the values are within a very short delta. Your comparison changes from "are these values equal?" to "are these values within a small margin of error from each other?"

Also, see this question: What is the most effective way for float and double comparison?

There are also a lot of other oddities about floating point numbers that break a simple equality comparison. Check this article for a description of some of them:

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

查看更多
Fickle 薄情
6楼-- · 2019-01-03 17:43

The important factors under consideration with float or double numbers are:
Precision & Rounding


Precision:
The precision of a floating point number is how many digits it can represent without losing any information it contains.

Consider the fraction 1/3. The decimal representation of this number is 0.33333333333333… with 3′s going out to infinity. An infinite length number would require infinite memory to be depicted with exact precision, but float or double data types typically only have 4 or 8 bytes. Thus Floating point & double numbers can only store a certain number of digits, and the rest are bound to get lost. Thus, there is no definite accurate way of representing float or double numbers with numbers that require more precision than the variables can hold.


Rounding:
There is a non-obvious differences between binary and decimal (base 10) numbers.
Consider the fraction 1/10. In decimal, this can be easily represented as 0.1, and 0.1 can be thought of as an easily representable number. However, in binary, 0.1 is represented by the infinite sequence: 0.00011001100110011…

An example:

#include <iomanip>
int main()
{
    using namespace std;
    cout << setprecision(17);
    double dValue = 0.1;
    cout << dValue << endl;
}

This output is:

0.10000000000000001

And not

0.1.

This is because the double had to truncate the approximation due to it’s limited memory, which results in a number that is not exactly 0.1. Such an scenario is called a Rounding error.


Whenever comparing two close float and double numbers such rounding errors kick in and eventually the comparison yields incorrect results and this is the reason you should never compare floating point numbers or double using ==.

The best you can do is to take their difference and check if it is less than an epsilon.

abs(x - y) < epsilon
查看更多
登录 后发表回答