Double vs. BigDecimal?
Asked Answered
A

8

422

I have to calculate some floating point variables and my colleague suggest me to use BigDecimal instead of double since it will be more precise. But I want to know what it is and how to make most out of BigDecimal?

Adjudication answered 5/8, 2010 at 9:39 Comment(1)
Check out this one; #323249Snoopy
B
596

A BigDecimal is an exact way of representing numbers. A Double has a certain precision. Working with doubles of various magnitudes (say d1=1000.0 and d2=0.001) could result in the 0.001 being dropped altogether when summing as the difference in magnitude is so large. With BigDecimal this would not happen.

The disadvantage of BigDecimal is that it's slower, and it's a bit more difficult to program algorithms that way (due to + - * and / not being overloaded).

If you are dealing with money, or precision is a must, use BigDecimal. Otherwise Doubles tend to be good enough.

I do recommend reading the javadoc of BigDecimal as they do explain things better than I do here :)

Bertina answered 5/8, 2010 at 9:45 Comment(7)
Yep, I'm calculating the price for stock so I believe BigDecimal is useful in this case.Adjudication
@Truong Ha: When working with prices you want to use BigDecimal. And if you store them in the database you want something similar.Bertina
Saying that "BigDecimal is an exact way of representing numbers" is misleading. 1/3 and 1/7 can't be expressed exactly in a base 10 number system (BigDecimal) or in base 2 number system (float or double). 1/3 could be exactly expressed in base 3, base 6, base 9, base 12, etc. and 1/7 could be expressed exactly in base 7, base 14, base 21, etc. BigDecimal advantages are that it is arbitrary precision and that humans are used to the rounding errors you get in base 10.Romance
Good point about it being slower, helps me understand why the Netflix Ribbon load balancer code deals with doubles, and then has lines like this: if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {Codee
@Bertina I think you mean to say "if accuracy is a must, use BigDecimal", a Double would have more "precision" (more digits).Tipple
BigDecimal exactly represents only numbers which can be written as finite decimal numbers, i.e. x*10^y where x and y are integers.Octogenarian
I used to work at Exegy (high speed trading hardware platform as a solution). Everything was integer based. Precise representation of money. For example, USD would be in terms of cents, as integers.Renz
D
253

My English is not good so I'll just write a simple example here.

double a = 0.02;
double b = 0.03;
double c = b - a;
System.out.println(c);

BigDecimal _a = new BigDecimal("0.02");
BigDecimal _b = new BigDecimal("0.03");
BigDecimal _c = _b.subtract(_a);
System.out.println(_c);

Program output:

0.009999999999999998
0.01

Does anyone still want to use double? ;)

Deaden answered 13/5, 2016 at 19:10 Comment(6)
@eldjon Thats not true, Look at this example: BigDecimal two = new BigDecimal("2"); BigDecimal eight = new BigDecimal("8"); System.out.println(two.divide(eight)); This prints out 0.25.Woodsman
Nonetheless if you use a float instead you get the same precision than BigDecimal in that case but way better performanceHumane
@Humane Float may work with 0.03-0.02, but other values are still imprecise: System.out.println(0.003f - 0.002f); BigDecimal is exact: System.out.println(new BigDecimal("0.003").subtract(new BigDecimal("0.002")));Rumph
But this is because you are not printing the float point properly. The idea is use double to do calculations. Once you get the result, transform it to BigDecimal. Set up your precision and rounding settings and print it. Alternatively you can use a FormatterMosstrooper
For example 0.00999999999998 rounded gives you exactly 0.01Mosstrooper
can someone explain why does this happen?Renick
M
81

There are two main differences from double:

  • Arbitrary precision, similarly to BigInteger they can contain number of arbitrary precision and size (whereas a double has a fixed number of bits)
  • Base 10 instead of Base 2, a BigDecimal is n*10^-scale where n is an arbitrary large signed integer and scale can be thought of as the number of digits to move the decimal point left or right

It is still not true to say that BigDecimal can represent any number. But two reasons you should use BigDecimal for monetary calculations are:

  • It can represent all numbers that can be represented in decimal notion and that includes virtually all numbers in the monetary world (you never transfer 1/3 $ to someone).
  • The precision can be controlled to avoid accumulated errors. With a double, as the magnitude of the value increases, its precision decreases and this can introduce significant error into the result.
