13

Method BigDecimal.add takes a long time when one argument has a big exponent (9 digits), and the second has an exponent with the different length. I've waited for more than 5 minutes, and it was still going on and on.

Here's code:

@Test
public void testAddBig() throws Exception {
    MathContext mc = new MathContext(10, RoundingMode.HALF_UP);
    BigDecimal v1 = new BigDecimal("1E+100000000", mc);
    BigDecimal v2 = new BigDecimal("1", mc);
    System.out.println(v1.add(v2));
}

Here's part of thread dump:

at java.math.BigInteger.square(BigInteger.java:1884)
at java.math.BigInteger.squareKaratsuba(BigInteger.java:1975)
at java.math.BigInteger.square(BigInteger.java:1888)
at java.math.BigInteger.squareToomCook3(BigInteger.java:2011)
at java.math.BigInteger.square(BigInteger.java:1890)
at java.math.BigInteger.squareToomCook3(BigInteger.java:2006)
at java.math.BigInteger.square(BigInteger.java:1890)
at java.math.BigInteger.squareToomCook3(BigInteger.java:2012)
at java.math.BigInteger.square(BigInteger.java:1890)
at java.math.BigInteger.squareToomCook3(BigInteger.java:2010)
at java.math.BigInteger.square(BigInteger.java:1890)
at java.math.BigInteger.squareToomCook3(BigInteger.java:2006)
at java.math.BigInteger.square(BigInteger.java:1890)
at java.math.BigInteger.squareToomCook3(BigInteger.java:2012)
at java.math.BigInteger.square(BigInteger.java:1890)
at java.math.BigInteger.squareToomCook3(BigInteger.java:2011)
at java.math.BigInteger.square(BigInteger.java:1890)
at java.math.BigInteger.pow(BigInteger.java:2263)
at java.math.BigDecimal.bigTenToThe(BigDecimal.java:3543)
at java.math.BigDecimal.bigMultiplyPowerTen(BigDecimal.java:4508)
at java.math.BigDecimal.add(BigDecimal.java:4443)
at java.math.BigDecimal.add(BigDecimal.java:1289)

What is going on? Is this a bug?

GhostCat
  • 137,827
  • 25
  • 176
  • 248
VMN
  • 275
  • 2
  • 13
  • You ask for a huge number here. On a VM, I had a OutOfMemoryError because of this ;) Just do this in the windows calculator ;) It give me a mice Invalid Imput. That give you an idea of the length of this number. – AxelH Nov 08 '16 at 13:02
  • 2
    @VMN: Do you know what `1E+100000000` means? – Axel Nov 08 '16 at 13:08
  • Plus, there is this comment in bigTenToThe `BigInteger.pow is slow, so make 10**n by constructing a BigInteger from a character string (still not very fast)` using a char[] of the size of your number (not small...) So don't expect to be able to use BigNumbers that fast. – AxelH Nov 08 '16 at 13:09
  • 1
    @VMN You probably made a mistake, number of all atoms in universe is about 10e+82:), 9 digits is `1E+9` – olsli Nov 08 '16 at 13:11
  • @Axel This is really big number, I know, but it's just test.To be clear, I tried to test cases java BigDecimal with of test of javascript implementation https://github.com/MikeMcl/decimal.js/blob/master/test/modules/plus.js#L544 – VMN Nov 08 '16 at 13:18
  • BigDecimal is using character to store those values, have a number that big won't fit in Memory. Not sure how `decimal.js` works. – AxelH Nov 08 '16 at 13:22
  • if i print BigDecimal.toPlainString() for 1E+100 that itself prints this 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000. This number is not practically required for any banking or finance application. – Karthik Nov 08 '16 at 13:37
  • `1e+10000000` is an enormously huge number (100 million digits). Of course that is slow. Heck, even just creating such a number, or turning such a number into a string is bound to be slow already. – Rudy Velthuis Nov 08 '16 at 14:46

1 Answers1

7

Well, to answer this simply. BigNumbers are working with array of character to keep the precision at the maximum. Since you number will be composed of 100000000 digits, this will be the length of your array.

100.000.000 chars = bytes

This is 100MB if I am not mistaken. Then you want to do math with that, this start to be a lot of reading ;)

If you open the BigDecimal class, you will see that there is a lot of checks since this is "letter" and not numbers.

BigDecimal are usefull to keep precision but this is done at some cost, here memory and time of processing.

EDIT :

This will only be a problem if you use the instance in some ways, the constructor will store the value as a exponent value 1E+***. If you print it this will be fine but if you ask a numerical value, this will start to failed.

To be more precise, BigDecimal.bigTenToThe(int) will receive the exponent value (1000000000).

private static BigInteger bigTenToThe(int n) {
    ...
    char tenpow[] = new char[n + 1];
    ....
}
AxelH
  • 14,325
  • 2
  • 25
  • 55
  • Mm, internal representation looks like (intCompact = 1, scale = -10000000, precision = 1) There is no 100.000.000 chars – VMN Nov 08 '16 at 13:51
  • @VMN Until you do some math with it. I have added more info, see your stack trace, you will see the method I talked about. – AxelH Nov 08 '16 at 13:56
  • Thanks, I understood. In my version of jdk (1.8.72) `int[] mag` is used inside `BigInteger` but this fact doesn't change a lot. – VMN Nov 08 '16 at 14:16
  • @VMN This is from Java 6 .. there is a comment with this code saying that this use a char array for efficiency but might change their minds later. Told me if you want more detail, I can still dig more into this later but I don't see what we could add in the answer. If this is enought for you, you know what to do ;) – AxelH Nov 08 '16 at 14:26
  • BigDecimals (and BigIntegers) do not use arrays of bytes where each byte is a digit. They use arrays of 32 bit values which together form the bits of the BigInteger (and a BigDecimal is just a BigInteger and a scale). And @VMN is right, it is stored as 1 with scale -100000000. But as soon as you add a small number, it must be scaled to a huge BigInteger to which the small number can be added. That is terribly slow. – Rudy Velthuis Nov 09 '16 at 20:46
  • @RudyVelthuis, that's what I tried to explain shortly in the edit. They only use full values array when there is an operation needed. If this is not clear, I could try to be more specific. – AxelH Nov 10 '16 at 07:02