How to use BigDecimal in Java? Example

Hello Java Programmers, if you are wondering how to use BigDecmial in Java then you have come to the right place. In this article, I will show you how to use BigDecimal for example in Java. BigDecimal is used to represent bigger numbers in Java, similar to float and long but many Java programmers also use BigDecimal to store floating-point values because it's easier to perform arithmetic operations on BigDecimal than float or double. That's the reason, many Java programmer uses BigDecmial for monetary or financial calculation that floats or double. 

Enough of talking about BigDecimal in Java, now let's see simple examples of how to create, initialize and use BigDecimal in Java. In this example, we have used BigDecimal inside a for loop and also demonstrate how to perform arithmetic and logical operation with BigDEcimalar like addition, subtraction, comparison, etc.

BigDecimal TEN_DOLLARS =  new BigDecimal("10.0");
BigDecimal TEN_CENTS = new BigDecimal("0.1"); /
/ AlwaysUse String constructor and not double one

BigDecimal ZERO_CENTS = new BigDecimal("0.0");
           
for(BigDecimal balance= TEN_DOLLARS;
    balance.compareTo(ZERO_CENTS) >= 0 ;
    balance = balance.subtract(TEN_CENTS)){

    System.out.println(balance);
}
Output:
10.0
9.9
9.8
0.2
0.1
0.0


More BigDecimal Examples in Java

That was a really simple example of using BigDecimal inside a for loop and decreasing value until it becomes zero. Now let's see few more BigDecimal examples about addition, subtraction, and multiplication of BigDecimal in Java. 

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
/**
 * Don't use == operator with float and double values in Java
 *
 * @author Javin Paul
 */
public class Testing {

    public static void main(String args[]) {

        // Creating instance of BigDecimal in Java
        BigDecimal twentyOne = new BigDecimal("21.01");  
        // String constructor behaves as they seen

        BigDecimal twentyTwo = new BigDecimal("22.01");
        BigDecimal _22 = new BigDecimal(22.01); 
        // double constructor, doesn't behave as they look

        System.out.println(twentyOne + ", " + twentyTwo + ", " + _22);


        // How to get the scale of BigDecimal number
        int scale = twentyOne.scale();
        System.out.printf("scale of BigDecimal %s is %d %n", twentyOne, scale);
        int _scale = _22.scale();
        System.out.printf("scale of BigDecimal %s is %d %n", _22, _scale);


        //How to set the scale of BigDecimal number
        _22 = _22.setScale(4, RoundingMode.HALF_DOWN); 
        // may return a different BigDecimal object
        System.out.printf("new scale of BigDecimal %s is %d %n", _22, _22.scale());


        // Adding two BigDecimal numbers in Java
        BigDecimal sum = twentyOne.add(twentyOne); // adding BigDecimal of same scale
        BigDecimal addition = twentyOne.add(_22);  // adding BigDecimal of different scale
        System.out.printf("sum of %s and %s is %s %n", twentyOne, twentyTwo, sum);
        System.out.printf("addition of %s and %s is %s %n", twentyOne, _22, addition);


        // Subtracting two BigDecimal in Java
        BigDecimal minus = twentyOne.subtract(twentyOne); 
        // subtracting BigDecimal of same scale
        BigDecimal subtraction = twentyOne.subtract(_22);  
        // subtracting BigDecimal of different scale
        System.out.printf("minus of %s and %s is %s %n", twentyOne, twentyTwo, minus);
        System.out.printf("subtraction of %s and %s is %s %n", twentyOne, _22,
                                    subtraction);


        // Multiplying BigDecimals in Java
        BigDecimal product = twentyOne.multiply(twentyOne); 
        // multiplying BigDecimal of same scale

        BigDecimal multiplication = twentyOne.multiply(_22);  
       // multiplying BigDecimal of different scale
        System.out.printf("product of %s and %s is %s %n", twentyOne, 
                                twentyTwo, product);
        System.out.printf("multiplication of %s and %s is %s %n",
                          twentyOne, _22, multiplication);


        // Divide two BigDecimal in Java
        BigDecimal two = new BigDecimal("2.0");
        BigDecimal divide = twentyOne.divide(two); // dividing BigDecimal of same scale
        BigDecimal division = _22.divide(two);  // dividing BigDecimal of different scale
        System.out.printf("divide of %s and %s is %s %n", twentyOne, two, divide);
        System.out.printf("division of %s and %s is %s %n", _22, two, division);
    }
}
Output
21.01, 22.01, 22.010000000000001563194018672220408916473388671875
scale of BigDecimal 21.01 is 2
scale of BigDecimal 22.010000000000001563194018672220408916473388671875 is 48
new scale of BigDecimal 22.0100 is 4
sum of 21.01 and 22.01 is 42.02
addition of 21.01 and 22.0100 is 43.0200
minus of 21.01 and 22.01 is 0.00
subtraction of 21.01 and 22.0100 is -1.0000
product of 21.01 and 22.01 is 441.4201
multiplication of 21.01 and 22.0100 is 462.430100
divide of 21.01 and 2.0 is 10.505
division of 22.0100 and 2.0 is 11.005


