Chitika

Monday, January 31, 2005

double vs. BigDecimal

Apart of continuous education and code reviews, there are still some Java codes I've found among my colleagues which are created without giving much thought on the floating-point arithmetic in Java. This is contradictory to the fact that a consistent floating-point arithmetic is essential for any financial applications.

Try and run the following code snippet:


public static void main (String[] args) {
    System.out.println (
        "(0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1) = " +
        (0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1));

    double d = 0.0;
    while (d <= 1.0) d += 0.1;
    System.out.println ("d = " + d);

    System.out.println ("0.0175 * 100000 = " + 0.0175 * 100000);
}

and guess, what's the output?
You might've guessed that 1.0, 1.0, 1750.0 are the outputs, right?
Well, the results are..

(0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1) = 0.9999999999999999
d = 1.0999999999999999
0.0175 * 100000 = 1750.0000000000002

So, what's the conclusion here?
1. Do not use double/float for floating-point arithmetic in Java, use BigDecimal instead. This is because Java cannot represent floating-point precisely.
2. Do not use == or != as a floating-point comparison. Compare Float.floatToIntBits (float) or Double.doubleToLongBits (double) instead. If == or != is used on float/double, there's a possibility that the code will go into infinite loop.
3. Always use BigDecimal for temporary variables, which will be processed/involved in future calculations. Convert the values to float/double only if you want to persist them into the database.

Here's an example of the code to add using BigDecimal:

// default to read a double primitive value of 18 digit
// precision
public static final NumberFormat DEFAULT_DECIMAL_FORMAT =
    new DecimalFormat ("#.0#################");
public static final BigDecimal ZERO = new BigDecimal ("0");

public static BigDecimal add (double a, double b) {
    String s = DEFAULT_DECIMAL_FORMAT.format(a);
    BigDecimal bd = new BigDecimal (s);
    return add (bd, b);
}

public static BigDecimal add (BigDecimal a, double b) {
    String s = DEFAULT_DECIMAL_FORMAT.format(b);
    BigDecimal bd = new BigDecimal (s);
    return add (a, bd);
}

public static BigDecimal add (BigDecimal a, BigDecimal b) {
    if (a == null) return (b == null) ? ZERO : b;
    return a.add (b);
}

Applying the code above, we'll have the following code:

System.out.println (
    "add (add (add (add (add (add (add (add (add (0.1, 0.1), 0.1), 0.1), 0.1), 0.1), 0.1), 0.1), 0.1), 0.1) = " +
    add (add (add (add (add (add (add (add (add (0.1, 0.1), 0.1), 0.1), 0.1), 0.1), 0.1), 0.1), 0.1), 0.1));
System.out.println (
    "new BigDecimal (\"0.0175\").multiply (new BigDecimal (\"100000\").doubleValue()) = " +
    new BigDecimal ("0.0175").multiply (new BigDecimal ("100000")).doubleValue());

And the results are..

add (add (add (add (add (add (add (add (add (0.1, 0.1), 0.1), 0.1), 0.1), 0.1), 0.1), 0.1), 0.1), 0.1) = 1.0
new BigDecimal ("0.0175").multiply (new BigDecimal ("100000").doubleValue()) = 1750.0

A note about BigDecimal is don't use the double constructor, instead use the String constructor. There is no point in trying to do the right thing if you're giving it the bad/wrong seed.. :D

Further Reading:
Effective Java Programming Language Guide

