Chandra Sekhar
Chandra Sekhar

Reputation: 19500

Double precision confusion?

While answering this question, I got these confusing results:

double d = 0.49999999999999990d; //output 0.4999999999999999 as expected
d = 0.49999999999999991d; //output 0.4999999999999999
d = 0.49999999999999992d; //output 0.49999999999999994
d = 0.49999999999999993d; //output 0.49999999999999994
d = 0.49999999999999994d; //output 0.49999999999999994 as expected
d = 0.49999999999999995d; //output 0.49999999999999994
d = 0.49999999999999996d; //output 0.49999999999999994
d = 0.49999999999999997d; //output 0.49999999999999994
d = 0.49999999999999998d; //output 0.5

Why is this behaviour showing?

NOTE: I got these outputs just by printing d; I mean I used:

System.out.println(d);

Upvotes: 1

Views: 459

Answers (3)

Stephen C
Stephen C

Reputation: 719386

Floating point types cannot exactly represent all Real numbers. In fact, a double is a 64-bit floating point type, and can therefore only represent 264 different values ... and there are an infinite number of Real numbers. (Indeed, there are an infinite number of Real numbers between 0.49999999999999990d and 0.49999999999999999d.)

You have picked some numbers that fall between consecutive values in the set-of-all double values. In other words, you've exceeded the limits of precision for the double type.

What can you do about it? Well one way to get more precision is to use the BigDecimal class, which can (in theory) give you in the region of 2 BILLION decimal digits of precision. The downside is that your code will be more complicated ... and significantly slower, depending on how much precision you use.

The other approach is to recognize that you probably don't need that much precision.

Upvotes: 2

aioobe
aioobe

Reputation: 421180

System.out.println(d) will go through Double.toString which is a fairly complex method (as seen in its documentation) an will not always behave as you'd expect. It basically gives the shortest string which uniquely determines d.

Perhaps the output of this program clarifies this:

double[] tests = {
        0.49999999999999990d, //output 0.4999999999999999 as expected
        0.49999999999999991d, //output 0.4999999999999999
        0.49999999999999992d, //output 0.49999999999999994
        0.49999999999999993d, //output 0.49999999999999994
        0.49999999999999994d, //output 0.49999999999999994 as expected
        0.49999999999999995d, //output 0.49999999999999994
        0.49999999999999996d, //output 0.49999999999999994
        0.49999999999999997d, //output 0.49999999999999994
        0.49999999999999998d, //output 0.5
    };

String[] literals = {
        "0.49999999999999990d",
        "0.49999999999999991d",
        "0.49999999999999992d",
        "0.49999999999999993d",
        "0.49999999999999994d",
        "0.49999999999999995d",
        "0.49999999999999996d",
        "0.49999999999999997d",
        "0.49999999999999998d",
    };

String f = "%-25s%-65s%-25s%n";
System.out.printf(f, "Literal", "Actually represents", "Printed as");

for (int i = 0; i < tests.length; i++)
    System.out.printf(f, literals[i],
                         new BigDecimal(tests[i]).toString(), 
                         Double.valueOf(tests[i]));

Output:

Literal                  Actually represents                                              Printed as               
0.49999999999999990d     0.49999999999999988897769753748434595763683319091796875          0.4999999999999999       
0.49999999999999991d     0.49999999999999988897769753748434595763683319091796875          0.4999999999999999       
0.49999999999999992d     0.499999999999999944488848768742172978818416595458984375         0.49999999999999994      
0.49999999999999993d     0.499999999999999944488848768742172978818416595458984375         0.49999999999999994      
0.49999999999999994d     0.499999999999999944488848768742172978818416595458984375         0.49999999999999994      
0.49999999999999995d     0.499999999999999944488848768742172978818416595458984375         0.49999999999999994      
0.49999999999999996d     0.499999999999999944488848768742172978818416595458984375         0.49999999999999994      
0.49999999999999997d     0.499999999999999944488848768742172978818416595458984375         0.49999999999999994      
0.49999999999999998d     0.5                                                              0.5                      

As can be seen, the literal is sometimes far from the value it actually represents, which means that Double.toString prints something that may look surprising.

Upvotes: 1

NPE
NPE

Reputation: 500853

Only certain numbers can be represented exactly as doubles. There are three such numbers in the range under consideration:

  • 0.49999999999999990
  • 0.49999999999999994
  • 0.5

Everything between these numbers gets rounded to the nearest of the three.

If you look at how these doubles are represented in hex, you'll see that the three numbers have consecutive mantissas (the part before the p):

In [20]: float.hex(0.49999999999999990)
Out[20]: '0x1.ffffffffffffep-2'

In [21]: float.hex(0.49999999999999994)
Out[21]: '0x1.fffffffffffffp-2'

In [22]: float.hex(0.5)
Out[22]: '0x1.0000000000000p-1'

Representing numbers such as 0.49999999999999992 exactly would require more bits of mantissa than double can offer.

Upvotes: 0

Related Questions