6

I need to represent some numbers in Java with perfect precision and fixed number of decimal points after decimal point; after that decimal point, I don't care. (More concretely - money and percentages.)

I used Java's own BigDecimal now, but I found out, that it's really slow and it starts to show in my application.

So I want to solve it with a "regular" integers and a fixed-point arithmetics (long integers have big enough precision for my purposes).

Now, I would think that I am not the first one who has this kind of problem and there would be already a library for that, that already has multiplication/division implemented - but it seems that it isn't.

Now, I very probably can write it myself (and I probably will), but really, am I really the first person that needs this? Isn't there already some library for that?

Karel Bílek
  • 36,467
  • 31
  • 94
  • 149

4 Answers4

10

decimal4j is a Java library for fast fixed precision arithmetic based on longs with support for up to 18 decimal places.

Disclosure: I am involved in the decimal4j project.

marco
  • 711
  • 6
  • 14
6

Are you completely sure BigDecimal is the performance problem? Did you use a profiler to find out? If yes, two options that could help are:

1) Use long and multiply all values by a factor (for example 100 if you are interested in cents).

2) Use a specially designed class that implements something similar to BigDecimal, but using long internally. I don't know if a good open source library exists (maybe the Java Math Fixed Point Library?). I wrote one such class myself quite a long time ago (2001 I believe) for J2ME. It's a bit tricky to get right. Please note BigDecimal uses a long internally as well except if high precision is needed, so this solution will only help a tiny bit in most cases.

Using double isn't a good option in many cases, because of rounding and precision problems.

Thomas Mueller
  • 48,905
  • 14
  • 116
  • 132
  • +1 All problems can be solved and rounding errors are not random errors and easily solved. IHMO I would use `long` if that is simpler, otherwise `double` and `BigDecimal` if you have really large numbers. – Peter Lawrey Aug 11 '12 at 19:04
  • 1
    *Use a specially designed class that implements something similar to BigDecimal, but using long internally.* `BigDecimal` does exactly that, unless inflated (i.e. `long` ain't enough). – bestsss Aug 11 '12 at 19:22
  • `BigDecimal` doesn't use `long` internally. It uses `BigInteger`, which in turn uses an `int[]` internally. That's why `BigDecimal` is a bit slower than using a `long`. However, in many (most) cases the performance difference is too small to to be important. – Thomas Mueller Aug 11 '12 at 19:46
  • *It uses BigInteger, which in turn uses an int[] internally* untrue for values that fit long. Read the code again, I claim I know how it works. – bestsss Aug 11 '12 at 20:31
  • /** * If the absolute value of the significand of this BigDecimal is * less than or equal to {@code Long.MAX_VALUE}, the value can be * compactly stored in this field and used in computations. */ private transient long intCompact; – bestsss Aug 11 '12 at 20:32
  • OK, I was wrong, the bottleneck is somewhere else (near the BigDecimal, so that's why I was confused) .... I am not sure what to do with the question now :) should I close it? – Karel Bílek Aug 11 '12 at 20:40
  • @Karel, objects creation can still be an issue if you have tons of but I do doubt it's the bottle neck for real. (GC for BigDecimals is trivial as they die in young gen) – bestsss Aug 11 '12 at 20:51
  • @bestsss You are right about BigInteger using long! I didn't know about this optimization... Thanks a lot! There's always something new to be learned. – Thomas Mueller Aug 12 '12 at 01:21
0

Not sure why you need a library for it.

For example, say you want to add two longs with the same fixed precision

long c = a + b;

Say you have a fixed precision number you want to multiple by an integer

long c = a * i;

Say you want to divide a number by a integer rounding to zero

long c = a / i;

Say you want to print a fixed precision number with 3 decimal places.

System.out.println(c / 1e3);

Perhaps you are over thinking the problem and assuming you need a library for everything.

If you are using long or double you might want a small number helper methods for rounding, but you don't need a library as such.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • 3
    -1 library would be useful, 3 fraction digits example: 1.234 * 1.234 is 1234 * 1234 = 1522756, you have to divide by 1000, otherwise you get 1522.756 instead of correct 1.522 – peenut Mar 22 '13 at 08:20
  • 1
    @Peenut It's so trivial to write a helper class for your applications needs, but I have never bothered, nor have I ever seen it done. Not sure why -1. – Peter Lawrey Mar 22 '13 at 14:23
  • 1
    let me quote better answer: "It's a bit tricky to get right." – peenut May 01 '13 at 11:34
  • 2
    The worst part of this is dealing with arithmetic overflows. Say we need to track 5 decimal places (e.g. to track prices of currency pairs in 1/10 of a pip). Multiplication of two fixed-point numbers (e.g. price*size) can easily lead to overflow with decimal numbers as small as 100K. – Andy Malakov Mar 05 '16 at 17:41
  • @AndyMalakov I agree, my preference is to use `double` which neither will overflow for sane numbers, nor do you having the wrong order of magnitude if you use fixed precision. While double can produce strange numbers with representation error, at least they are obviously strange (but relatively small errors) If fixed precision or BigDecimal is wrong it can still look just fine. – Peter Lawrey Mar 05 '16 at 19:19
  • 1
    Take a serious look at Spire (https://github.com/non/spire). It requires using Scala, not Java, but it has a really interesting feature set. – James Moore Mar 04 '18 at 16:25
0

Although this is not exactly what you are asking about, this can speed up your app without leaving BigDecimal:

Since Java 8, this is solved by BigDecimal itself. A new class MathContext was added and limits the precision to which the operations are calculated.

var num = new BigDecimal("1234.56780", new MathContext(10, RoundingMode.DOWN));

The catch is that the precision 10 does not apply to digits after decimal point. It applies to the number of significant digits. For 1234.50, 6 is needed.
For 1_500_000_000.100, 13 is needed to keep the number as is.
So the precision might suffer when you had a precision of 10 and counted billions of Czech Korunas.
Still, a precision of, say, 1000, is way faster than unlimited precision (which is I think the default).

This can also be applied to the individual operations:

BigDecimal n = new BigDecimal("0.12345");
n = n.pow(2, new MathContext(1000, RoundingMode.DOWN));
n = n.pow(2, new MathContext(1000, RoundingMode.DOWN));
n = n.pow(2, new MathContext(1000, RoundingMode.DOWN));
n = n.pow(2, new MathContext(1000, RoundingMode.DOWN));
Ondra Žižka
  • 43,948
  • 41
  • 217
  • 277