Now that you have seen examples of using BigDecimal in Java, let's revisit some important points about BigDecimal which every Java developer should know. 

Things to note while using BigDecimal 

Here are some important details about BigDecimal class and values in Java. Knowing these details is mandatory for effective using BigDecimal in a real-world Java application. 

1. Prefer String Constructor
You should always use the String constructor of BigDecimal i.e. new BigDecimal("20.0"), rather than a double constructor like new BigDecimal(20.0), though they look very similar, there is a big difference between them. The results of this constructor can be unpredictable. 

You may assume that creating new BigDecimal(0.1) in Java creates a BigDecimal which is exactly equal to 0.1 (an unscaled value of 1, with a scale of 1), but it is actually equal to 0.1000000000000000055511151231257827021181583404541015625. 

This is because 0.1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the value that is being passed into the constructor is not exactly equal to 0.1, appearances notwithstanding.

2. Same Scale
Always operate with BigDecimal of the same scale, otherwise, the result would be unpredictable. For example in our case, all BigDecimal number has only one number after the decimal point. You would notice that I have not used BigDecimal.TEN or BigDecimal.ZERO constants from BigDecimal class itself because they have different scales and using them will result in unpredictable results similar to using double values.

3. Immutable
BigDecimal is an Immutable class in Java. It also caches frequently used values like boxed primitive classes e.g. Integer and Long.

4. Internal Representation
BigDecimal internally uses an int and int array to reprint numbers. When you create another BigDecimal number, which just differs in sign e.g. by using negate() method then these two BigDecimal share the same int array.

5. BigDecimal class also has a companion mutable class to reduce temporary objects, which is created during a multi-step calculation process. Read Effective Java for more details.

6. float and double data types are mainly provided for scientific and engineering calculations. Java uses binary floating-point calculations which are good for approximation but don't provide exact results. The bottom line is, don't use float/double when an exact calculation is needed. You cannot represent values like 0.1 or 0.01 or any negative power of 10 accurately in Java. calculating interest, expenses is one example of this.

7. compareTo() and equals() method of java.lang.BigDecimal class is inconsistent with each other. This means comparing "3.0" and "3.00" with equals() and compareTo() will give a different result, the former will return false, while later will return true. Why? because equals() method of BigDecimal only return true if both numbers are the same in value and scale, while the compareTo() method only compares values. 

This inconsistent behavior of BigDecimal can leads to subtle issues when used with Collection classes, which make use of both equals() and compareTo() method e.g. if you put both "3.0" and "3.00" in a Set, it will allow both of them, which is wrong, given Set doesn't allow duplicates, as shown in below example :

BigDecimal THREE = new BigDecimal("3.0");
BigDecimal three = new BigDecimal("3.00");
           
Set biggies = new HashSet();
biggies.add(THREE);
biggies.add(three);
           
System.out.println("Size: " + biggies.size());
System.out.println(biggies);
           
System.out.println("Does biggies contains 3.0 : " 
+ biggies.contains(new BigDecimal("3.0")));
System.out.println("Does biggies contains 3.000 : " 
+ biggies.contains(new BigDecimal("3.000")));
Output:
Size: 2
[3.00, 3.0]
Does biggies contains 3.0 : true
Does biggies contains 3.000 : false

You can see that HashSet allowed both "3.0" and "3.00", which may look like violation of its contract of not allowing duplicate, but it does because both of these BigDecimal number is not equal by equals() method. Similarly contains() method false for "3.000" because it also uses equals() method for checking if an object exists inside Set or not.

Now let's change the Set implementation from HashSet to TreeSet, which uses compareTo() method for comparison, here is how our code and result look like :

BigDecimal THREE = new BigDecimal("3.0");
BigDecimal three = new BigDecimal("3.00");
           
Set treeSet = new TreeSet();
treeSet.add(THREE);
treeSet.add(three);
           
System.out.println("Size: " + treeSet.size());
System.out.println(treeSet);
           
System.out.println("Does TreeSet contains 3.0 : " 
+ treeSet.contains(new BigDecimal("3.0")));
System.out.println("Does TreeSet contains 3.000 : " 
+ treeSet.contains(new BigDecimal("3.000")));
Size: 1
[3.0]
Does TreeSet contains 3.0 : true
Does TreeSet contains 3.000 : true

Now it seems like TreeSet is following Set contract and not allowing duplicate, while HashSet is not, but the truth is they both are using different methods for comparison, TreeSet is using compareTo() while HashSet is using equals() and because they are inconsistent to each other, we are getting different behavior by different Set implementations.

How to use BigDecimal in Java? Example



Lesson learned :


1) Pay attention to both scale and value while comparing two BigDecimal or checking for equality. Choose equals() if you want to consider both value and scale and choose compareTo() if two BigDecimal numbers can be equal by just values.

2) While overriding compareTo() and equals() method for a class in Java, make sure they are consistent with each other.

3) Be careful while storing BigDecimal in Collection classes e.g. Set and Map especially while using them as key in SortedMap and storing them as inside SortedSet because BigDecimal's natural ordering is inconsistent with equals.

That’s all about BigDecimal in Java. It's one of the important class, which is not well understood by many Java developers. I suggest programmers of all level to develop a good understanding of java.lang.BigDecimal to avoid surprises and bugs, when you really need to use it. Almost half of the bugs, which involves BigDecimal is because of poor understanding of this class and its behaviour. Programmer face issues when using float/double and blindly turns to BigDecimal, without knowing about scale, value and which BigDecimal constructor to use for which purpose.

8. Scale of a BigDecimal is a number of digit after decimal point, for example scale of BigDecimal 10.1 is 1, 10.01 is 2 and 10.001 is 3. You can get scale of a BigDecimal by calling scale() method and you can also change scale by calling setScale() method. Remember, since BigDecimal is Immutable, setScale may return a new BigDecimal object, of course only if new scale is different than old scale. By the way you can also specify RountingMode while changing scale, BigDecimal provides overloaded version of setScale() method. Prefer to new setScale(int, RoundingMode) over legacy setScale(int, int), which also provide same functionality. Remeber, setScale(int) method may throw Exception in thread "main" java.lang.ArithmeticException: Rounding necessary, if it found that Rounding is needed.

9. BigDecimal is a sub-class of Number, which means you can pass a BigDecimal object to any method, which is accepting Number object. Similarly a method can return a BigDecimal object if its return type is Number. This makes BigDecimal a close cousin of float/double and other numeric primitive types.

10.  If zero or positive, the scale is the number of digits to the right of the decimal point. If negative, the unscaled value of the number is multiplied by ten to the power of the negation of the scale. The value of the number represented by the BigDecimal is therefore (unscaled Value × 10-scale). add few more points from javadoc

2. Immutable, arbitrary-precision signed decimal numbers. A BigDecimal consists of an arbitrary precision integer unscaled value and a 32-bit integer scale

1) Using BigDecimal has the added advantage that it gives you full control over rounding, letting you select from eight rounding modes whenever an operation that entails rounding is performed. This comes in handy if you’re performing business calculations with legally mandated rounding behaviour. If performance is of the essence, you don’t mind keeping track of the decimal point yourself, and the quantities aren’t too big, use int or long. If the quantities don’t exceed nine decimal digits, you can use int; if they don’t exceed eighteen digits, you can use long. If the quantities might exceed eighteen digits, you must use BigDecimal.

2) It was not widely understood that immutable classes had to be effectively final when BigInteger and BigDecimal were written, so all of their methods may be overridden. Unfortunately, this could not be corrected after the fact while preserving backward compatibility. If you write a class whose security depends on the immutability of a BigInteger or BigDecimal argument from an untrusted client, you must check to see that the argument is a “real” BigInteger or BigDecimal, rather than an instance of an untrusted subclass. If it is the latter, you must defensively copy it under the assumption that it might be mutable (Item 39):

public static BigInteger safeInstance(BigInteger val) {
  if (val.getClass() != BigInteger.class)
     return new BigInteger(val.toByteArray());
   return val;
}


That's all about how to use BigDecimal in Java. We have not only seen how to use BigDecimal but also learned important details and best practices you can follow while using BigDecimal in Java like using String constructor and using the same scale while performing operations on BigDecimal.

No comments:

Post a Comment

Feel free to comment, ask questions if you have any doubt.