What to do with Java BigDecimal performance?

May be you should start with replacing a = (1/b) * c with a = c/b ? It's not 10x, but still something.

If I were you, I'd create my own class Money, which would keep long dollars and long cents, and do math in it.


Most double operations give you more than enough precision. You can represent $10 trillion with cent accuracy with double which may be more than enough for you.

In all the trading systems I have worked on (four different banks), they have used double with appropriate rounding. I don't see any reason to be using BigDecimal.


So my original answer was just flat out wrong, because my benchmark was written badly. I guess I'm the one who should have been criticized, not OP ;) This may have been one of the first benchmarks I ever wrote... oh well, that's how you learn. Rather than deleting the answer, here are the results where I'm not measuring the wrong thing. Some notes:

  • Precalculate the arrays so I don't mess with the results by generating them
  • Don't ever call BigDecimal.doubleValue(), as it's extremely slow
  • Don't mess with the results by adding BigDecimals. Just return one value, and use an if statement to prevent compiler optimization. Make sure to have it work most of the time to allow branch prediction to eliminate that part of the code, though.

Tests:

  • BigDecimal: do the math exactly as you suggested it
  • BigDecNoRecip: (1/b) * c = c/b, just do c/b
  • Double: do the math with doubles

Here is the output:

 0% Scenario{vm=java, trial=0, benchmark=Double} 0.34 ns; ?=0.00 ns @ 3 trials
33% Scenario{vm=java, trial=0, benchmark=BigDecimal} 356.03 ns; ?=11.51 ns @ 10 trials
67% Scenario{vm=java, trial=0, benchmark=BigDecNoRecip} 301.91 ns; ?=14.86 ns @ 10 trials

    benchmark      ns linear runtime
       Double   0.335 =
   BigDecimal 356.031 ==============================
BigDecNoRecip 301.909 =========================

vm: java
trial: 0

Here's the code:

import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Random;

import com.google.caliper.Runner;
import com.google.caliper.SimpleBenchmark;

public class BigDecimalTest {
  public static class Benchmark1 extends SimpleBenchmark {
    private static int ARRAY_SIZE = 131072;

    private Random r;

    private BigDecimal[][] bigValues = new BigDecimal[3][];
    private double[][] doubleValues = new double[3][];

    @Override
    protected void setUp() throws Exception {
      super.setUp();
      r = new Random();

      for(int i = 0; i < 3; i++) {
        bigValues[i] = new BigDecimal[ARRAY_SIZE];
        doubleValues[i] = new double[ARRAY_SIZE];

        for(int j = 0; j < ARRAY_SIZE; j++) {
          doubleValues[i][j] = r.nextDouble() * 1000000;
          bigValues[i][j] = BigDecimal.valueOf(doubleValues[i][j]); 
        }
      }
    }

    public double timeDouble(int reps) {
      double returnValue = 0;
      for (int i = 0; i < reps; i++) {
        double a = doubleValues[0][reps & 131071];
        double b = doubleValues[1][reps & 131071];
        double c = doubleValues[2][reps & 131071];
        double division = a * (1/b) * c; 
        if((i & 255) == 0) returnValue = division;
      }
      return returnValue;
    }

    public BigDecimal timeBigDecimal(int reps) {
      BigDecimal returnValue = BigDecimal.ZERO;
      for (int i = 0; i < reps; i++) {
        BigDecimal a = bigValues[0][reps & 131071];
        BigDecimal b = bigValues[1][reps & 131071];
        BigDecimal c = bigValues[2][reps & 131071];
        BigDecimal division = a.multiply(BigDecimal.ONE.divide(b, MathContext.DECIMAL64).multiply(c));
        if((i & 255) == 0) returnValue = division;
      }
      return returnValue;
    }

    public BigDecimal timeBigDecNoRecip(int reps) {
      BigDecimal returnValue = BigDecimal.ZERO;
      for (int i = 0; i < reps; i++) {
        BigDecimal a = bigValues[0][reps & 131071];
        BigDecimal b = bigValues[1][reps & 131071];
        BigDecimal c = bigValues[2][reps & 131071];
        BigDecimal division = a.multiply(c.divide(b, MathContext.DECIMAL64));
        if((i & 255) == 0) returnValue = division;
      }
      return returnValue;
    }
  }

  public static void main(String... args) {
    Runner.main(Benchmark1.class, new String[0]);
  }
}

Assuming you can work to some arbitrary but known precision (say a billionth of a cent) and have a known maximum value you need handle (a trillion trillion dollars?) you can write a class which stores that value as an integer number of billionths of a cent. You'll need two longs to represent it. That should be maybe ten times as slow as using double; about a hundred times as fast as BigDecimal.

Most of the operations are just performing the operation on each part and renormalizing. Division is slightly more complicated, but not much.

EDIT:In response to the comment. You will need to implement a bitshift operation on your class (easy as along as the multiplier for the high long is a power of two). To do division shift the divisor until it's not quite bigger than the dividend; subtract shifted divisor from dividend and increment the result (with appropriate shift). Repeat.

EDIT AGAIN:You may find BigInteger does what you need here.