61

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 which has arbitrary-precision arithmetics, but it's even slower than BigDecimal.

Any suggestions?

Elijah
  • 12,636
  • 9
  • 54
  • 88
Alexander Temerev
  • 611
  • 1
  • 6
  • 5
  • if the values of b and c have little variation, you could memoize the values. – sfossen Mar 04 '09 at 18:06
  • Oddly enough, this was something that was easier in C. Just link against a BCD library and you were done! – Brian Knoblauch Mar 04 '09 at 19:29
  • 2
    not odd at all, Java makes easier common tasks, while big decimal is not so much common. – Vladimir Dyuzhev Mar 04 '09 at 21:55
  • 1
    Don't laugh, but one solution is to use PHP. I just found this posting while investigating the reason why a small program I converted from PHP to Java was so much slower in Java than PHP. – Alex R Apr 24 '10 at 01:08
  • @AlexR: How did you achieved it? Does PHP give you access to AVX or did you employ the GPU? Fun aside, I'm really curios. Maybe post a question... – maaartinus Apr 04 '14 at 14:12
  • I realize I'm a few years late to the party, but what is the value of `b`? Unless `b` is special, the code `ONE.divide(b)` will crash. So you're leaving out a few details from your question. – Mark VY Apr 14 '18 at 00:37

20 Answers20

39

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.

Vladimir Dyuzhev
  • 17,603
  • 9
  • 45
  • 61
  • And implement division, rounding, exponentiation etc. myself from scratch? :) – Alexander Temerev Mar 04 '09 at 18:06
  • 5
    Yes, I believe that's what he's suggesting. – Ryan Graham Mar 04 '09 at 18:10
  • This is quite difficult task to get it right (if you doubt, take a look in Java Math classes). I don't believe no one else does high-performance monetary calculations in Java. – Alexander Temerev Mar 04 '09 at 18:14
  • 11
    It's a hard task to do it for a general-purpose library. For specific application (which uses only a **subset**) of operations it's trivial. In fact, I have such as class in my own app, and it only need 5 or 6 common operations. – Vladimir Dyuzhev Mar 04 '09 at 18:16
  • The gain would be essentially in that for computations you'd be using double*long, which is native op, and thus fast. E.g. USD/JPY * 1000 yen => double * long. If double covers your precision when multiples to biggest money amounts you have -- you're OK. – Vladimir Dyuzhev Mar 04 '09 at 18:21
  • 15
    If you write currency trading apps for a living, these calculations are your 'core functionality'. You will need to spend time and effort getting them right to give yourself a competitive advantage. – DJClayworth Mar 04 '09 at 18:28
  • DJClayworth: This is what I trying to do, after BigDecimal proved to be not enough for my needs. – Alexander Temerev Mar 04 '09 at 18:33
  • From browsing BigDecimal class it seems it wastes a considerable amount of time building a new object for every operation (e.g. because it's immutable). I guess if a mutable implementation existed, it would be faster. Not 10 times though, alas. – Vladimir Dyuzhev Mar 05 '09 at 17:39
18

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.

Peter Lawrey
  • 498,481
  • 72
  • 700
  • 1,075
  • 2
    Yes, the precision of double is more than sufficient. I do such things too, end it works perfectly unless I forget to round and the customer sees something like -1e-13 where they expect a non-negative result. – maaartinus Aug 04 '12 at 21:35
  • 2
    I have since designed three different trading systems for different funds and used `double` for prices or `long` cents. – Peter Lawrey Aug 05 '12 at 06:50
15

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]);
  }
}
durron597
  • 30,764
  • 16
  • 92
  • 150
  • +1 for making the benchmark but -1 for the implementation. You're mostly measuring how long it takes to create a `BigDecimal`... or more exactly, the creation overhead is present in all benchmarks and may dominate them. Unless it's what you wanted (but why?), you'd need to pre-create the values and store in an array. – maaartinus Apr 04 '14 at 13:50
  • @maaartinus Well this is embarrassing, I have gotten *so* much better at writing benchmarks in the last 14 months. I'll edit the post now – durron597 Apr 04 '14 at 14:15
  • +1 now the values make sense! I'm not sure about what you're doing with the `if`. It probably won't get optimized away but it may. I used to do something like `result += System.identityHashCode(o)` but then I discovered the [JMH `BlackHole`](http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-core/src/main/java/org/openjdk/jmh/logic/BlackHole.java). – maaartinus Apr 04 '14 at 15:25
  • @maaartinus Could you tell more about the JMH blackhole please? – Amrinder Arora Mar 17 '17 at 16:04
  • @AmrinderArora Not really. The [Blackhole](http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-core/src/main/java/org/openjdk/jmh/infra/Blackhole.java) is a pretty complicated thing doing something with the input, so it can't be optimized away. It's optimized for speed even in the multithreaded case. – maaartinus Mar 18 '17 at 06:08
