Contents [Hide] 1 the Problem 2 Primer on Financial Issues 3 introducing BigDecimal 4 rounding and Scalin 5 immutability and Arithm Etic 6 Comparison 7 "to" Round:thoughts on Precision 8 Appendix 8.1 API Reference 8.2 ofbiz Framework 8.3 Minilang |
Ref url:http://www.opentaps.org/docs/index.php/how_to_use_java_bigdecimal:_a_tutorial the Problem
When we started building the general Ledger services for accounting, we discovered that there were errors of 0.01 cent or More in many places. This made accounting practically impossible. Who would want to bill a customer for $4.01 when he order says $4.00?
The reasons for this errors soon became clear:computations that yielded amounts, quantities, adjustments, and many Things were generally do with little or no attention to the special precision and rounding concerns then when the DEA Ling with financial issues.
All of this computations used Java doubles, which offer no way to control how the number are rounded or to limit the Preci Sion in computation. We came up with a solution involving the use of java.math.BigDecimal, which gives us.
This document serves as a primer to financial math issues and as a tutorial on the "use of" BigDecimal R on Financial Issues
Currency calculations require precision to a specific degree, such as two after the decimal for digits most. They also require a specific type of rounding behavior, such as always up to the case of rounding.
For example, suppose we have a product which costs 10.00 into a given currency and the local sales tax are 0.0825, or 8.25%. If We work it out in paper, the tax amount is,
10.00 * 0.0825 = 0.825
Because we precision for the currency are two digits after the decimal, we need to round the 0.825 figure. Also, because this is a tax, it are good practice to always round up to the next highest. That way then the accounts are balanced at the "end of", we never find ourselves underpaying.
0.825-> 0.83
And so, we charge to the customer are 10.83 in the local currency and pay 0.83 to the tax collector. Note So if we sold 1000 of these, we would have overpaid the collector is much,
1000 * (0.83-0.825) = 5.00
Another important issue is where to doing the rounding in a given computation. Suppose we sold liquid nitrogen at 0.528361 per liter. A customer comes in and buys 100.00 liters, so we write out the total price,
100.0 * 0.528361 = 52.8361
Because this isn ' t a tax and we can round this either up or down at our discretion. Suppose we round according to standard rounding rules:if the next significant digit are less than 5, then round down. Otherwise round up. This is gives us a figure of 52.84 for the final price.
Now suppose we want to give a promotional discount of 5% off the entire purchase. Do we apply this discount on the 52.8361 figure or the 52.84 figure? What ' s the difference?
Calculation 1:52.8361 * 0.95 = 50.194295 = 50.19
calculation 2:52.84 * 0.95 = 50.198 = 50.20
We rounded the final figure by using the standard rounding rule.
How do you there ' s a difference of one cent between the two figures? The old code never bothered to consider rounding, so it always did computations as in calculation 1. But in the new code we always round before applying promotions, taxes, and as on, just like in calculation 2. This is one of the main reasons for the one cent error. Introducing BigDecimal
From the examples on the previous section, it should is clear that we need two things: ability to specify a scale, WHI CH represents the number of digits after the decimal place ability to specify a rounding method
The Java.math.BigDecimal class handles both of these considerations. BigDecimal Javadocs
Creating a big decimal from a (scalar) double are simple:
BD = new BigDecimal (1.0);
To get a BigDecimal from a Double, get its doublevalue () a.
However It is a good idea to use the string constructor:
BD = new BigDecimal ("1.5");
If you don ' t, then and you'll get the following,
BD = new BigDecimal (1.5);
Bd.tostring (); => 0.1499999999999999944488848768742172978818416595458984375
Rounding and Scalin
To set the number of digits in the decimal, use the. Setscale (scale) method. However, it is good practice to also specify the rounding mode along with the scale by using. Setscale (Scale, Roundingmode ). The rounding mode specifies how to round the number.
Why do we also want to specify the rounding mode? Let's use the BD's 1.5 from above as a example,
BD = new BigDecimal (1.5); is actually 1.4999 ....
Bd.setscale (1); Throws ArithmeticException
It throws the exception because it does not know how to round 1.49999. So it's a good idea to always use. Setscale (scale, Roundingmode).
There are eight choices for rounding mode,
round_ceiling:ceiling function
0.333 -> 0.34
-0.333 -> -0.33
Round_down:round towards Zero
0.333 -> 0.33
-0.333 -> -0.33
Round_floor:floor function
0.333 -> 0.33
-0.333 -> -0.34
Round_half_up:round up if decimal >=. 5
0.5 -> 1.0
0.4 -> 0.0
Round_half_down:round up if decimal > 5
0.5 -> 0.0
0.6 -> 1.0
Round_half_even:
Round half even is Round as normal. However, when the rounding digit is 5, it would round down if the digit to the left of the 5 are even and up otherwise. This is best illustrated by example,
A = new BigDecimal ("2.5"); Digit left of 5 are even, so round down
b = new BigDecimal ("1.5");//digit left of 5 are odd, so round up
a.sets Cale (0, Bigdecimal.round_half_even). ToString ()//=> 2
b.setscale (0, Bigdecimal.round_half_even). ToString ()/ /=> 2
The Javadoc says about Round_half_even:note that's the rounding mode that minimizes cumulative error when applied R epeatedly over a sequence of calculations.
Round_unnecessary:
Use round_unnecessary at need to use one of the methods that requires input of a rounding mode but you know the ResU Lt won ' t need to be rounded.
When dividing bigdecimals, being careful to specify the rounding in the. Divide (...) method. Otherwise, you could run to arithmeticexception if there is no precisely rounded resulting value, such as 1/3. Thus, your should always do:
A = B.divide (c, decimals, rounding);
immutability and arithmetic
BigDecimal numbers are immutable. What that means are that if you create a new BD with the value "2.00", that object would remain "2.00" and can never be changed.
So does we do math then? The methods. Add (),. Multiply (), and "All" return a new BD value containing the result. For example, at the want to keep a running total of the order amount,
Amount = Amount.add (thisamount);
Make sure you don ' t doing this,
Amount.add (Thisamount);
This is the MOST COMMON mistake MADE with bigdecimals! Comparison
It is important to never with the. Equals () method to compare Bigdecimals. That's because this equals function would compare the scale. If the scale is different,. Equals () would return false, even if they are the same number mathematically.
BigDecimal a = new BigDecimal ("2.00");
BigDecimal B = new BigDecimal ("2.0");
Print (A.equals (b)); False
Instead, we should use the CompareTo () and. Signum () methods.
A.compareto (b); Returns ( -1 if a < b), (0 if a = = B), (1 if a > B)
a.signum ();//Returns ( -1 if a < 0), (0 if a = = 0), (1 If a > 0)
When to round:thoughts on Precision
Now, can I control how to round your calculations, what precision should is they to? The answer depends on, how are you, the resulting number.
You are know what the precision needed for the final result from your user requirements. For numbers which would is added or subtracted to arrive in the final result, you are should add one more decimal of precision , so that 0.0144 + 0.0143 would be rounded to 0.03, whereas if you rounded both to 0.01, and you would get 0.02
If you need numbers which would is multiplied to arrive in the final result, you should preserve as many decimal places as Possible. Ratios and unit costs, for example, should is rounded. After the multiplication, your should round your final result.