While trying to calculate a ratio of the volume of 2 objects, I noticed some weirdness in the calculation, here is a sample you can run for yourself:
public class TestApplication {
public static void main(String[] args) {
BigDecimal first = BigDecimal.valueOf(21099000.0);
BigDecimal second = BigDecimal.valueOf(13196000.0);
System.out.println("First: " + first);
System.out.println("Second: " + second);
System.out.println("Division: " + first.divide(second, RoundingMode.HALF_UP).doubleValue());
}
}
And the result is:
First: 2.1099E+7
Second: 1.3196E+7
Division: 0.0
There are 3 ways I could make it give me the expected result
1. If I change the decimal part from 0 to 1 (or any non-0 number):
First: 21099000.1
Second: 13196000.1
Division: 1.6
2. If I divide the numbers beforehand (make them 7 digit numbers instead of 8):
First: 2109900.0
Second: 1319600.0
Division: 1.6
3. If I specify a scale doing division (first.divide(second, 0, RoundingMode.HALF_UP
):
First: 2.1099E+7
Second: 1.3196E+7
Division: 2.0
I thought that BigDecimal is backed by an integer and the numbers I used are way below 2 billion. Can anyone explain what makes these 3 cases different from the original result?
As per the documentation, divide(BigDecimal divisor, RoundingMode roundingMode)
returns a BigDecimal
whose value is (this / divisor), and whose scale is this.scale()
.
Check the result of the following code:
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal first = BigDecimal.valueOf(21099000.1);
BigDecimal second = BigDecimal.valueOf(13196000.1);
System.out.println("First: " + first + ", Scale: " + first.scale());
System.out.println("Second: " + second + ", Scale: " + second.scale());
// 21099000.0 / 13196000.0 = 1.5988936041
System.out.println(BigDecimal.valueOf(1.5988936041).setScale(first.scale(), RoundingMode.HALF_UP));
}
}
Output:
First: 21099000.1, Scale: 1
Second: 13196000.1, Scale: 1
1.6
As you can see, JVM has chosen the scale as 1
for first
and thus the result of divide
(which is 1.5988936041
) is also set as 1
which is equal to 1.6
with RoundingMode.HALF_UP
.
Check the result of the following code:
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal first = BigDecimal.valueOf(21099000.0);
BigDecimal second = BigDecimal.valueOf(13196000.0);
System.out.println("First: " + first + ", Scale: " + first.scale());
System.out.println("Second: " + second + ", Scale: " + second.scale());
// 21099000.0 / 13196000.0 = 1.5988936041
System.out.println(BigDecimal.valueOf(1.5988936041).setScale(first.scale(), RoundingMode.HALF_UP));
}
}
Output:
First: 2.1099E+7, Scale: -3
Second: 1.3196E+7, Scale: -3
0E+3
As you can see, JVM has chosen the scale as -3
for first
and thus the result of divide
(which is 1.5988936041
) is also set as -3
which is equal to 0
(or 0E+3
) with RoundingMode.HALF_UP
.
As mentioned in the documentation, scale of the division is set as this.scale()
which means if you set the scale of first
to 1
, you can get the expected result.
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal first = BigDecimal.valueOf(21099000.0).setScale(1);
BigDecimal second = BigDecimal.valueOf(13196000.0);
System.out.println("First: " + first + ", Scale: " + first.scale());
System.out.println("Second: " + second + ", Scale: " + second.scale());
System.out.println("Division: " + first.divide(second, RoundingMode.HALF_UP).doubleValue());
}
}
Output:
First: 21099000.0, Scale: 1
Second: 1.3196E+7, Scale: -3
Division: 1.6
The last example worked well and there is no problem using it. However, the most common way is to use divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
.
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal first = BigDecimal.valueOf(21099000.0);
BigDecimal second = BigDecimal.valueOf(13196000.0);
System.out.println("First: " + first + ", Scale: " + first.scale());
System.out.println("Second: " + second + ", Scale: " + second.scale());
System.out.println("Division: " + first.divide(second, 1, RoundingMode.HALF_UP).doubleValue());
}
}
Output:
First: 2.1099E+7, Scale: -3
Second: 1.3196E+7, Scale: -3
Division: 1.6