BigDecimal 下溢错误的可能解决方案

Possible solutions to BigDecimal underflow error

我正在尝试使用具有非常大的基数和指数的 BigDecimal.pow(int i),但是我收到 ArithmeticException: Underflow 错误。

简单来说,代码就是:

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

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

是的,这是一个欧拉计划问题。但是我知道我的数字是正确的。这不是数学问题,纯粹是我不明白为什么 BigDecimal.pow(int i) 给我一个 ArithmeticException: Underflow.

我知道 BigDecimalscale is a 32-bit int 但是有什么办法可以绕过它并计算这么大的值吗?如果有帮助,我确实计划对产品进行地板化并通过 100000000 对其进行修改,因为我只想要最后 8 位数字。如果有任何其他方法我可以在数学上做到这一点,我想要一个提示。

堆栈跟踪:

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

谢谢。

计算可能会分成几个部分,例如:

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 被抛出,因为 scaleValue * powValue[Integer.MIN_VALUE; Integer.MAX_VALUE] 段之外。请注意,在应用 pow 后重置比例是必要的,因为每次调用 pow 并等于 oldScaleValue * powValue

时都会重新计算 BigDecimal 比例

此外,我认为获取 pow 值将花费很多时间

答案是一个十进制数,有 6913580247 位小数,以“11234048”(最后 8 位小数)结尾。你的基数有 7 位小数,987654321 * 7 等于 6913580247.

我的问题是这个数字不能用 BigDecimal 表示,因为它需要 6913580247 的比例,这会溢出 BigDecimal 用于其比例的整数。我不知道你想要哪种格式的电话号码。下面的代码打印出结果为

Result is 1.1234048e-6913580240

也就是和科学记数法一样,只是指数超出了科学记数法的正常范围。对于我使用的模数 100000000:

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

现在我这样做了:

    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)));

我使用的是 Anton Dovzhenko 的回答中的技巧以及 987654321 是 2601 * 379721 这一事实。在我的计算机上计算大约需要 4 秒,这可能会有很大差异。

期待您的后续问题。

编辑:使用 BigInteger 而不是 BigDecimal 可以用更简单的代码更快地完成计算的核心部分:

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

(它打印 11234048 正如我们现在所知道的那样。)