9

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.

DJClayworth
  • 24,627
  • 8
  • 50
  • 71
5

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.

Pesto
  • 23,272
  • 2
  • 67
  • 76
  • that's adding even more operations. so that would be slower. – sfossen Mar 04 '09 at 18:07
  • 1
    How is it slower? Math computations on long are far, far faster than those on BigDecimal. You only need convert to dollars and cents for output. – Pesto Mar 04 '09 at 18:10
  • 2
    I need to track (in intermediate calculations) billionths of cents. Let's say we have a quote for USD/JPY: 99.223. Somewhere else I will need a JPY/USD quote, which is around 0.0100779022 (I need even more precision). – Alexander Temerev Mar 04 '09 at 18:10
  • @sfossen: more operations than what? BigDecimal? Definitely not. BigDecimal doesn't use long to store it's value (because the value can be almost arbitrary large). Using longs is definitely faster than BigDecimal. – Joachim Sauer Mar 04 '09 at 18:10
  • @Alexander: long is 64 bits, which mean a max value of 2^63 - 1, or roughly 9.22 x 10^18. If you want ten digits after the decimal in dollar terms, you get a max value of somewhere in the neighborhood of $9.22 x 10^8. You can decide if that's large enough for you. – Pesto Mar 04 '09 at 18:16
  • @Pesto: $9.22 x 10^8 is 90 billion. It's a normal daily trading volume on mid-range forex marketplaces. – Alexander Temerev Mar 04 '09 at 18:25
  • 1
    @Pesto: missed the long conversion, however, 2 decimal points is almost never acceptable in monetary calculations, although similar to my suggestion of fixed point math. – sfossen Mar 04 '09 at 18:25
  • @sfossen: Which is why I mentioned that in my original answer, and why Alexander and I just had this whole conversation about using additional decimal places. – Pesto Mar 04 '09 at 18:46
  • 1
    @Pesto: Ya, a single primitive won't be enough, which is why I suggested a fixed point library. – sfossen Mar 04 '09 at 18:53
5

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.

sfossen
  • 4,678
  • 22
  • 18
  • JScience is excellent library, I must admit; however, there is no performance improvement compared to BigDecimal. – Alexander Temerev Mar 04 '09 at 18:29
  • Using a fixed point library will get you speed, but you will lose some precision. You could try using BigInteger to make a fixed point library. – sfossen Mar 04 '09 at 18:35
  • 1
    Also don't use a power of ten, if you do this, use a power of 2. power of ten easier for humans but harder for computers :P – sfossen Mar 04 '09 at 21:08
3

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.

SCdF
  • 51,261
  • 23
  • 74
  • 108
3

I remember attending a sales presentation from IBM for a hardware accelerated implementation of BigDecimal. So if your target platform is IBM System z, or System p, you could exploit this seamlessly.

Nimantha
  • 4,731
  • 5
  • 15
  • 38
toolkit
  • 47,529
  • 17
  • 103
  • 134
2
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).

Ryan Fernandes
  • 7,732
  • 7
  • 32
  • 52
2

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.

TofuBeer
  • 58,140
  • 15
  • 111
  • 160
1

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.

1

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.

Tom Hawtin - tackline
  • 139,906
  • 30
  • 206
  • 293
0

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.

samthebest
  • 28,224
  • 21
  • 93
  • 129
0

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.

ozone
  • 311
  • 2
  • 9
0

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

BigDecimal bd = new BigDecimal(Double.toString(d), MathContext.DECIMAL64);
tsquared
  • 99
  • 4
0

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.