Maccaboy answered 14/4, 2015 at 8:18 Comment(3)
This answer truly explains the difference and the reason of using BigDecimal over double. Performance concerns are secondary.Woolgathering
@Maccaboy - can you elaborate - "arbitrary precision" ?Avi
docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.htmlMaccaboy
F
46

If you write down a fractional value like 1 / 7 as decimal value you get

1/7 = 0.142857142857142857142857142857142857142857...

with an infinite repetition of the digits 142857. Since you can only write a finite number of digits you will inevitably introduce a rounding (or truncation) error.

Numbers like 1/10 or 1/100 expressed as binary numbers with a fractional part also have an infinite number of digits after the decimal point:

1/10 = binary 0.0001100110011001100110011001100110...

Doubles store values as binary and therefore might introduce an error solely by converting a decimal number to a binary number, without even doing any arithmetic.

Decimal numbers, on the other hand, often store each decimal digit as is (binary coded, but each decimal on its own) or, in the case of BigDecimal, as two signed binary integer numbers in the form x*10y (thanks to @AlexSalauyou for pointing out). See: Class BigDecimal. This means that a decimal type is not more precise than a binary floating point or fixed point type in a general sense (i.e. it cannot store 1/7 without loss of precision), but it is more accurate for numbers that have a finite number of decimal digits as is often the case for money calculations.

Java's BigDecimal has the additional advantage that it can have an arbitrary (but finite) number of digits on both sides of the decimal point, limited only by the available memory.

Faust answered 28/5, 2016 at 15:28 Comment(1)
Decimal numbers (like BigDecimal), store each decimal digit as is (binary coded, but each decimal on its own)---not true, BigDecimal stores number in form x*10^y, both x and y are stored as signed binary integers.Octogenarian
S
26

If you are dealing with calculation, there are laws on how you should calculate and what precision you should use. If you fail that you will be doing something illegal. The only real reason is that the bit representation of decimal cases are not precise. As Basil simply put, an example is the best explanation. Just to complement his example, here's what happens:

static void theDoubleProblem1() {
    double d1 = 0.3;
    double d2 = 0.2;
    System.out.println("Double:\t 0,3 - 0,2 = " + (d1 - d2));

    float f1 = 0.3f;
    float f2 = 0.2f;
    System.out.println("Float:\t 0,3 - 0,2 = " + (f1 - f2));

    BigDecimal bd1 = new BigDecimal("0.3");
    BigDecimal bd2 = new BigDecimal("0.2");
    System.out.println("BigDec:\t 0,3 - 0,2 = " + (bd1.subtract(bd2)));
}

Output:

Double:  0,3 - 0,2 = 0.09999999999999998
Float:   0,3 - 0,2 = 0.10000001
BigDec:  0,3 - 0,2 = 0.1

Also we have that:

static void theDoubleProblem2() {
    double d1 = 10;
    double d2 = 3;
    System.out.println("Double:\t 10 / 3 = " + (d1 / d2));

    float f1 = 10f;
    float f2 = 3f;
    System.out.println("Float:\t 10 / 3 = " + (f1 / f2));

    // Exception! 
    BigDecimal bd3 = new BigDecimal("10");
    BigDecimal bd4 = new BigDecimal("3");
    System.out.println("BigDec:\t 10 / 3 = " + (bd3.divide(bd4)));
}

Gives us the output:

Double:  10 / 3 = 3.3333333333333335
Float:   10 / 3 = 3.3333333
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion

But:

static void theDoubleProblem2() {
    BigDecimal bd3 = new BigDecimal("10");
    BigDecimal bd4 = new BigDecimal("3");
    System.out.println("BigDec:\t 10 / 3 = " + (bd3.divide(bd4, 4, BigDecimal.ROUND_HALF_UP)));
}

Has the output:

