1

I am trying to use the BigDecimal.pow(int i) with very big base and exponents, however I'm getting an ArithmeticException: Underflow error.

To simply put it, the code is:

BigDecimal base = BigDecimal.valueOf(2147483645.4141948);
BigDecimal product = base.pow(987654321);

System.out.println("product = " + product.toPlainString());

Yes, this is a Project Euler problem. However I know my numbers are correct. This is not a mathematical problem, it is purely me not understanding why BigDecimal.pow(int i) is giving me an ArithmeticException: Underflow.

I know that BigDecimal's scale is a 32-bit int but is there any way at all to bypass this and calculate such a big value? If it helps, I do plan on flooring the product and modding it by 100000000 since I only want the last 8 digits. If there is any other way I could do this mathematically, I'd like a hint.

Stack trace:

Exception in thread "main" java.lang.ArithmeticException: Underflow
    at java.math.BigDecimal.checkScale(BigDecimal.java:3841)
    at java.math.BigDecimal.pow(BigDecimal.java:2013)
    at test.main(test.java:10)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

Process finished with exit code 1

Thanks.

kkmonlee
  • 379
  • 2
  • 16
  • 3
    `BigDecimal.valueOf(2147483645.4141948)` - no! Never construct a BigDecimal from a floating-point literal; you're already incurring a rounding error just by doing that. Construct it from a string literal: `new BigDecimal("2147483645.4141948")`. – user2357112 Dec 24 '16 at 02:22
  • @user2357112 `BigDecimal.valueOf()` takes `long l` as its argument, not a string literal though. – kkmonlee Dec 24 '16 at 02:23
  • Whoops, fixed. You need the constructor, not valueOf. – user2357112 Dec 24 '16 at 02:26
  • @user2357112 Sadly that still gives me the same error. – kkmonlee Dec 24 '16 at 02:28
  • Can you add the stack trace please. – Rajind Ruparathna Dec 24 '16 at 03:39
  • 1
    http://stackoverflow.com/a/40556096/1849366 – Rajind Ruparathna Dec 24 '16 at 03:42
  • May that calculation overflow your available RAM? – Ole V.V. Dec 24 '16 at 04:32
  • 1
    Could it be that there is a solution to your Project Euler problem that does not require such huge calculations? – Ole V.V. Dec 24 '16 at 04:32
  • When `BigDecimal.pow` method is invoked `checkScale` is done. In case `scale * powValue` is greater than `Integer.MAX_VALUE` or lower than `Integer.MIN_VALUE` corresponding Underflow or Overflow exception is thrown. As you have 7 digits after dot, your default scale is 7. What could you do is: 1. set appropriate scale for base 2. invoke `pow` several times with value, which composition with `scale` won't break Integer MIN_VALUE/MAX_VALUE limits – Anton Dovzhenko Dec 24 '16 at 04:40
  • Which Project Euler problem? – ruakh Dec 24 '16 at 05:06
  • @OleV.V. As I mentioned in my question, I plan on using the `floor()` function on `2147483645.4141948 ^ 987654321` and using the last 8 digits as the solution to my Project Euler problem. – kkmonlee Dec 24 '16 at 18:41
  • Thanks for editing the question. Just to be sure, you mean the last 8 digits before the decimal point? – Ole V.V. Dec 25 '16 at 10:40
  • @OleV.V. Unfortunately, no. I need the last 8 digits after the decimal point. – kkmonlee Jan 07 '17 at 17:00
  • You said you would do modulo 100000000, which will keep the last 8 digits *before* the decimal point, and now you say those are *not* the ones you want?? Please explain. – Ole V.V. Jan 08 '17 at 07:48
  • The last 8 digits after the decimal point? So assuming the result has 6913580247 decimals (digits after the point), would it be OK to discard the first 6913580239 decimals and only keep the last 8? If so, the task is suddenly easy enough. – Ole V.V. Jan 08 '17 at 07:50
  • @OleV.V. Yes, it is OK to discard the other decimals as long as I have the last 8. By the way how would I only get the last 8 digits without calculating the whole thing? – kkmonlee Jan 08 '17 at 21:22
  • @user2357112, sorry my comment is so late: The documentation of `BigDecimal.valueOf(double)` says “This is generally the preferred way to convert a `double` (or `float`) into a `BigDecimal`, as the value returned is equal to that resulting from constructing a `BigDecimal` from the result of using `Double.toString(double)` .” I also checked, in this case no rounding error is introduced (`base` has a scale of 7 and represents the number exactly). – Ole V.V. Jan 09 '17 at 10:03
  • @OleV.V.: It might be the usually preferred way to convert a double to a BigDecimal, but it's not the preferred way to write a "BigDecimal literal"; if you want to put a fixed BigDecimal into your program, it's better to use the string constructor. I will admit that I was thinking of the `new BigDecimal(someDouble)` behavior instead of the `BigDecimal.valueOf(someDouble)` behavior, though. – user2357112 Jan 09 '17 at 20:17

2 Answers2

1

Computation could be broken in several parts, for example:

BigDecimal base = BigDecimal.valueOf(2147483645.4141948);
base = base.setScale(20, BigDecimal.ROUND_FLOOR);
// 109739369 = 6455257 * 17
base = base.pow(17).setScale(20, BigDecimal.ROUND_FLOOR);
base = base.pow(6455257);

ArithmeticException is thrown because scaleValue * powValue is outside [Integer.MIN_VALUE; Integer.MAX_VALUE] segment. Note, that resetting of scale after applying pow is necessary, because BigDecimal scale is recalculated every time pow is invoked and equal to oldScaleValue * powValue

Also, I assume, that getting pow value will take much time

Anton Dovzhenko
  • 2,399
  • 11
  • 16
1

The answer is a decimal number with 6913580247 decimals ending in "11234048" (the last 8 decimals). You have 7 decimals in your base, and 987654321 * 7 equals 6913580247.

My problem is this number cannot be represented in a BigDecimal because it would need a scale of 6913580247, which overflows the integer that BigDecimal uses for its scale. I don’t know in which format you want your number instead. The following code prints out the result as

Result is 1.1234048e-6913580240

That is, like scientific notation, only with an exponent out of the normal range for scientific notation. For the modulo 100000000 I am using:

public static final BigDecimal moduloBase = new BigDecimal(10).pow(8); // 8 digits

Now I do:

    long noOfDecimals = 987654321L * 7L;

    BigDecimal bd = new BigDecimal("54141948"); // last 8 digits of base
    bd = bd.pow(379721);
    bd = bd.remainder(moduloBase);
    bd = bd.pow(2601);
    bd = bd.remainder(moduloBase);

    double result = bd.doubleValue() / 10_000_000.0; // print with 7 decimals
    System.out.println("Result is " + result + "e" + (-(noOfDecimals - 7)));

I am using the trick from Anton Dovzhenko’s answer and the fact that 987654321 is 2601 * 379721. The calculation takes some 4 seconds on my computer, this will probably vary a great deal.

Looking forward to your follow-up questions.

EDIT: The central part of the calculation can be done both with simpler code and faster using BigInteger instead of BigDecimal:

    BigInteger bi = new BigInteger("54141948");
    bi = bi.modPow(new BigInteger("987654321"), new BigInteger("100000000"));
    System.out.println("As BigInteger: " + bi);

(It prints 11234048 as we now know it should.)

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161