The purpose of this note is twofold. First, there is an awareness of the compelling need to use BigDecimal instead of double management applications.
Secondly, we will give some recommendations for effective use of BigDecimal.
Why use BigDecimal and not double
Handling amount of programming has often encountered some problems of comparison, two numbers are supposed to be equal as tested different example is different from 7273.27x10 72732.7.
The storage mechanism traditional binary numbers: a sum of power of 2, is responsible for this problem.
Numbers as simple as 0.1 can not be stored precisely in that form. Therefore 0.1 is never exactly equal to 0.1. So 0.1 x10 is not exactly equal to 1. Out 1 = 2 ^ 0, so it has an accurate representation. If we made a comparison with 0.1 x10 1 gives a result in this case wrong.
NB It seems as if the 0.1 is better treated now since Java, if using double, it has 0.1 x10 = 1.0.
In business applications, numbers are often the amounts or percentages: the number of digits after the comma is usually known (between 2 and 4, see 6 or 8 for some financial applications).
In case of division, there are rules of rounded and it is not desirable to change the storage to gain "precision". These applications do not know the actual: 1 / 3 will be equal to 0.33.
The BigDecimal in Java
In Java, there is a type BigDecimal which can take into account this type of problem: it relies on the fact that an integer has an exact binary representation.
For an amount, simply set the number of digits after the decimal point and store the number as a whole: 7273.27 example will be stored as a value of 727,327 with 2 digits after the decimal point. The number of digits after the decimal point corresponds to the scale property of the BigDecimal.
Using BigDecimal causes a small constraint: it is not a native type but an object. It is therefore not possible to use the operators +, -, * and /.
example
7273.27 * 10.0 which is written with double: double
doubleApproche = 7273.27;
double result = 10 * doubleApproche;
becomes BigDecimal:
BigDecimal = new BigDecimal doubleApproche ("7273.27");
doubleApproche.multiply BigDecimal result = (new BigDecimal (10));
Project Example
We'll create a simple Eclipse project to illustrate the different cases.
menu: File -> New Project
Then select the node in Java: Java Project.
The project name will TestBigDecimal.
Press the Finish button.
On node src, right click and select New -> New JUnit Test Case
the radio buttonThings New JUnit 4 test.
Package: fr.j2ltho.test.bigdecimal
Name: TestDifferences
Then press the Finish button.
A dialog box appears: "JUnit 4 is Not On The build path. Do you want to add it?".
Choose the radio button: Perform Action THE FOLLOWING:
And check that there is: Add To The JUnit 4 library build path.
Validate by pressing the OK button.
It adds the following code: package
fr.j2ltho.test.bigdecimal;
import static org.junit.Assert.assertEquals;
import java.math.BigDecimal;
import org.junit.Test ;
TestDifferences public class Test {@
(timeout = 1000) public void
testProblemeDouble () {double
doubleApproche = 7273.27;
double resultat=10*doubleApproche;
assertEquals("Double fois 10", 72732.7, resultat);
if (resultat==72732.7) {
System.out.println("Egale: " + resultat);
}
else {
System.out.println("Ecart: " + (resultat-72732.7));
}
}
@Test(timeout=1000)
public void testProblemeBigDecimal() {
BigDecimal doubleApproche=new BigDecimal("7273.27");
BigDecimal resultat=doubleApproche.multiply(new BigDecimal(10));
assertEquals("BigDecimal fois 10", new BigDecimal("72732.7"), resultat);
if (resultat.compareTo(new BigDecimal("72732.7")) ==0){
System.out.println("Egale: " + resultat);
}
else {
System.out.println("Ecart: " + (resultat.subtract(new BigDecimal("72732.7"))));
}
}
@Test(timeout=1000)
public void testDixiemeDouble() {
double doubleApproche=0.1;
double resultat=10*doubleApproche;
assertEquals("Double fois 10", 1.0, resultat);
if (result == 1.0) {
System.out.println ("Egal:" + result);
} else {
System.out.println ("Écart:" + (result-1.0));
}
}
@Test(timeout=1000)
public void testCentiemeDouble() {
double doubleApproche=0.01;
double resultat=10*doubleApproche;
assertEquals("Double fois 10", 0.10, resultat);
if (resultat==0.10) {
System.out.println("Egale: " + resultat);
}
else {
System.out.println("Ecart: " + (resultat-0.10));
}
}
@Test(timeout=1000)
public void testDixiemmeCentiemeDouble() {
double doubleApproche=0.11;
double resultat=10*doubleApproche;
assertEquals("Double fois 10", 1.10, resultat);
if (result == 1.10) {
System.out.println ("Egal:" + result);
} else {
System.out.println ("Écart:" + (result-1.10));
}}}
The execution of Test: Run As -> JUnit Test provides a successful test at 100% but with the following result in the Console:
Gap: 1.4551915228366852E-11
Equals: 72732.70
Equals: 1.0
Equals: 0.1 Equal
: 1.1
This shows that the comparison JUnit assertEquals: assertEquals ("Double time 10", 72732.7, result); returns a just result when comparing the double return an error and the difference in returns to differ.
If handling of Java double, beware of assertEquals who seem to take into account a rounding difference.
Creating a BigDecimal
The BigDecimal is an object, it is created with a new. Its constructor accepts a String, a long or a double.
should never use the constructor twice because it reintroduces the problem of the round.
The following test illustrates the problem:
@ Test (timeout = 1000)
testProblemeConstructeurDouble public void () {BigDecimal
doubleApproche = new BigDecimal (7273.27);
BigDecimal = new BigDecimal stringApproche ( "7273.27");
assertEquals ("BigDecimal x 10" doubleApproche, stringApproche)
if (doubleApproche. compareTo (stringApproche) == 0) {
System.out.println("Egale: " + doubleApproche);
}
else {
System.out.println("Ecart: " + (doubleApproche.subtract(stringApproche)));
}
}
The console displays:
Gap: 4.3655745685100555419921875E-13
Again, we see that for assertEquals BigDecimal is unreliable because it indicates an equality that is not real.
For integers, it is preferable to use the static method valueOf () that is more readable:
BigDecimal.valueOf (40)
There are three methods to create a static BigDecimal value 1 or 0 or 10.
- BigDecimal.ONE
- BigDecimal.ZERO
- BigDecimal.TEN
In short, it is recommended that:
- For a fee, rate or a percentage, the constructor with a String parameter: BigDecimal = new BigDecimal stringApproche ("7273.27");
- For units, the static method valueOf: BigDecimal.valueOf ( 40) For
- remarkable values 0, 1 and 10: use static methods: BigDecimal.ZERO, and BigDecimal.ONE BigDecimal.TEN
Addition and subtraction with BigDecimal.
Addition and subtraction do not feature in Apart from the requirement to use a method:
- add subtract
It will for example
BigDecimal value = new BigDecimal ("70.36")
/ / resAddition = value + 1 = valeur.add resAddition
BigDecimal (BigDecimal.ONE)
/ / = value -10 resSoustraction
resSoustraction BigDecimal = valeur.subtract (BigDecimal.TEN)
Multiplication and Division BigDecimal
Multiplication on the same principle that addition and subtraction using the method: multiply.
BigDecimal = new BigDecimal pricePerShare ("70.36");
/ / coutTotal pricePerShare = * 3 = pricePerShare.multiply coutTotal
BigDecimal (BigDecimal.valueOf (3));
For the division is more complicated because it must indicate:
- the number by which we divide
- but especially the number of digits after the decimal point that we wish to retain the result (a division does not always just fall)
- and finally the mechanism rounding: we usually use BigDecimal.ROUND_HALF_UP
For example, the number of shares purchased:
BigDecimal = new BigDecimal pricePerShare ("70.36");
purchaseAmount = BigDecimal.valueOf BigDecimal (200);
/ / = nbPart purchaseAmount / pricePerShare
BigDecimal nbPart = purchaseAmount . divide (pricePerShare, 4, BigDecimal.ROUND_HALF_UP)
There are two methods to simplify multiplication and division by multiples of 10:
- movePointLeft (2) we move the decimal 2 to the left of which corresponds to a division 100.
- movePointRight (1) is moved from one point to the right which corresponds to a multiplication by 10.
These two methods are particularly useful when calculating rates or percentages.
BigDecimal total = BigDecimal.valueOf (950);
BigDecimal BigDecimal.valueOf Percentage = (20);
/ / We want 20% of 950
/ / amount = (total * percentage) / 100 = total amount
BigDecimal . multiply (percentage). movePointLeft (2);
comparison method should be promoted: compareTo that ignores the number of digits after the decimal point. compareTo considers that 15.2 and 15 200 are identical. This is not the case with equals ().
The method compareTo returns an integer and not boolean:
- 0 if the value is identical
- 1 if the value is greater than the parameter
- -1 if the value is less than the parameter
In through the use of JUnit we recommend:
assertEquals ("Comparison", 0, doubleApproche.compareTo (stringApproche));
instead of:
assertEquals ("Comparing imprecise "doubleApproche, stringApproche)
Indeed in some cases (small gap): the first show is a difference, while the second will show a tie.
Display
To view a BigDecimal, there are two methods:
- plainString
- toString
We recommend using a third solution: the use of formatting with DecimalFormat. this mechanism to force the representation of BigDecimal following a defined format.
The following example is significant: we have deliberately used the double manufacturer to show the difference.
BigDecimal = new BigDecimal doubleApproche (7273.27);
System.out.println ("7273.27 toPlainString:" + doubleApproche.toPlainString ());
System . out.println ("7273.27 toString:" + doubleApproche.toString ());
DecimalFormat DecimalFormat = new DecimalFormat ("##,###,###,## 0.00 ");
System.out. println ("7273.27 DecimalFormat:" + decimalFormat.format (doubleApproche));
Conclusion
Using BigDecimal is a little heavier than double. But there are a set of methods to streamline work and especially the assurance of not having a problem rounding at the end.
This forces us to disambiguate the choice of rounding early: This avoids disappointment with the business teams during the phases of recipe. The business teams are fully aware of these rules, they they are so natural that they forget to specify them. Using BigDecimal forces us to determine from the beginning of the program what to do.