Tuesday, January 06, 2009

More Double Troubles

We saw in a previous entry how one has to be careful with Double.NaN. Today we will see how regular double can cause problems. By the way the NaN issue was not Java specific and this issue is also general in different programming languages.

A coworker was shocked that in Java (I was a bit surprised he saw that only today, but it is true it can be surprising that such a simple thing does not work as expected):
408.16 - 40.82 = 367.34000000000003
In C, this would lead to the same result. This is all due to the binary represention of double numbers. Using the formula 2^(exponent)*1.mantissa where mantissa is on 52 bits, we have

408.16 decomposition:
  • exponent = 256. Then 408.16/256 = 1.594375 = 1 + 0x9828F5C28F5C28F5C28F5C... * 2^-52
  • We round to 52 bits, the mantissa is 0x9828F5C28F5C3 = 2676827028518339.
  • As a decimal, the internal value is (2676827028518339/2^52+1) * 256 = 408.1600000000000250111042987555265426635742
40.82 decomposition:
  • exponent = 32. Then 40.82/32= 1.275625 = 1 + 0x468F5C28F5C28F5C...*2^-52
  • Rounded to 52 bits, the mantissa is 0x468F5C28F5C29 = 1241304647293993
  • As a decimal, the internal value is (1241304647293993/2^52 + 1)*32 = 40.8200000000000002842170943040400743484497
The difference in decimal becomes 367.34000000000002472... which becomes 367.34000000000003 when represented in binary (to convince yourself you can apply the same technique).

The Solution

One solution to this problem is to use java.math.BigDecimal which stores a number as 2 integers, one for the digits, one for the exponent power of 10 (and not 2).
The correct code would become:

BigDecimal value = BigDecimal.valueOf(408.16).subtract(BigDecimal.valueOf(40.82));


value would then be 367.34.

But BigDecimal has also many potential for bugs. For example, you should never use the constructor taking a double but always the one taking a String.

new BigDecimal(408.16) = 408.16000000000002501110429875552654266357421875

This is because of the binary representation of 408.16 as a double. 408.16 is only an approximation of 408.16!

Another trick with BigDecimal is not to use equals(Object) but compareTo(Object) because 408.160 is not equal to 408.16 using equals.

Why Could not They Make it Work With Double?

If you were too lazy to follow the steps of the explanation. There is a simpler explanation. Imagine the representation of a number in base 3 with 2 "digits". Let's imagine 1/3 is represented as 0.1 (this is a very simple number representation) 1/3+1/3+1/3 becomes 0.1+0.1+0.1 = 1.0 (in base 3) = 1.0 if we convert to base 10. Now in base 10, 1/3 can only be represented as 0.3, so 1/3+1/3+1/3 = 0.3+0.3+0.3 = 0.9 <> 1.0.
So BigDecimal is only interesting to handle ... decimals! In the enterprise world, this should be most apps. It is a bit sad it appeared so late in the JDK. It should really be a primitive type.

More Double Troubles

We saw in a previous entry how one has to be careful with Double.NaN. Today we will see how regular double can cause problems. By the way the NaN issue was not Java specific and this issue is also general in different programming languages.

A coworker was shocked that in Java (I was a bit surprised he saw that only today, but it is true it can be surprising that such a simple thing does not work as expected):
408.16 - 40.82 = 367.34000000000003
In C, this would lead to the same result. This is all due to the binary represention of double numbers. Using the formula 2^(exponent)*1.mantissa where mantissa is on 52 bits, we have

408.16 decomposition:
  • exponent = 256. Then 408.16/256 = 1.594375 = 1 + 0x9828F5C28F5C28F5C28F5C... * 2^-52
  • We round to 52 bits, the mantissa is 0x9828F5C28F5C3 = 2676827028518339.
  • As a decimal, the internal value is (2676827028518339/2^52+1) * 256 = 408.1600000000000250111042987555265426635742
40.82 decomposition:
  • exponent = 32. Then 40.82/32= 1.275625 = 1 + 0x468F5C28F5C28F5C...*2^-52
  • Rounded to 52 bits, the mantissa is 0x468F5C28F5C29 = 1241304647293993
  • As a decimal, the internal value is (1241304647293993/2^52 + 1)*32 = 40.8200000000000002842170943040400743484497
The difference in decimal becomes 367.34000000000002472... which becomes 367.34000000000003 when represented in binary (to convince yourself you can apply the same technique).

The Solution

One solution to this problem is to use java.math.BigDecimal which stores a number as 2 integers, one for the digits, one for the exponent power of 10 (and not 2).
The correct code would become:

BigDecimal value = BigDecimal.valueOf(408.16).subtract(BigDecimal.valueOf(40.82));


value would then be 367.34.

But BigDecimal has also many potential for bugs. For example, you should never use the constructor taking a double but always the one taking a String.

new BigDecimal(408.16) = 408.16000000000002501110429875552654266357421875

This is because of the binary representation of 408.16 as a double. 408.16 is only an approximation of 408.16!

Another trick with BigDecimal is not to use equals(Object) but compareTo(Object) because 408.160 is not equal to 408.16 using equals.

Why Could not They Make it Work With Double?

If you were too lazy to follow the steps of the explanation. There is a simpler explanation. Imagine the representation of a number in base 3 with 2 "digits". Let's imagine 1/3 is represented as 0.1 (this is a very simple number representation) 1/3+1/3+1/3 becomes 0.1+0.1+0.1 = 1.0 (in base 3) = 1.0 if we convert to base 10. Now in base 10, 1/3 can only be represented as 0.3, so 1/3+1/3+1/3 = 0.3+0.3+0.3 = 0.9 <> 1.0.
So BigDecimal is only interesting to handle ... decimals! In the enterprise world, this should be most apps. It is a bit sad it appeared so late in the JDK. It should really be a primitive type.

Wednesday, December 17, 2008

Java Programmers Are Idiots?

My brother just sent me a funny quote. I don't know if it is true or not:
entwickeln Sie lieber überzeugende Lösungen anstatt viele Stunden mit Coding zu verbringen? Ist Ihnen die Produktivität Ihres Teams wichtig?

Mark Driver, *VP Research von Gartner*, kommentierte kürzlich:

"Here’s a simple equation. In terms of mental fortitude...*
1 Smalltalk developer = 2.5 C++ developers
1 C++ developer = 1.5 Java developers*"
You don't need german to understand. Of course it can not be true. How can anyone measure mental fortitude? And how does it related with productivity is another issue.

There is a famous email exchange from Linux Torvald claiming the advantages of C versus C++, here is a quote:
If you want a VCS that is written in C++, go play with Monotone. Really.They use a "real database". They use "nice object-oriented libraries". They use "nice C++ abstractions". And quite frankly, as a result of all these design decisions that sound so appealing to some CS people, the end result is a horrible and unmaintainable mess.


This however is quite interesting since I am probably not the only one to have seen the disastrous effects of too enthusiastic abstraction professionals in Java. This is why many Java projects are utter crap. But again, not everybody makes this mistake. Some people know how to build good things. As the result of Java being popular, we have many crap programmers with pseudo genius theories you don't find in C or Haskell.

On another subject, I seriously wonder why we don't have more distributed compilation in Java as in C/C++. I am tired of seing those core doing nothing while compiling.