Reputation: 7228
We have the following code:
BigDecimal net = price
.divide(taxCumulative, RoundingMode.HALF_UP)
.setScale(2, BigDecimal.ROUND_UP);
We are unit testing this and getting different results depending on if we use @Transactional
on the test classes or not.
I just want to know if we should expect the application of the HALF_UP
to be considered along with the setScale
or before it.
In example:
Say that :
price = 4.00
taxCumulative = 1.20
Would you expect the calculation to be like :
a) 4.00 / 1.20 = 3.33333333... --> HALF_UP --> 3 --> setScale --> 3
or
b) 4.00 / 1.20 = 3.33333333... --> HALF_UP with setScale 2 --> 3.33
Like I said, we have unit tests against this code that behaves differently when we have the @Transactional
or not. So we could not conclude. In real world, the result is b. But a also makes sense.
Any thoughts?
UPDATE:
Following @Mark's suggestions, I ve created a test outside the spring context. (but i guess there is no way to share it online like codepen).
package com.company;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal price = new BigDecimal("4.00");
BigDecimal taxCumulative = new BigDecimal("1.20");
BigDecimal net = price.divide(taxCumulative, RoundingMode.HALF_UP).setScale(2, BigDecimal.ROUND_UP);
System.out.println("With String constructor, the net = " + net.toString());
// prints 3.33
price = new BigDecimal(4.00);
taxCumulative = new BigDecimal(1.20);
net = price.divide(taxCumulative, RoundingMode.HALF_UP).setScale(2, BigDecimal.ROUND_UP);
System.out.println("With doubles, the net = " + net.toString());
// prints 3.00
}
}
So, as pointed out by @gtgaxiola the String constructor makes the difference.
And as for the question for the use of setScale for the divide operation. I have done some more tests :
price = new BigDecimal("4.000000");
taxCumulative = new BigDecimal("1.2000000");
net = price.divide(taxCumulative, RoundingMode.HALF_UP).setScale(2, BigDecimal.ROUND_UP);
// 3.34
price = new BigDecimal("4.000000");
taxCumulative = new BigDecimal("1.2000000");
net = price.divide(taxCumulative, RoundingMode.HALF_UP);
// 3.333333
price = new BigDecimal("4");
taxCumulative = new BigDecimal("1.2");
net = price.divide(taxCumulative, RoundingMode.HALF_UP);
// 3
So the result highly depends on the precision of the string input and the setScale is applied after the divide yields its result.
Upvotes: 3
Views: 2708
Reputation: 9331
It would seem it gets affected depending on how you construct your BigDecimal
Check the Constructor Notes on public BigDecimal(double val)
1.- The results of this constructor can be somewhat unpredictable. One might assume that writing 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 in to the constructor is not exactly equal to 0.1, appearances notwithstanding.
2.-The String constructor, on the other hand, is perfectly predictable: writing new BigDecimal("0.1") creates a BigDecimal which is exactly equal to 0.1, as one would expect. Therefore, it is generally recommended that the String constructor be used in preference to this one.
3.-When a double must be used as a source for a BigDecimal, note that this constructor provides an exact conversion; it does not give the same result as converting the double to a String using the Double.toString(double) method and then using the BigDecimal(String) constructor. To get that result, use the static valueOf(double) method.
Upvotes: 2