Chris
  • 29
  • 1
  • 1
    Rounding results _decreases_ accuracy, rather than increasing it. – Hannele Jan 18 '12 at 20:02
  • 1
    @Hannele Most of the time yes, but sometimes it indeed *increases* it. For example, when computing sum of prices where each of them is given with two decimal places, the rounding to two decimal places *guarantees* a correct result (unless you're summing many billions of values). – maaartinus Aug 04 '12 at 21:40
  • @maaartinus You have an interesting point! However, I don't believe that's directly applicable to the OP (division). – Hannele Aug 07 '12 at 15:19
  • 1
    @Hannele: Agreed, rounding helps only if you know how many decimal places the result should have which is not the case with division. – maaartinus Aug 07 '12 at 16:21
  • Doubles are fractional binary approximations of numbers and, as such, almost no fractional decimal numbers have an exact fractional binary representation. Unless the fractional part is some multiple of a negative power of 2, the representation in a double is an approximation. Decimal fractions that are multiples of negative powers of two are extremely rare compared to the total number of decimal fractions down to billionths. Sums of several thousand of these approximations will very quickly have issues that will start to show up in cents and make rounding/truncation solutions difficult. – Jim Jul 21 '13 at 22:01
  • 1
    If `double` values are scaled in a manner such that any domain-required rounding is always to a whole number, then any rounded values will be "exact" unless they're really big. For example, if things that will round to the nearest $0.01 are stored as a number of pennies rather than dollars, `double` will can penny-rounded amounts precisely unless they exceeed $45,035,996,273,704.96. – supercat Oct 22 '13 at 19:20
  • Use of a base-ten type rather than a base-2 type is only helpful for representing things whose true value is an exact fraction with a base-ten denominator; even there, suitably-scaled base-2 types can still offer equal accuracy with better performance, though one must use care to avoid scaling mistakes. – supercat Oct 22 '13 at 19:22
  • @Jim: I disagree. A `double` has nearly 16 digits. Assume 9 of them before the decimal point, 2 after, and you're left with 5 to burn... in a worst case you could do 10**5 operation before it blows, assuming random round-off error distribution average case it'd be 10**10. – maaartinus Apr 04 '14 at 13:59
  • @maaartinus - You express the thinking that has buried many in a sea of woes when they used doubles when they need EXACT decimal fractions. Your 16 digits is ALWAYS a binary approximation of a decimal fraction and it is the approximation that will eventually be your downfall. Systems that deal in financials must ALWAYS have exact decimal fractions where the distributive properites of addition, subtraction, multiplication, and division hold to EXACT decimal fractions. x(a+b+c) eventually does NOT have the same result as xa+xb+xc with doubles used for decimal fractions. Here endeth the lesson. – Jim Apr 05 '14 at 20:07
  • @Jim: Starting with exact decimal numbers, it's always possible to obtain the exact result via rounding the approximation as long as the approximation is good enough. The lack of distributive and associative properties doesn't matter as long as I can round the approximation to the exact result. I agree that there are computation for which `double` is too imprecise, but they're rather rare. – maaartinus Apr 05 '14 at 22:17
  • @maaartinus: I have extensive experience in a cellular billing system which originally used doubles that rounding and truncation, no matter how attempted, cannot always resolve a computation to an exact decimal result. Errors occurred frequently even if they were statistically rare. Statistical rarities amongst thousands of computations for each of millions of customers a month. is still a really big number. Cases that reproduced them were trivial to create. Rarity is not acceptable. The only acceptable computation errors for accounting acceptance and certification are IMPOSSIBLE and NEVER. – Jim Apr 07 '14 at 00:06
  • @Jim: By rare I didn't mean that a double-using computation may fail from time to time, but rather that it often works perfectly (and provably by error bounds analysis). With some tasks it does not work, just as yours. – maaartinus Apr 07 '14 at 09:26
  • @Jim I'm yet to see a realistic example when it can not work and there's a [small bounty](https://stackoverflow.com/q/42871564/581205) for it. There are people [using doubles with success](https://stackoverflow.com/questions/611732/what-to-do-with-java-bigdecimal-performance#comment15703198_612063). – maaartinus Jun 15 '17 at 18:50
0

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/

basszero
  • 28,508
  • 9
  • 50
  • 76
  • 1
    it is unlikely that JNI will help performance here, unless the calculations can be batched. JNI introduces significant overhead as you cross the JVM/native boundary. – Kevin Day Mar 05 '09 at 05:30
  • 1
    You are correct that the boundary does have a slowdown and I've definitely felt that pain but if BigDecimal truly has the claimed 1000x slowdown and JNI was only a fraction, it may be worth it. – basszero Mar 05 '09 at 13:50
0

Can you provide more insight as to the purpose of the calculation?

What your dealing with is a trade-off between speed and precision. How great will the loss in precision be if you switched to a primitive?

I think in some cases the user may be comfortable with less accuracy in exchange for speed, so long as they can hone in on the accurate calculation when needed. It really depends on what you will use this calculation for.

Perhaps you can allow the user to preview the result quickly using doubles, and then request the more precise value using BigDecimal if they wish?

Justin Standard
  • 20,961
  • 22
  • 74
  • 89
0

Maybe you should look into getting hardware accelerated decimal arithmetics?

http://speleotrove.com/decimal/

John Nilsson
  • 16,089
  • 8
  • 29
  • 41
0

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.

Gareth Davis
  • 26,716
  • 11
  • 69
  • 103