ADDITIONAL SYSTEM INFORMATION :
Windows 10
Adopt Openjdk 11.0.10+9
A DESCRIPTION OF THE PROBLEM :
When dividing a value with a MathContext that has a precision >= 20, some divisors are significantly slower.
The divisors that cause this behavior can triggered by any divisor that has a prime decomposition consisting of 2s and 5s (i.e. 4, 10, 25, 40, 1000). If the dividend is different than 1 the numbers that trigger that behavior may also include a subset_primedecomposition(dividend) * 2^m * 5^n. For example, if the divident is 27 some additional numbers that triggers the the bad performance are: 3, 6, 96.
I have written a workaround which checks if the number is only divible by 2 and 5, if it is calculate 1/divisor without Math Context (i.e. 4 -> 1/4 = 0.25) and then multiply the result with my number under consideration of the Math Context. See sample code.
```java
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
public class BigDecimalTest {
@Test
public void test_big_decimal_performance() {
final var bigDecimal = BigDecimal.valueOf(1); // Value matters to some degree, but not so much
final var mc = new MathContext(38, RoundingMode.HALF_UP);
for(int i = 2; i <= 100; i++) {
final var divisor = BigDecimal.valueOf(i);
final var t0 = System.currentTimeMillis();
for(int j = 0; j < 1_000_000; j++) {
bigDecimal.divide(divisor, mc);
}
final var t1 = System.currentTimeMillis();
System.out.println(String.format("%3d: %10dms", i, (t1 -t0)));
}
System.out.println("-------------------------------------------------------------------------");
for(int i = 2; i <= 100; i++) {
final var divisor = BigDecimal.valueOf(i);
System.gc();
final var t0 = System.currentTimeMillis();
for(int j = 0; j < 1_000_000; j++) {
divideSpecial(bigDecimal, i, mc);
}
final var t1 = System.currentTimeMillis();
System.out.println(String.format("%3d: %10dms", i, (t1 -t0)));
}
}
private BigDecimal divideSpecial(BigDecimal dividend, int divisor, MathContext mc) {
if (isDivisibleOnlyBy2And5(divisor)) {
final var factor = BigDecimal.ONE.divide(BigDecimal.valueOf(divisor));
return dividend.multiply(factor, mc);
} else {
return dividend.divide(BigDecimal.valueOf(divisor), mc);
}
}
private boolean isDivisibleOnlyBy2And5(int i) {
while((i % 5) == 0) {
i /= 5;
}
while((i % 2) == 0) {
i /= 2;
}
return i == 1;
}
}
```
Windows 10
Adopt Openjdk 11.0.10+9
A DESCRIPTION OF THE PROBLEM :
When dividing a value with a MathContext that has a precision >= 20, some divisors are significantly slower.
The divisors that cause this behavior can triggered by any divisor that has a prime decomposition consisting of 2s and 5s (i.e. 4, 10, 25, 40, 1000). If the dividend is different than 1 the numbers that trigger that behavior may also include a subset_primedecomposition(dividend) * 2^m * 5^n. For example, if the divident is 27 some additional numbers that triggers the the bad performance are: 3, 6, 96.
I have written a workaround which checks if the number is only divible by 2 and 5, if it is calculate 1/divisor without Math Context (i.e. 4 -> 1/4 = 0.25) and then multiply the result with my number under consideration of the Math Context. See sample code.
```java
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
public class BigDecimalTest {
@Test
public void test_big_decimal_performance() {
final var bigDecimal = BigDecimal.valueOf(1); // Value matters to some degree, but not so much
final var mc = new MathContext(38, RoundingMode.HALF_UP);
for(int i = 2; i <= 100; i++) {
final var divisor = BigDecimal.valueOf(i);
final var t0 = System.currentTimeMillis();
for(int j = 0; j < 1_000_000; j++) {
bigDecimal.divide(divisor, mc);
}
final var t1 = System.currentTimeMillis();
System.out.println(String.format("%3d: %10dms", i, (t1 -t0)));
}
System.out.println("-------------------------------------------------------------------------");
for(int i = 2; i <= 100; i++) {
final var divisor = BigDecimal.valueOf(i);
System.gc();
final var t0 = System.currentTimeMillis();
for(int j = 0; j < 1_000_000; j++) {
divideSpecial(bigDecimal, i, mc);
}
final var t1 = System.currentTimeMillis();
System.out.println(String.format("%3d: %10dms", i, (t1 -t0)));
}
}
private BigDecimal divideSpecial(BigDecimal dividend, int divisor, MathContext mc) {
if (isDivisibleOnlyBy2And5(divisor)) {
final var factor = BigDecimal.ONE.divide(BigDecimal.valueOf(divisor));
return dividend.multiply(factor, mc);
} else {
return dividend.divide(BigDecimal.valueOf(divisor), mc);
}
}
private boolean isDivisibleOnlyBy2And5(int i) {
while((i % 5) == 0) {
i /= 5;
}
while((i % 2) == 0) {
i /= 2;
}
return i == 1;
}
}
```
- relates to
-
JDK-8302204 Optimize BigDecimal.divide
- Open