BigDec:  10 / 3 = 3.3333 
Septate answered 20/9, 2018 at 8:36 Comment(2)
damn, could you imagine the cops busting down your door at 2am... "Sir, is this your code? Are you aware that you used the wrong precision to divide these two numbers?! Up against the wall, NOW"Uniparous
@Tarek7 This is indeed a legal problem for any calculation in banks, markets, telecommunications,... anything that is money related. If you've watched Superman, you understand that a simple change in precision can make you a millionaire! :)Septate
W
11

BigDecimal is Oracle's arbitrary-precision numerical library. BigDecimal is part of the Java language and is useful for a variety of applications ranging from the financial to the scientific (that's where sort of am).

There's nothing wrong with using doubles for certain calculations. Suppose, however, you wanted to calculate Math.Pi * Math.Pi / 6, that is, the value of the Riemann Zeta Function for a real argument of two (a project I'm currently working on). Floating-point division presents you with a painful problem of rounding error.

BigDecimal, on the other hand, includes many options for calculating expressions to arbitrary precision. The add, multiply, and divide methods as described in the Oracle documentation below "take the place" of +, *, and / in BigDecimal Java World:

http://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html

The compareTo method is especially useful in while and for loops.

Be careful, however, in your use of constructors for BigDecimal. The string constructor is very useful in many cases. For instance, the code

BigDecimal onethird = new BigDecimal("0.33333333333");

utilizes a string representation of 1/3 to represent that infinitely-repeating number to a specified degree of accuracy. The round-off error is most likely somewhere so deep inside the JVM that the round-off errors won't disturb most of your practical calculations. I have, from personal experience, seen round-off creep up, however. The setScale method is important in these regards, as can be seen from the Oracle documentation.

Wilkens answered 18/7, 2015 at 0:58 Comment(3)
BigDecimal is part of Java's arbitrary-precision numerical library. 'In-house' is rather meaningless in this context, especially as it was written by IBM.Guaiacol
@EJP: I looked into BigDecimal class and learned that only portion of it is written by IBM. Copyright comment below: /* * Portions Copyright IBM Corporation, 2001. All Rights Reserved. */Ecru
The round-off error is most likely somewhere so deep inside the JVM---the error is hold not "deep in JVM", it is hold in assumption that 1/3 = 0.3333. When you make new BigDecimal("0.3333") the decimal 0.3333 is stored exactly.Octogenarian
D
0

If you need to use division in your arithmetic, you need to use double instead of BigDecimal. Division (divide(BigDecimal) method) in BigDecimal is pretty useless as BigDecimal can't handle repeating decimal rational numbers (division where divisors are and will throw java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

Just try BigDecimal.ONE.divide(new BigDecimal("3"));

Double, on the other hand, will handle division fine (with the understood precision which is roughly 15 significant digits)

Drus answered 7/7, 2022 at 21:2 Comment(1)
What about divide(BigDecimal divisor, int scale, int roundingMode)? This lets us specify exact precision of the division result which we will be happy with.Storz
O
0

Your colleague just wants to make it simple.

Almost every value x written as decimal fractional number, cannot be exactly stored in binary-based double—thus, instead of x, there will be stored some x + delta. This is not a problem by itself, as in most cases transformation back to decimal handles it correctly (e.g.: Double.toString(0.1) -> "0.1"). But when you perform arithmetic calculations, deltas of arguments are accumulated in result, and you have to take care of it.

It is often said that is impossible to accumulate enough inaccuracy to affect result, as double has precision 1e-16 (i.e.: |delta| < 1e-16). This is true for x around 1. For x around 1M precision is 1e-10; for 100B (quite typical amount in Indonesian Rupiah), precision falls to 1e-5. Now you have a limitation of 100K simple arithmetic operations to guaranteerly stay in the safe zone.

Even if you are absolutely sure that accumulated inaccuracy won't spread into significant part of result, you have to implement some rules to suppress it: rounding, converting from cents, etc—every time when result should be treated as decimal, i.e. compared, printed, saved in database, sent to downstream, etc. And your colleague has to review all that.

With BigDecimal, where decimal fractions are stored accurately, given that all monetary amounts in modern world are counted in decimals, you and your colleague do not have this headache at all.

Octogenarian answered 6/9, 2023 at 20:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.