Java question regarding doubles :-o

I found something "interesting" (or rather annoying). Shock
If I run the following code:
double a1 = 1.0D, a2 = 0.64D, a3 = a1 + a2, a4 = 1.64D;
System.out.println("a1 = " + a1);
System.out.println("a2 = " + a2);
System.out.println("a3 = " + a3);
System.out.println("a4 = " + a4);
I get the following output:
a1 = 1.0
a2 = 0.64
a3 = 1.6400000000000001
a4 = 1.64

What the hell is happening here? Shock Where comes that 0.0000000000000001 value from in the sum of the two doubles? It occured with Sun JDK 1.5.0 build7.

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Rounding Error

http://www.google.com/search?q=floating+point+rounding+error

I don't get it

I read through some of the links that your Google search gave, but found nothing that would explain the bug I've found. Shock I just tested the same in PHP and it had no problem with adding 1.0 to 0.64, the result was 1.64 as one would expect.

You cannot argue that this is a floating point rounding problem. These two numbers fit every floating point representation that I know of, and the sum of them fits a floating point too. I do not see how can this be a rounding error. Shock

one more thought

I just tested this:
double a = 1.0D, b = 0.64D, c = a + b;
System.out.println("a = " + a + ", b = " + b + ", a + b = " + c);
a = 0.1D; b = 0.064D; c = a + b;
System.out.println("a = " + a + ", b = " + b + ", a + b = " + c);
a = 10D; b = 6.4D; c = a + b;
System.out.println("a = " + a + ", b = " + b + ", a + b = " + c);
a = 100D; b = 64D; c = a + b;
System.out.println("a = " + a + ", b = " + b + ", a + b = " + c);
And I got the following output:
a = 1.0, b = 0.64, a + b = 1.6400000000000001
a = 0.1, b = 0.064, a + b = 0.164
a = 10.0, b = 6.4, a + b = 16.4
a = 100.0, b = 64.0, a + b = 164.0

If it's a rounding error, then why does it not occur eg. in the (0.1 + 0.064) case? Shock

it's a representation issue

I asked for help on the mailing list of my former university class and their answers showed me the right direction. Smile

The most used standard for floating point numbers is IEEE 754. It's quite well described in a Wikipedia article. I used this and the description of the Double.toHexString() function. The reason for the problem I described lies in both the representation of floating point numbers and in the inner workings of the Double.toString() function.

I've got the following output for the numbers of my example ...

The number: 1.0
Double.toHexString(): 0x1.0p0
Recalculated: 1.0

The number: 0.64
Double.toHexString(): 0x1.47ae147ae147bp-1
Recalculated: 0.64000000000000004440892098500627

The number: 1 + 0.64 (the result of the addition stored in a Double variable)
Double.toHexString(): 0x1.a3d70a3d70a3ep0
Recalculated: 1.64000000000000026645352591003763

The number: 1.64
Double.toHexString(): 0x1.a3d70a3d70a3dp0
Recalculated: 1.64000000000000004440892098500627

The "number" field shows the original decimal representation that I started from.
The "Double.toHexString()" field shows the result of Double.toHexString() function for the given Double value.
The "recalculated" field shows the decimal form calculated from the hexadecimal representation by toHexString() (I used the Calculator of Windows XP for this).

You can see that neither 0.64, nor 1.64 can be represented correctly using the IEEE 754 floating point formats and this is the source of the problem. Sad
The result of the addition and the literal 1.64 are stored differently and hence comes the difference in the output of the toHexString().

Knowing this it is not surprising that the following condition fails (gives a "false" result): 1.0D + 0.64D == 1.64D
If you choose numbers that can be represented correctly (and the their sum as well), then you get a "true" result of course: 1.0D + 1.0D == 2.0D

And as for the original problem ... the answer lies in the description of the Double.toString() function: "How many digits must be printed for the fractional part of m or a? There must be at least one digit to represent the fractional part, and beyond that as many, but only as many, more digits as are needed to uniquely distinguish the argument value from adjacent values of type double."

That's all folks. Have a nice time playing with floating point. Smile

PS: actually all this was already known to me, but I've spent so much time with Oracle PL/SQL (where the NUMBER format does not produce this issue ... or at least not so apparently) that I've forgotten about the beauties of floating point arithmetic. Smile