Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8269667

BigDecimal.divide with MathContext is significantly slower for certain divisors

XMLWordPrintable

    • generic
    • generic

      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;
          }
      }
      ```


            bpb Brian Burkhalter
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated: