BigDecimal Overview
Java provides the API class BigDecimal in the java.math package to perform exact arithmetic on numbers with more than 16 valid bits. The double-precision floating-point variable double can handle 16-bit significant digits, but in real-world applications it may be necessary to perform arithmetic and processing on larger or smaller numbers.
In general, for numbers that don’t need exact calculation precision, they can be handled directly with Float and Double, but Double.valueOf(String) and Float.valueOf(String) will lose precision. So if you need the result of the exact calculation, you must use the BigDecimal class to operate.
BigDecimal object provides the traditional +, -, *, / and other arithmetic operators corresponding to the method, through these methods to carry out the corresponding operations. bigDecimal are immutable (immutable), in each four operations, will produce a new object , so in the addition, subtraction, multiplication and division operations should remember to save the value after the operation.
4 Pitfalls of BigDecimal
When using BigDecimal, there are 4 kinds of pitfalls in the use of scenarios, you must know about it, if not used properly, it must be miserable. Master these cases, when others write code with pits, you will also be able to recognize at a glance, the big bull is so practiced.
First: the pit of floating-point types
Before learning to understand the pitfalls of BigDecimal, let’s talk about a cliché: if you use Float, Double, and other floating-point types for calculations, it’s possible that you’ll get an approximation rather than an exact value.
For example, the following code:
@Test
public void test0(){
float a = 1;
float b = 0.9f;
System.out.println(a - b);
}
What is the result? 0.1? No, the result of executing the above code is 0.100000024. The reason for this result is that the binary representation of 0.1 is infinite loop. Because the computer’s resources are limited, so there is no way to use binary to accurately represent 0.1, can only use the “approximation” to represent, that is, in the case of limited precision, maximize the binary number close to 0.1, which will result in a lack of precision.
About the above phenomenon we all know, not to expand in detail. At the same time, it will also conclude that in scientific notation can be considered to use the floating point type, but if it is involved in the calculation of the amount of money to use BigDecimal to calculate.
So, does BigDecimal necessarily avoid the floating point problem mentioned above? Take a look at the following example:
@Test
public void test1(){
BigDecimal a = new BigDecimal(0.01);
BigDecimal b = BigDecimal.valueOf(0.01);
System.out.println("a = " + a);
System.out.println("b = " + b);
}
What are the a and b results of the code in the unit test above?
a = 0.01000000000000000020816681711721685132943093776702880859375
b = 0.01
The above example shows that even when using BigDecimal, the result will still have precision issues. It comes down to the question of whether to create a BigDecimal object with an initial value in the form of a new BigDecimal or through the BigDecimal#valueOf method.
The reason for the above phenomenon is that when new BigDecimal, the 0.1 passed in is already a floating-point type, and given that the value stated above is only an approximation, the approximation is kept intact when using new BigDecimal.
This is not the case with BigDecimal#valueOf, which has the following source code implementation:
public static BigDecimal valueOf(double val) {
// Reminder: a zero double returns '0.0', so we cannot fastpath
// to use the constant ZERO. This might be important enough to
// justify a factory approach, a cache, or a few private
// constants, later.
return new BigDecimal(Double.toString(val));
}
Inside valueOf, the value of the floating-point type is converted to a string using the Double#toString method, so there is no loss of precision.
At this point a basic conclusion is reached: first, when using the BigDecimal constructor, try to pass strings instead of floating-point types; second, if the first one cannot be satisfied, the BigDecimal#valueOf method can be used to construct the initialization value.
To extend the point here, the common constructor methods for BigDecimal are as follows:
BigDecimal(int)
BigDecimal(double)
BigDecimal(long)
BigDecimal(String)
This involves constructor methods whose parameter type is double, which can cause the problems described above, and requires special attention when using them.
Second: the pit of floating-point precision
If you compare two BigDecimal values to see if they are equal, how would you compare them? Would you use the equals method or the compareTo method?
Let’s look at an example first:
@Test
public void test2(){
BigDecimal a = new BigDecimal("0.01");
BigDecimal b = new BigDecimal("0.010");
System.out.println(a.equals(b));
System.out.println(a.compareTo(b));
}
At first glance they may feel equal, but in reality they are not essentially the same.
The equals method is based on the BigDecimal implementation of the equals method to compare, the intuitive impression is to compare the two objects are the same, so how does the code achieve this?
@Override
public boolean equals(Object x) {
if (!(x instanceof BigDecimal))
return false;
BigDecimal xDec = (BigDecimal) x;
if (x == this)
return true;
if (scale != xDec.scale)
return false;
long s = this.intCompact;
long xs = xDec.intCompact;
if (s != INFLATED) {
if (xs == INFLATED)
xs = compactValFor(xDec.intVal);
return xs == s;
} else if (xs != INFLATED)
return xs == compactValFor(this.intVal);
return this.inflated().equals(xDec.inflated());
}
A careful reading of the code shows that the equals method compares not only whether the values are equal, but also whether the precision is the same. In the above example, since the precision of the two are different, the result of the equals method is of course false. The compareTo method implements the Comparable interface, which really compares the size of the values and returns -1 (less than), 0 (equal to), 1 (greater than).
Basic conclusion: In general, if you compare the size of two BigDecimal values, use its implementation of the compareTo method; if you strictly limit the precision of the comparison, then consider using the equals method.
In addition, this scenario is more common when comparing 0 values, such as comparing BigDecimal(“0”), BigDecimal(“0.0”), BigDecimal(“0.00”), when you must use the compareTo method to compare.
Third: Setting up the precision pit
In the project to see a lot of students through the BigDecimal calculations do not set the precision of the results and rounding mode, it is really anxious people, although in most cases there will be no problem. But the following scenario is not necessarily:
@Test
public void test3(){
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("3.0");
a.divide(b);
}
What is the result of executing the above code?ArithmeticException exception!
java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
at java.math.BigDecimal.divide(BigDecimal.java:1690)
...
The occurrence of this exception is also described in the official documentation:
If the quotient has a nonterminating decimal expansion and the operation is specified to return an exact result, an ArithmeticException is thrown. Otherwise, the exact result of the division is returned, as done for other operations.
To summarize, the ArithmeticException
exception is thrown if, during a divide operation, the quotient is an infinite decimal (0.333…) and the result of the operation is expected to be an exact number.
In this case, simply specify the precision of the result when using the divide method:
@Test
public void test3(){
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("3.0");
BigDecimal c = a.divide(b, 2,RoundingMode.HALF_UP);
System.out.println(c);
}
Execute the above code and enter a result of 0.33.
Basic conclusion: when using BigDecimal for (all) operations, be sure to explicitly specify the precision and rounding mode.
To expand on this, rounding modes are defined in the RoundingMode enumeration class, and there are eight of them:
RoundingMode.UP: Rounding mode for rounding away from zero. Always increment the number before discarding the non-zero portion (always add 1 to the number preceding the non-zero rounding portion). Note that this rounding mode never decreases the size of the computed value.
RoundingMode.DOWN: Near-zero rounding mode. Never adds a number before discarding a part (never adds 1 to the number before the discarded part, i.e. truncates it). Note that this rounding mode never increases the size of the computed value.
RoundingMode.CEILING: rounding mode close to positive infinity. If BigDecimal is positive, the rounding behavior is the same as ROUNDUP; if negative, the rounding behavior is the same as ROUNDDOWN. Note that this rounding mode never reduces the computed value.
RoundingMode.FLOOR: rounding mode close to negative infinity. If BigDecimal is positive, the rounding behavior is the same as ROUNDDOWN; if negative, the rounding behavior is the same as ROUNDUP. Note that this rounding mode never increases the computed value.
RoundingMode.HALF_UP: round to the “nearest” digit, if the distance between two neighboring digits is equal, then the rounding mode is rounding up. If the rounding part >= 0.5, the rounding behavior is the same as ROUND_UP; otherwise, the rounding behavior is the same as ROUND_DOWN. Note that this is the rounding pattern we learned in elementary school (rounding up).
RoundingMode.HALF_DOWN: round to the “nearest” digit, if the distance between two neighboring digits is equal, then the rounding mode is up. If the rounding part > 0.5, the rounding behavior is the same as ROUND_UP; otherwise, the rounding behavior is the same as ROUND_DOWN (rounding up).
RoundingMode.HALF_EVEN: round to the “nearest” number, if the distance to two neighboring numbers is equal, then round to the adjacent even number. If the number to the left of the rounding part is odd, the rounding behavior is the same as ROUNDHALFUP; if it is even, the rounding behavior is the same as ROUNDHALF_DOWN. Note that this rounding mode minimizes the summing error when repeating a series of calculations. This rounding mode is also known as “banker’s rounding” and is mainly used in the United States. Rounding to five is done in two ways. If the first digit is an odd number, it is rounded up, otherwise it is rounded down. The following example retains 1 decimal place, so the result of this rounding method. 1.15 ==> 1.2 ,1.25 ==> 1.2
RoundingMode.UNNECESSARY: Asserts that the requested operation has an exact result and therefore does not require rounding. An ArithmeticException is thrown if this rounding mode is specified for an operation that obtains an exact result.
Usually we use rounding i.e. RoundingMode.HALF_UP.
Fourth: Three string output pits
When after using BigDecimal, you need to convert to String type, how do you do it? Directly toString?
Let’s start with the code below:
@Test
public void test4(){
BigDecimal a = BigDecimal.valueOf(35634535255456719.22345634534124578902);
System.out.println(a.toString());
}
Does the execution result in the corresponding value above? Not really:
3.563453525545672E+16
That is, what was intended to be a string printed out as a scientific notation value.
Here we need to understand the three methods of BigDecimal to convert strings
toPlainString(): does not use any scientific notation;
toString(): use scientific notation when necessary;
toEngineeringString() : Use engineering counting when necessary. Similar to scientific notation, except that the powers of the exponent are multiples of 3, which makes it easier for engineering applications, since it is 10^3 in many unit conversions;
Examples of the three methods of displaying results are shown below:
Basic conclusion: according to the data results show different formats, using different string output methods, usually use more methods for toPlainString().
In addition, the format() method of the NumberFormat class can use a BigDecimal object as its parameter, which can be utilized to control the formatting of currency values, percent values, and general numeric values beyond the 16-bit valid number.