Floating point: NaN, negative zero and equality

Core Java · lesson 39 of 42 · 6 min read

Values that are not equal to themselves, and why BigDecimal has two comparisons.

Open this lesson in the learning hub

Key points

  • NaN is not equal to anything, including itself. So x != x is a valid NaN test, and a NaN in a list makes contains and sorting behave strangely.
  • Yet Double.equals says two NaNs are equal, because collections need a consistent equals. Two different notions of equality coexist deliberately.
  • 0.0 and -0.0 compare equal with == but are distinguished by Double.compare and by equals. That inconsistency breaks sorted-collection assumptions.
  • BigDecimal.equals compares scale as well as value, so 2.0 does not equal 2.00. Use compareTo() == 0 for numeric equality - this is the single most common BigDecimal bug.
  • Because of that, a HashSet<BigDecimal> can hold both 2.0 and 2.00 as distinct elements, which is almost never what anyone wants.
  • Never construct a BigDecimal from a double. new BigDecimal(0.1) captures the full binary error; the String constructor or valueOf gives the value you wrote.

Example

import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Set;

public class FloatingPointTraps {

    public static void main(String[] args) {
        double nan = 0.0 / 0.0;

        System.out.println("nan == nan            : " + (nan == nan));
        System.out.println("Double.valueOf equals : "
                + Double.valueOf(nan).equals(Double.valueOf(nan)));
        System.out.println("x != x is a NaN test  : " + (nan != nan));

        System.out.println();
        System.out.println("0.0 == -0.0           : " + (0.0 == -0.0));
        System.out.println("Double.compare(0,-0)  : " + Double.compare(0.0, -0.0));
        System.out.println("Double equals(0,-0)   : "
                + Double.valueOf(0.0).equals(Double.valueOf(-0.0)));

        System.out.println();
        System.out.println("0.1 + 0.2             : " + (0.1 + 0.2));
        System.out.println("== 0.3                : " + (0.1 + 0.2 == 0.3));

        // BigDecimal: equals compares SCALE, compareTo compares VALUE.
        BigDecimal a = new BigDecimal("2.0");
        BigDecimal b = new BigDecimal("2.00");
        System.out.println();
        System.out.println("2.0.equals(2.00)      : " + a.equals(b));
        System.out.println("2.0.compareTo(2.00)==0: " + (a.compareTo(b) == 0));

        Set<BigDecimal> set = new HashSet<>();
        set.add(a);
        set.add(b);
        System.out.println("HashSet holds both    : " + set.size() + " -> " + set);

        // NEVER construct from a double.
        System.out.println();
        System.out.println("new BigDecimal(0.1)   : " + new BigDecimal(0.1));
        System.out.println("BigDecimal.valueOf    : " + BigDecimal.valueOf(0.1));
        System.out.println("new BigDecimal(\"0.1\") : " + new BigDecimal("0.1"));
    }
}

NaN is not equal to itself, BigDecimal.equals compares scale not value, and a BigDecimal must never be built from a double.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Core Java course, and every lesson in it is listed on the Core Java contents page.