18 comments:

  1. I was asked to convery a program from double to Bigdecimal usage through out. This blog really helped clear up reasons for it. Thanks

    ReplyDelete
  2. A very informative Blog and good explanation provided with example code. It was really helpful to clear my concepts :).

    ReplyDelete
  3. Look at that thread for an issue with BigDecimal when using divide.


    http://forum.java.sun.com/thread.jspa?threadID=682611&messageID=3977174

    ReplyDelete
  4. Amazing blog. Amazing news. What's the difference between comparing two doubles and their bit representation? It would be amazing if these two operations were not exactly the same.

    ReplyDelete
  5. The statement "Java cannot represent floating-point precisely." Is misleading. As if it were somehow Java's fault or a bug.
    Java is doing what every other language does when operating on 32 or 64 bit floats (float and double).
    The problem is that it's impossible to represent "1.0" exactly on a computer. (BigDecimal isn't exact either by the way).

    Each programmer should determine what level of accuracy is necessary before writing code. Sometimes "float" is just fine, other times not.

    That said, I completely agree with the main point which is that you should think before you program!

    ReplyDelete
  6. I disagree with the statement "This is because Java cannot represent floating-point precisely", as if this were a Java problem.
    All programming languages have the same problem that "0.1" cannot be represented exactly by a computer. Float (32 bit precision) is the least precise, while BigDecimal (arbitrary precision) is the most precise.

    As a programmer, you should periodically ask yourself, "How precise do I need to be?", and choose accordingly.

    In the example given, it shows double precision to be accurate up to +/- 0.0000000000000001. As a scientific programmer, this is plenty accurate %99.9 of the time (perhaps my algorithm is only accurate up to 0.001 significant digits any way?)

    The author points out that he's mainly interested in financial applications, perhaps the added precision is necessary there?

    ReplyDelete
  7. Hi,
    Do you have an example for your comparaison "infinite loop" with "=="
    Thanks for your post

    ReplyDelete
  8. for a simple program like to reverse
    a number like 789.123456 to 654321.987 the code i built was
    import java.io.DataInputStream;
    class Reverse
    {
    public static void main(String args[])
    {
    double a=0.0,b=0.0;
    int c=0,d=0,e=0;
    DataInputStream in = new DataInputStream(System.in);
    System.out.println("Enter a number");
    try
    {
    a = Double.valueOf(in.readLine()).doubleValue();
    while(a%1!=0)
    {
    a =a*10;
    c++;
    }
    e=(int)a;
    while(c!=0)
    {
    d=e%10;
    e=e/10;
    System.out.print(d);
    c--;
    }
    System.out.print(".");
    while(e!=0)
    {
    d=e%10;
    e=e/10;
    System.out.print(d);
    }
    }
    catch (Exception z)
    {
    System.out.println("Error");
    }
    }
    }
    but if u take the input as 789.123456 the output is 7463847412000. that is because when u use a modulas operator on double a%1==0, a is not precisely the value that the used entered.. it is precise till 15th or 16th deciamal so the value of a would be like 789.123456000000000000000245645
    that is where it gives a problem.

    ReplyDelete
  9. Nice, but there is a flaw in your add() method. If a and b are both null, it will return zero; if a is null but b is not null, it will return b; however, if a is not null but b is null, it will throw a null pointer exception.

    ReplyDelete
  10. BigDecimal Vs Double

    http://java-j2ee-interview-questions2.blogspot.com/2008/12/bigdecimal-vs-double.html

    ReplyDelete
  11. I appreciate Scotts comments, but it is impractical when you target a third party to use your code. I would definitely go the authors way as I have already implemented this long ago and it met the expectations. Thanks again.

    ReplyDelete
  12. why java is having problem in representing floating numbers, when executed same program in C it gives exact answer (0.1+0.1+0.1...=1.0)? can someone throw light on insight of java's way of representing numbers?
    why it works in C?

    ReplyDelete
  13. I think that the example provided could be improved by getting rid of duplicates (formatting and instantiating) in add() methods ..method like BigDecimal convert(double value) would add value in my view

    thx

    ReplyDelete
  14. Java can represent some floating point numbers precisely, namely those that have an exact binary fraction part, for example, 0.5 or 99.375

    ReplyDelete
  15. pak, emang bisa ya infinite loop waktu compare float/double ?

    ReplyDelete
  16. Be aware that locale may influence the example given. In certain countries such as i.e. Sweden and Norway, the fraction indicator is a , (comma) which will make the BigDecimal constructor throw a NumberFormatException.

    ReplyDelete