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
?
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 :)
if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
–
Codee 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 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? ;)
System.out.println(0.003f - 0.002f);
BigDecimal is exact: System.out.println(new BigDecimal("0.003").subtract(new BigDecimal("0.002")));
–
Rumph 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.
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.
BigDecimal
stores number in form x*10^y
, both x
and y
are stored as signed binary integers. –
Octogenarian 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
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.
/* * Portions Copyright IBM Corporation, 2001. All Rights Reserved. */
–
Ecru new BigDecimal("0.3333")
the decimal 0.3333 is stored exactly. –
Octogenarian 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)
divide(BigDecimal divisor, int scale, int roundingMode)
? This lets us specify exact precision of the division result which we will be happy with. –
Storz 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.
© 2022 - 2024 — McMap. All rights reserved.