Comparing Floating Point Numbers in Java

Comparison of floating point numbers in Java. Illustration of an unbalanced scale with the word double on each side and a question mark in the middle.

In the last post, we looked at the binary representation of floating point numbers. We also printed out the three components of a floating point number in a variety of ways.

The previous posts on floating point numbers have been somewhat theoretical. How do these theoretical aspects affect us in everyday real-life programming?

Let’s think about a common everyday requirement of comparing two floating point numbers.

Comparing Integral Numbers

Comparing integral numbers, i.e. whole numbers without a decimal component, is dead easy. We do it all the time without thinking twice about it.

Here’s a simple code snippet:

int a = 42;
int b = 43;

if (a == b) {
    // do something
}

The result will be exactly the same even if we had calculated the value of a in any other way:

int a = 21 * 2;
// or 
int a = 84 / 2;
// or 
int a = 420000 / 10000;
// etc, etc.

Why is this? Integral values are exact within their range. They don’t have any rounding errors in their binary representation. If the least significant bit changes from a 0 to a 1, then the number changes by one, nothing more and nothing less.

What About Floating Point Numbers?

However, when we do floating point calculations, the results can often be slightly imprecise. This is due to rounding errors. The rounding errors are generally very small, and depending on the calculations, can often be ignored.

The rounding errors can affect us a lot when we compare numbers that we’re expecting to be the same.

Let’s say we were comparing two floating point numbers, for example salaries paid to employees:

public class Employee extends Person {

    private double salary;

    // Overridden equals() method
    @Override
    public boolean equals(Object otherObject) {
        // call the equals() method of the superclass
        if (!super.equals(otherObject))
            return false;
        Employee temp = (Employee)otherObject;
        return salary == temp.salary; // what could go wrong here?
    }

} // end of class

If we use the == operator to compare the two salaries, we might be surprised when two supposedly identical double values aren’t equal. This can happen when the two salaries are calculated differently, which will result in different rounding errors.

Example Code

Let’s illustrate this in the following simple program:

public static void main(String args[]) {

    double a = 0.3F; // bigger errors when mixing float and double
    double b = 0.3D;
    double c = 0.1D + 0.2D;

    System.out.println("a = " + a);
    System.out.println("b = " + b);
    System.out.println("c = " + c);

    System.out.println();

    System.out.printf("a = %.17f%n", a);
    System.out.printf("b = %.17f%n", b);
    System.out.printf("c = %.17f%n", c);
}

The results are:

a = 0.30000001192092896
b = 0.3
c = 0.30000000000000004

a = 0,30000001192092896
b = 0,30000000000000000
c = 0,30000000000000004

Obviously if we compared any of these values with the == operator, we wouldn’t have got the results we expected.

Possible Solutions

There are a number of possible solutions to the comparison problem.

  • We can subtract the two numbers and check if the difference between them is very small. For general floating point usage we would compare this difference to a value called epsilon. However, for our domain of employee salaries, we could check that the difference is less than a few cents or tenths of cents. Check the references at the end of the post for a nearlyEqual() method which you could use.

  • We can convert the two numbers to their integral representations, and subtract the two values. This difference indicate how many ULPs (Units in the Last Place) the two numbers differ by. If we subtract the two integrals and get a value of 1, then the two values are as close as they can be without actually being equal (see the next section on epsilon). If we get a value of 2, then the two values are still really close, with just one float value between them. Comparing numbers using ULPs works well in the range of normal numbers. It’s a bit more complex at the extremes of floating-point numbers (like infinity, zero and NaN). Java provides library functionality to convert floating point values to their integral representations (see my previous post).

Epsilon For the More Technically Minded

Epsilon is a hardware-dependent value that defines an upper bound on floating point errors. It is equivalent to the difference between 1.0 and the smallest representable value that is greater than 1.0. We can think of this as the difference between two floating point values that only differ in their least significant bit being a 0 or a 1.

Go to https://float.exposed/0x3ff0000000000000, select double precision, and toggle the very last (rightmost) bit by clicking on it.

This shows an epsilon value of about 2.22e-016 (the mathematical value is 2.220446049250313e−016). This is the value when the significand/mantissa is 53 bits.

Further Reading and Signing Off

You can read more on how to compare floating point numbers at https://floating-point-gui.de/errors/comparison/.

The previous link includes a nearlyEqual() method that works relatively well, but is fairly complex and includes non-obvious code. They also have a test suite for the nearlyEqual() method.

For some of the more mind-blowing intricacies of floating-point maths, see the tech blog of Bruce Dawson. He has hugely detailed coverage on comparisons using epsilon and ULP, and a lot of other floating point gotchas.

Was this useful? Please share your comments, and as always, stay safe and keep learning!

Leave a Comment

Your email address will not be published. Required fields are marked *

Thank You

We're Excited!

Thank you for completing the form. We're excited that you have chosen to contact us about training. We will process the information as soon as we can, and we will do our best to contact you within 1 working day. (Please note that our offices are closed over weekends and public holidays.)

Don't Worry

Our privacy policy ensures your data is safe: Incus Data does not sell or otherwise distribute email addresses. We will not divulge your personal information to anyone unless specifically authorised by you.

If you need any further information, please contact us on tel: (27) 12-666-2020 or email info@incusdata.com

How can we help you?

Let us contact you about your training requirements. Just fill in a few details, and we’ll get right back to you.