What to do with Java BigDecimal performance?

JavaOptimizationCurrencyMathBigdecimal

Java Problem Overview


I write currency trading applications for living, so I have to work with monetary values (it's a shame that Java still doesn't have decimal float type and has nothing to support arbitrary-precision monetary calculations). "Use BigDecimal!" — you might say. I do. But now I have some code where performance is an issue, and BigDecimal is more than 1000 times (!) slower than double primitives.

The calculations are very simple: what the system does is calculating a = (1/b) * c many many times (where a, b and c are fixed-point values). The problem, however, lies with this (1/b). I can't use fixed point arithmetic because there is no fixed point. And BigDecimal result = a.multiply(BigDecimal.ONE.divide(b).multiply(c) is not only ugly, but sluggishly slow.

What can I use to replace BigDecimal? I need at least 10x performance increase. I found otherwise excellent [JScience library][1] which has arbitrary-precision arithmetics, but it's even slower than BigDecimal.

Any suggestions?

[1]: http://www.jscience.org "JScience"

Java Solutions


Solution 1 - Java

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.

Solution 2 - Java

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.

Solution 3 - Java

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]);
  }
}

Solution 4 - Java

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.

Solution 5 - Java

Store longs as the number of cents. For example, BigDecimal money = new BigDecimal ("4.20") becomes long money = 420. You just have to remember to mod by 100 to get dollars and cents for output. If you need to track, say, tenths of a cent, it'd become long money = 4200 instead.

Solution 6 - Java

You might want to move to fixed point math. Just searching for some libraries right now. on sourceforge fixed-point I haven't looked at this in depth yet. beartonics

Did you test with org.jscience.economics.money? since that has assured accuracy. The fixed point will only be as accurate as the # of bits assigned to each piece, but is fast.

Solution 7 - Java

Personally, I don't think BigDecimal is ideal for this.

You really want to implement your own Money class using longs internally to represent the smallest unit (i.e. cent, 10th cent). There is some work in that, implementing add() and divide() etc, but it's not really that hard.

Solution 8 - Java

What version of the JDK/JRE are you using?

Also you might try ArciMath BigDecimal to see if theirs speeds it up for you.

Edit:

I remember reading somewhere (I think it was Effective Java) that the BigDecmal class was changed from being JNI called to a C library to all Java at some point... and it got faster from that. So it could be that any arbitrary precision library you use is not going to get you the speed you need.

Solution 9 - Java

Only 10x performance increase desired for something that is 1000x slower than primitive?!.

Throwing a bit more hardware at this might be cheaper (considering the probability of having a currency calculation error).

Solution 10 - Java

On a 64bit JVM creating your BigDecimal as below makes it about 5x faster:

BigDecimal bd = new BigDecimal(Double.toString(d), MathContext.DECIMAL64);

Solution 11 - Java

1/b is not exactly representable with BigDecimal either. See the API docs to work out how the result is rounded.

It shouldn't be too difficult to write your own fixed decimal class based around a long field or two. I don't know any appropriate off the shelf libraries.

Solution 12 - Java

I know that I'm posting under very old topic, but this was the first topic found by google. Consider moving your calculations to the database from which you probably are taking the data for processing. Also I agree with Gareth Davis who wrote:

>. In most bog standard webapps the overhead of jdbc access and accessing other network > resources swamps any benefit of having really quick math.

In most cases wrong queries have higher impact on performance than math library.

Solution 13 - Java

Commons Math - The Apache Commons Mathematics Library

http://mvnrepository.com/artifact/org.apache.commons/commons-math3/3.2

According to my own benchmarking for my specific use case it's 10 - 20x slower than double (much better than 1000x) - basically for addition / multiplication. After benchmarking another algorithm which had a sequence of additions followed by an exponentiation the performance decrease was quite a bit worse: 200x - 400x. So it seems pretty fast for + and *, but not exp and log.

> Commons Math is a library of lightweight, self-contained mathematics and statistics components addressing the most common problems not > available in the Java programming language or Commons Lang.

Note: The API protects the constructors to force a factory pattern while naming the factory DfpField (rather than the somewhat more intuitive DfpFac or DfpFactory). So you have to use

new DfpField(numberOfDigits).newDfp(myNormalNumber)

to instantiate a Dfp, then you can call .multiply or whatever on this. I thought I'd mention this because it's a bit confusing.

Solution 14 - Java

Is JNI a possibility? You may be able to recover some speed and potentially leverage existing native fixed point libraries (maybe even some SSE* goodness too)

Perhaps http://gmplib.org/

Solution 15 - Java

Maybe you should look into getting hardware accelerated decimal arithmetics?

