How to compare float numbers

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

1
2
3
4
5
6
7
8
somefunction() {
double threeTenths1 = 0.3;
double threeTenths2 = 0.1 + 0.1 + 0.1;

if (threeTenths1 == threeTenths2) {
// ...
}
}

then their unit tests fail and they don’t know what’s wrong until I helped them to print out the boolean

1
2
3
4
5
6
7
8
9
10
11
12
13
somefunction() {
double threeTenths1 = 0.3;
double threeTenths2 = 0.1 + 0.1 + 0.1;

System.out.println(threeTenths1);
System.out.println(threeTenths2);

if (threeTenths1 == threeTenths2) {
System.out.println("Math is a world of absolute truth.");
} else {
System.out.println("This is not logical!");
}
}

Output:

1
2
3
0.3
0.30000000000000004
This is not logical!

which turned out to be false.

Comparison

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:

1
2
3
4
float a = 0.15 + 0.15
float b = 0.1 + 0.2
if(a == b) // can be false!
if(a >= b) // can also be false!

What do you mean ‘imprecise’ ?

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):

1
2
3
4
Number	    Value
0.1 0.1 (of course)
float(.1) 0.100000001490116119384765625
double(.1) 0.1000000000000000055511151231257827021181583404541015625

Don’t use absolute error margins

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:

1
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”.

Look out for edge cases

Therefore, it is necessary to see whether the relative error is smaller than epsilon:

1
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
float maxRelDiff = FLOAT_EPSILON; // Can be 1/100,000

boolean almostEqualRelatively(float a, float b) {
// Calculate the difference.
float diff = Math.abs(a - b);
a = Math.abs(a);
b = Math.abs(b);

// Find the largest
float largest = (b > a) ? b : a;

if (diff <= largest * maxRelDiff) {
return true;
} else {
return false;
}
}

References: