Tags

, , , , ,

Today I wanna talk about a little trap Java can present us when using float and double primitive data types in the wrong way. These two data types were designed especially for scientific calculations, where approximation errors are acceptable. They can be a great pain in the neck when developing with accuracy in mind. Let’s understand why.

Float and double in Java are types that implement the IEEE floating point 754 specification. Floating-point numbers in IEEE 754 format consist of three fields: a sign bit, an exponent and a fraction. This means that numbers are represented in a form like:

SIGN FRACTION * 2 ^ EXP.

For example the number 0.15625 can be represented in binary as 0.00101, which in floating-point  format is represented as: 1.01 * 2^-3 (we simply carry out the zeros to the exponent).

But it’s here that the accuracy problem comes. In fact not all fractions can be represented exactly as a fraction of a power of two. As a simple example, 0.1 cannot be stored inside a floating-point variable. Instead of it we would get  the nearest possible value, something like 0.100000001490116119384765625 and the toString() will round it to 0.1 when displaying it.

To have a real taste of what I’m saying try the following code:

public class FloatingPointUtility {

	public static void printTheRealValue(double x) {
        // Translates a double into a BigDecimal
        // which is the exact decimal representation
        // of the double's binary floating-point value.
		System.out.println(new BigDecimal(x));
	}

	public static void main(String args[]) {
		for (double x = 0.0; x <= 0.5; x += 0.1) {
			FloatingPointUtility.printRealValue(x);
		}
	}

}

The code’s output will show that, in this case, four out of six representation are not accurate:

0
0.1000000000000000055511151231257827021181583404541015625
0.200000000000000011102230246251565404236316680908203125
0.3000000000000000444089209850062616169452667236328125
0.40000000000000002220446049250313080847263336181640625
0.5

Now think about a scenario where your software has to deal with money. It can come natural to represent a currency value (like 3,45€ or 2,05$) with a double type. But unfortunately, currency requires accuracy and  the approximation error we get from floating-point operations is not acceptable.

If you are a doubting Thomas 😛 let’s try some code. For example, suppose we have 1€ and we buy nine candies priced at ten cents each. How much change should we get? 10 cents right?

	public static void main(String args[]){
		double inThePocket = 1.0;
		double candyPrice = 0.10;
		inThePocket = inThePocket - (9 * candyPrice);

		assert inThePocket == 0.10 : "Hey! Did someone steal my money? " +
				"\nI have only: "+inThePocket;
		System.out.println("I have: "+inThePocket+" left.");
	}

This code, you can try, will throw an AssertionError:

Exception in thread "main" java.lang.AssertionError:
Hey! Did you steal my money?
I have only: 0.09999999999999998

Hey! What’s going on here? Did someone steal our money? Yes! The floating-point’s approximation error did! 😀

Ok, now that we know about the problem, how do we solve it? A possible solution is to try to keep the control of the error rounding every time our results. Let’s write the following utility method:

	public static double roundToTwoPlaces(double d) {
	    return Math.round(d * 100) / 100.0;
	}

And now let’s change the candies shopping code in:

	public static void main(String args[]){
		double inThePocket = 1.0; // euro
		double candyPrice = 0.10; // euro
		inThePocket = inThePocket - (9 * candyPrice);

		// I control the error rounding the result
		inThePocket = roundToTwoPlaces(inThePocket);

		assert inThePocket == 0.10 : "Hey! Did someone steal my money? " +
				"\nI have only: "+inThePocket;
		System.out.println("I have: "+inThePocket+" left.");
	}

This time we get the right result:

I have: 0.1 left.

Ok, this is a solution, but in my opinion is more a patch to a design mistake: we are using double data type in a way that the authors of the language didn’t want to.

Another solution is to use the java.math.BigDecimal class, which was designed to give us accuracy. But we have to think that using BigDecimal we get two main disadvantages:

  1. It’s less convenient than using a primitive arithmetic type. For instance, writing: x * y / z is clearer than: x.multiply(y).divide(z, 10, BigDecimal.ROUND_HALF_UP)
  2. It’s slower.

There is a third solution, the one I prefer, and it is to use int or long, depending on the amounts involved, keeping track of decimal point. In other words: do all computation in cents instead of euro. The following is a transformation of the code we saw before:

	public static void main(String args[]){
		int inThePocket = 100; // cents of euro
		int candyPrice = 10; // cents of euro
		inThePocket = inThePocket - (9 * candyPrice);

		assert inThePocket == 10 : "Hey! Did someone steal my money? " +
				"\nI have only: "+inThePocket;
		System.out.println("I have: "+inThePocket+" cents left.");
	}

If you try this program you’ll see that now is working well without have to think about rounding or performance problem. Next time you’re designing software which has to deal with accuracy, please think about the floating-point’s trap 😉

Advertisements