http://speleotrove.com/decimal/

Solution 16 - Java

Had a similar problem to this in an equity trading system back in 99. At the very start of the design we choose to have every number in the system represented as a long multiplied by 1000000 thus 1.3423 was 1342300L. But the main driver for this was memory foot print rather than straight line performance.

One word on caution, I wouldn't do this again today unless I was really sure that the math performance was super critical. In most bog standard webapps the overhead of jdbc access and accessing other network resources swamps any benefit of having really quick math.

Solution 17 - Java

It seems like the simplest solution is to use BigInteger instead of long to implement pesto's solution. If it seems messy it would be easy to write a class that wraps BigInteger to hide the precision adjustment.

Solution 18 - Java

easy... round your results often will eliminate double data type's error. if you are doing balance calculation, you have to also consider who will own the more/less penny caused by rounding.

bigdeciaml calculation produces more/less penny too, consider 100/3 case.

Solution 19 - Java

I know this is a really old thread, but i am writing an app (incidentally a trading app), in which computation of the indicators like MACD (which computes multiple exponential moving averages) over several thousand ticks of historical candlesticks was taking an unacceptable amount of time (several minutes). I was using BigDecimal.

every time i scrolled or resized the window, it would have to just iterate through the cached values to resize the Y scale, but even that would take several seconds to update. it made the app unusable. every time i would tweak the parameters for various indicators, it would take several minutes again to recompute.

then i switched it all to double and it's sooooo much faster. the problem was that i cache values using a hashmap. the solution i came up with uses a pool of wrappers for the double values. by pooling the wrappers, you don't take the performance hit of autoboxing to/from Double.

the app now calculates MACD (+MACD signal, MACD histogram) instantly with no lag. it's amazing how expensive BigDecimal object creation was. think about something like a.add( b.multiply( c )).scale(3) and how many objects that one statement creates.

 import java.util.HashMap;

public class FastDoubleMap<K>
{
	private static final Pool<Wrapper> POOL = new Pool<FastDoubleMap.Wrapper>()
	{
		protected Wrapper newInstance()
		{
			return new Wrapper();
		}
	};
	
	private final HashMap<K, Wrapper> mMap;
	
	public FastDoubleMap()
	{
		mMap = new HashMap<>();
	}

	public void put( K pKey, double pValue )
	{
		Wrapper lWrapper = POOL.checkOut();
		lWrapper.mValue = pValue;
		mMap.put( pKey, lWrapper );
	}
	
	public double get( K pKey )
	{
		Wrapper lWrapper  = mMap.get( pKey );
		if( lWrapper == null )
		{
			return Double.NaN;
		}
		else
		{
			return lWrapper.mValue;
		}
	}
	
    public double remove( K pKey )
    {
	    Wrapper lWrapper = mMap.remove( pKey );
	    if( lWrapper != null )
	    {
		    double lDouble = lWrapper.mDouble;
		    POOL.checkIn( lWrapper );
		    return lDouble;
	    }
	    else
	    {
		    return Double.NaN;
	    }
    }

	private static class Wrapper
		implements Pooled
	{
		private double mValue ;
		
		public void cleanup()
		{
			mValue = Double.NaN;
		}
	}
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAlexander TemerevView Question on Stackoverflow
Solution 1 - JavaVladimir DyuzhevView Answer on Stackoverflow
Solution 2 - JavaPeter LawreyView Answer on Stackoverflow
Solution 3 - Javadurron597View Answer on Stackoverflow
Solution 4 - JavaDJClayworthView Answer on Stackoverflow
Solution 5 - JavaPestoView Answer on Stackoverflow
Solution 6 - JavasfossenView Answer on Stackoverflow
Solution 7 - JavaSCdFView Answer on Stackoverflow
Solution 8 - JavaTofuBeerView Answer on Stackoverflow
Solution 9 - JavaRyan FernandesView Answer on Stackoverflow
Solution 10 - JavatsquaredView Answer on Stackoverflow
Solution 11 - JavaTom Hawtin - tacklineView Answer on Stackoverflow
Solution 12 - JavaMarek KowalczykView Answer on Stackoverflow
Solution 13 - JavasamthebestView Answer on Stackoverflow
Solution 14 - JavabasszeroView Answer on Stackoverflow
Solution 15 - JavaJohn NilssonView Answer on Stackoverflow
Solution 16 - JavaGareth DavisView Answer on Stackoverflow
Solution 17 - JavaozoneView Answer on Stackoverflow
Solution 18 - JavaChrisView Answer on Stackoverflow
Solution 19 - JavaTheWheelManView Answer on Stackoverflow