Why Can't We Use '==' to Compare Two Float or Double Numbers

Is it safe when compare 2 float/double directly in Java?

In general, no it is not safe due to the fact that so many decimal numbers cannot be precisely represented as float or double values. The often stated solution is test if the difference between the numbers is less than some "small" value (often denoted by a greek 'epsilon' character in the maths literature).

However - you need to be a bit careful how you do the test. For instance, if you write:

if (Math.abs(a - b) < 0.000001) {
System.err.println("equal");
}

where a and b are supposed to be "the same", you are testing the absolute error. If you do this, you can get into trouble if a and b are (say_ 1,999,999.99 and 2,000,000.00 respectively. The difference between these two numbers is less than the smallest representable value at that scale for a float, and yet it is much bigger than our chosen epsilon.

Arguably, a better approach is to use the relative error; e.g. coded (defensively) as

if (a == b ||
Math.abs(a - b) / Math.max(Math.abs(a), Math.abs(b)) < 0.000001) {
System.err.println("close enough to be equal");
}

But even this is not the complete answer, because it does not take account of the way that certain calculations cause the errors to build up to unmanageable proportions. Take a look at this Wikipedia link for more details.

The bottom line is that dealing with errors in floating point calculations is a lot more difficult than it appears at first glance.


The other point to note is (as others have explained) integer arithmetic behaves very differently to floating point arithmetic in a couple of respects:

  • integer division will truncate if the result is not integral
  • integer addition subtraction and multiplication will overflow.

Both of these happen without any warning, either at compile time or at runtime.

comparing float/double values using == operator

IBM has a recommendation for comparing two floats, using division rather than subtraction - this makes it easier to select an epsilon that works for all ranges of input.

if (abs(a/b - 1) < epsilon)

As for the value of epsilon, I would use 5.96e-08 as given in this Wikipedia table, or perhaps 2x that value.

Why is comparing floats inconsistent in Java?

The difference is that 6.5 can be represented exactly in both float and double, whereas 3.2 can't be represented exactly in either type. and the two closest approximations are different.

An equality comparison between float and double first converts the float to a double and then compares the two. So the data loss.


You shouldn't ever compare floats or doubles for equality; because you can't really guarantee that the number you assign to the float or double is exact.

This rounding error is a characteristic feature of floating-point computation.

Squeezing infinitely many real numbers into a finite number of bits
requires an approximate representation. Although there are infinitely
many integers, in most programs the result of integer computations can
be stored in 32 bits.

In contrast, given any fixed number of bits,
most calculations with real numbers will produce quantities that
cannot be exactly represented using that many bits. Therefore the
result of a floating-point calculation must often be rounded in order
to fit back into its finite representation. This rounding error is the
characteristic feature of floating-point computation.

Check What Every Computer Scientist Should Know About Floating-Point Arithmetic for more!

How should I do floating point comparison?

Comparing for greater/smaller is not really a problem unless you're working right at the edge of the float/double precision limit.

For a "fuzzy equals" comparison, this (Java code, should be easy to adapt) is what I came up with for The Floating-Point Guide after a lot of work and taking into account lots of criticism:

public static boolean nearlyEqual(float a, float b, float epsilon) {
final float absA = Math.abs(a);
final float absB = Math.abs(b);
final float diff = Math.abs(a - b);

if (a == b) { // shortcut, handles infinities
return true;
} else if (a == 0 || b == 0 || diff < Float.MIN_NORMAL) {
// a or b is zero or both are extremely close to it
// relative error is less meaningful here
return diff < (epsilon * Float.MIN_NORMAL);
} else { // use relative error
return diff / (absA + absB) < epsilon;
}
}

It comes with a test suite. You should immediately dismiss any solution that doesn't, because it is virtually guaranteed to fail in some edge cases like having one value 0, two very small values opposite of zero, or infinities.

An alternative (see link above for more details) is to convert the floats' bit patterns to integer and accept everything within a fixed integer distance.

In any case, there probably isn't any solution that is perfect for all applications. Ideally, you'd develop/adapt your own with a test suite covering your actual use cases.

If operator works properly for floating-point types, why can't we use it for equality testing?

The ==, <, >, <=, >=, and != operators work just fine with floating-point numbers.

You seem to have the premise that some reasonable implementation of < ought to compare (double)0.7f equal to 0.7. This is not the case. If you cast 0.7f to a double, you get 0x1.666666p-1. However, 0.7 is equal to 0x1.6666666666666p-1. These are not numerically equal; in fact, (double)0.7f is considerably smaller than 0.7 --- it would be ridiculous for them to compare equal.

When working with floating-point numbers, it is important to remember that they are floating-point numbers, rather than real numbers or rational numbers or any other such thing. You have to take into account their properties and not the properties everyone wants them to have. Do this and you automatically avoid most of the commonly-cited "pitfalls" of working with floating-point numbers.



Related Topics



Leave a reply



Submit