The reason I want to have a special blog talking about this problem is that, many people I know tend to think that float number comparison is the same as integer comparison.
They usually write code like
then their unit tests fail and they don’t know what’s wrong until I helped them to print out the boolean
which turned out to be
Due to rounding errors, most floating-point numbers end up being slightly imprecise. As long as this imprecision stays small, it can usually be ignored.
However, it also means that numbers expected to be equal (e.g. when calculating the same result through different correct methods) often differ slightly, and a simple equality test fails. For example:
float a = 0.15 + 0.15
Before we can continue I need to make clear the difference between 0.1, float(0.1), and double(0.1). In C/C++ the numbers 0.1 and double(0.1) are the same thing, but when I say “0.1” in text I mean the exact base-10 number, whereas float(0.1) and double(0.1) are rounded versions of 0.1. And, to be clear, float(0.1) and double(0.1) don’t have the same value, because float(0.1) has fewer binary digits, and therefore has more error.
Here are the exact values for 0.1, float(0.1), and double(0.1):
The solution is to check not whether the numbers are exactly the same, but whether their difference is very small. The error margin that the difference is compared to is often called epsilon. The most simple form:
if( Math.abs(a-b) < 0.00001) // wrong - don't do this
This is a bad way to do it because a fixed epsilon chosen because it “looks small” could actually be way too large when the numbers being compared are very small as well. The comparison would return “true” for numbers that are quite different. And when the numbers are very large, the epsilon could end up being smaller than the smallest rounding error, so that the comparison always returns “false”.
Therefore, it is necessary to see whether the relative error is smaller than epsilon:
if( Math.abs((a-b)/b) < 0.00001 ) // still not right!
There are some important special cases where this will fail:
When both a and b are zero. 0.0/0.0 is “not a number”, which causes an exception on some platforms, or returns false for all comparisons.
When only b is zero, the division yields “infinity”, which may also cause an exception, or is greater than epsilon even when a is smaller.
It returns false when both a and b are very small but on opposite sides of zero, even when they’re the smallest possible non-zero numbers.
float maxRelDiff = FLOAT_EPSILON; // Can be 1/100,000