views:

3508

answers:

15

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?

+13  A: 

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
And implement division, rounding, exponentiation etc. myself from scratch? :)
Alexander Temerev
Yes, I believe that's what he's suggesting.
Ryan Graham
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
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
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
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
DJClayworth: This is what I trying to do, after BigDecimal proved to be not enough for my needs.
Alexander Temerev
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
+4  A: 

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
that's adding even more operations. so that would be slower.
sfossen
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
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
@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
@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
@Pesto: $9.22 x 10^8 is 90 billion. It's a normal daily trading volume on mid-range forex marketplaces.
Alexander Temerev
@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
@Pesto: No, it's 900 million, it's clearly not enough.
Alexander Temerev
@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
@Pesto: Ya, a single primitive won't be enough, which is why I suggested a fixed point library.
sfossen
+4  A: 

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
JScience is excellent library, I must admit; however, there is no performance improvement compared to BigDecimal.
Alexander Temerev
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
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
+1  A: 

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
I don't need exact representation; I need knowable precision.
Alexander Temerev
+6  A: 

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
Will you suggest me an algorithm for division in this case?
Alexander Temerev
+1  A: 

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
1.6.I tested ArciMath. The performance gain is 2x at best...
Alexander Temerev
A: 

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
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
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
A: 

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
A: 

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
A: 

Maybe you should look into getting hardware accelerated decimal arithmetics?

http://speleotrove.com/decimal/

John Nilsson
+1  A: 

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
+1  A: 

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.

The following link might be of some use.

http://www-03.ibm.com/servers/enable/site/education/wp/181ee/181ee.pdf

toolkit
A: 

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
A: 

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
A: 

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