在Java中如何对BigDecimal进行分数次幂运算?

30
在我的小项目中,我需要做类似 Math.pow(7777.66, 5555.44) 的操作,但需要使用非常大的数字。我找到了一些解决方案:
  • 使用double - 但是数字太大
  • 使用BigDecimal.pow 但是不支持分数
  • 使用 X^(A+B)=X^A*X^B 公式 (B 是第二个数的余数),但是再次没有支持大的 X 或大的 A 因为我仍然会转换为 double
  • 使用某种泰勒级数算法或类似于此的东西 - 我不太擅长数学,所以这是我的最后一个选择,如果我找不到任何解决方案 (一些库或公式用于 (A+B)^(C+D))。

有人知道一个库或容易的解决方案吗? 我想很多人都会遇到同样的问题...

p.s. 我发现了一个叫 ApFloat 的库,声称可以近似计算,但我得到的结果非常近似,甚至 8 ^ 2 都给了我 60...


你能给一个你想要实现的例子吗?8^2 != 64 听起来不太好,2^100^100 需要缩小规模。 - stacker
我必须说,我尝试了这个公式技巧,即使是数位达到百万级别的数字,它也运行良好!(看来我并不知道double和int的所有知识)... 例如: 50!^10! = 12.5091131786207625236425910^233996181 50!^0.06 = 7395.788659356498101260513 代码有点长,无法在此处发布,但你可以理解X^(A+B)=X^AX^B的思想... 现在我正在尝试理解它如何以及为什么(以及是否)适用于如此巨大的数字。 - Eugene Marin
1
我已经在这里提供了解决方案 https://dev59.com/wmfWa4cB1Zd3GeqPcgHw#22556217 - softawareblog.com
3个回答

27

处理小于1.7976931348623157E308(Double.MAX_VALUE)的参数并支持结果有数百万位:

由于double类型支持的数字最大为MAX_VALUE(例如,100!在double类型下的值为9.332621544394415E157),因此使用BigDecimal.doubleValue()没有问题。但是如果使用 Math.pow(double, double)计算,如果计算结果超过MAX_VALUE将会得到无穷大。所以,应该使用公式X^(A+B)=X^A*X^B来将计算分解成两个幂次运算,其中一个是大数部分,使用BigDecimal.pow计算,另一个是小数部分(第二个参数的余数),使用Math.pow计算,然后相乘。X将被复制为DOUBLE,确保它不大于MAX_VALUE,A将是INT类型(最大为2147483647,但是BigDecimal.pow不支持超过十亿的整数),而B将是double类型,始终小于1。这样你就可以做以下操作(忽略我的私人常量等):

    int signOf2 = n2.signum();
    try {
        // Perform X^(A+B)=X^A*X^B (B = remainder)
        double dn1 = n1.doubleValue();
        // Compare the same row of digits according to context
        if (!CalculatorUtils.isEqual(n1, dn1))
            throw new Exception(); // Cannot convert n1 to double
        n2 = n2.multiply(new BigDecimal(signOf2)); // n2 is now positive
        BigDecimal remainderOf2 = n2.remainder(BigDecimal.ONE);
        BigDecimal n2IntPart = n2.subtract(remainderOf2);
        // Calculate big part of the power using context -
        // bigger range and performance but lower accuracy
        BigDecimal intPow = n1.pow(n2IntPart.intValueExact(),
                CalculatorConstants.DEFAULT_CONTEXT);
        BigDecimal doublePow =
            new BigDecimal(Math.pow(dn1, remainderOf2.doubleValue()));
        result = intPow.multiply(doublePow);
    } catch (Exception e) {
        if (e instanceof CalculatorException)
            throw (CalculatorException) e;
        throw new CalculatorException(
            CalculatorConstants.Errors.UNSUPPORTED_NUMBER_ +
                "power!");
    }
    // Fix negative power
    if (signOf2 == -1)
        result = BigDecimal.ONE.divide(result, CalculatorConstants.BIG_SCALE,
                RoundingMode.HALF_UP);

结果示例:

50!^10! = 12.50911317862076252364259*10^233996181

50!^0.06 = 7395.788659356498101260513

10
没有 CalculatorUtilsCalculatorConstantsCalculatorException 类,这是没有用的。 - Ky -

2

采用 MIT 许可证发布的 big-math 库提供了一个简单的静态辅助方法 BigDecimalMath.log(BigDecimal, MathContext),用于计算对数以及许多 BigDecimal 不包含的其他函数。使用非常简单,并且具有大量基准测试数据可用于比较性能。


0

这个问题的被采纳答案所引用的源代码不仅仅适用于自然对数,还有更多的解决方案。 - prunge
1
@Prunge - 谢谢。我实际上从未提到自然对数。如果您看一下Gene Marin的被接受的答案,他所描述的是对数。X^(A+B)=X^AX^B等价于说log(base X)A + log(base X)B = log(base X)(AB)。这应该可以让您将数字带到一个可管理的数量级。 - Matthew Flynn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接