为了账号安全,请及时绑定邮箱和手机立即绑定

BigDecimal的对数

BigDecimal的对数

拉风的咖菲猫 2019-12-15 16:12:12
如何计算BigDecimal的对数?有人知道我可以使用的任何算法吗?到目前为止,我的谷歌搜索工作提出了(无用的)想法,即仅转换为double并使用Math.log。我将提供所需答案的精确度。编辑:任何基地都可以。如果在base x中更简单,我会这样做。
查看完整描述

3 回答

?
撒科打诨

TA贡献1934条经验 获得超2个赞

Java Number Cruncher:《 Java数值计算程序员指南》提供了使用牛顿方法的解决方案。这本书的源代码在这里。以下内容摘自第12.5章大十进制函数(p330&p331):

/**

 * Compute the natural logarithm of x to a given scale, x > 0.

 */

public static BigDecimal ln(BigDecimal x, int scale)

{

    // Check that x > 0.

    if (x.signum() <= 0) {

        throw new IllegalArgumentException("x <= 0");

    }


    // The number of digits to the left of the decimal point.

    int magnitude = x.toString().length() - x.scale() - 1;


    if (magnitude < 3) {

        return lnNewton(x, scale);

    }


    // Compute magnitude*ln(x^(1/magnitude)).

    else {


        // x^(1/magnitude)

        BigDecimal root = intRoot(x, magnitude, scale);


        // ln(x^(1/magnitude))

        BigDecimal lnRoot = lnNewton(root, scale);


        // magnitude*ln(x^(1/magnitude))

        return BigDecimal.valueOf(magnitude).multiply(lnRoot)

                    .setScale(scale, BigDecimal.ROUND_HALF_EVEN);

    }

}


/**

 * Compute the natural logarithm of x to a given scale, x > 0.

 * Use Newton's algorithm.

 */

private static BigDecimal lnNewton(BigDecimal x, int scale)

{

    int        sp1 = scale + 1;

    BigDecimal n   = x;

    BigDecimal term;


    // Convergence tolerance = 5*(10^-(scale+1))

    BigDecimal tolerance = BigDecimal.valueOf(5)

                                        .movePointLeft(sp1);


    // Loop until the approximations converge

    // (two successive approximations are within the tolerance).

    do {


        // e^x

        BigDecimal eToX = exp(x, sp1);


        // (e^x - n)/e^x

        term = eToX.subtract(n)

                    .divide(eToX, sp1, BigDecimal.ROUND_DOWN);


        // x - (e^x - n)/e^x

        x = x.subtract(term);


        Thread.yield();

    } while (term.compareTo(tolerance) > 0);


    return x.setScale(scale, BigDecimal.ROUND_HALF_EVEN);

}


/**

 * Compute the integral root of x to a given scale, x >= 0.

 * Use Newton's algorithm.

 * @param x the value of x

 * @param index the integral root value

 * @param scale the desired scale of the result

 * @return the result value

 */

public static BigDecimal intRoot(BigDecimal x, long index,

                                 int scale)

{

    // Check that x >= 0.

    if (x.signum() < 0) {

        throw new IllegalArgumentException("x < 0");

    }


    int        sp1 = scale + 1;

    BigDecimal n   = x;

    BigDecimal i   = BigDecimal.valueOf(index);

    BigDecimal im1 = BigDecimal.valueOf(index-1);

    BigDecimal tolerance = BigDecimal.valueOf(5)

                                        .movePointLeft(sp1);

    BigDecimal xPrev;


    // The initial approximation is x/index.

    x = x.divide(i, scale, BigDecimal.ROUND_HALF_EVEN);


    // Loop until the approximations converge

    // (two successive approximations are equal after rounding).

    do {

        // x^(index-1)

        BigDecimal xToIm1 = intPower(x, index-1, sp1);


        // x^index

        BigDecimal xToI =

                x.multiply(xToIm1)

                    .setScale(sp1, BigDecimal.ROUND_HALF_EVEN);


        // n + (index-1)*(x^index)

        BigDecimal numerator =

                n.add(im1.multiply(xToI))

                    .setScale(sp1, BigDecimal.ROUND_HALF_EVEN);


        // (index*(x^(index-1))

        BigDecimal denominator =

                i.multiply(xToIm1)

                    .setScale(sp1, BigDecimal.ROUND_HALF_EVEN);


        // x = (n + (index-1)*(x^index)) / (index*(x^(index-1)))

        xPrev = x;

        x = numerator

                .divide(denominator, sp1, BigDecimal.ROUND_DOWN);


        Thread.yield();

    } while (x.subtract(xPrev).abs().compareTo(tolerance) > 0);


    return x;

}


/**

 * Compute e^x to a given scale.

 * Break x into its whole and fraction parts and

 * compute (e^(1 + fraction/whole))^whole using Taylor's formula.

 * @param x the value of x

 * @param scale the desired scale of the result

 * @return the result value

 */

public static BigDecimal exp(BigDecimal x, int scale)

{

    // e^0 = 1

    if (x.signum() == 0) {

        return BigDecimal.valueOf(1);

    }


    // If x is negative, return 1/(e^-x).

    else if (x.signum() == -1) {

        return BigDecimal.valueOf(1)

                    .divide(exp(x.negate(), scale), scale,

                            BigDecimal.ROUND_HALF_EVEN);

    }


    // Compute the whole part of x.

    BigDecimal xWhole = x.setScale(0, BigDecimal.ROUND_DOWN);


    // If there isn't a whole part, compute and return e^x.

    if (xWhole.signum() == 0) return expTaylor(x, scale);


    // Compute the fraction part of x.

    BigDecimal xFraction = x.subtract(xWhole);


    // z = 1 + fraction/whole

    BigDecimal z = BigDecimal.valueOf(1)

                        .add(xFraction.divide(

                                xWhole, scale,

                                BigDecimal.ROUND_HALF_EVEN));


    // t = e^z

    BigDecimal t = expTaylor(z, scale);


    BigDecimal maxLong = BigDecimal.valueOf(Long.MAX_VALUE);

    BigDecimal result  = BigDecimal.valueOf(1);


    // Compute and return t^whole using intPower().

    // If whole > Long.MAX_VALUE, then first compute products

    // of e^Long.MAX_VALUE.

    while (xWhole.compareTo(maxLong) >= 0) {

        result = result.multiply(

                            intPower(t, Long.MAX_VALUE, scale))

                    .setScale(scale, BigDecimal.ROUND_HALF_EVEN);

        xWhole = xWhole.subtract(maxLong);


        Thread.yield();

    }

    return result.multiply(intPower(t, xWhole.longValue(), scale))

                    .setScale(scale, BigDecimal.ROUND_HALF_EVEN);

}



查看完整回答
反对 回复 2019-12-16
?
慕田峪9158850

TA贡献1794条经验 获得超7个赞

一种关系不好的小算法,适用于大量数字log(AB) = log(A) + log(B)。以下是以10为基数的方法(您可以将其转换为任何其他对数基数):

  1. 计算答案中的小数位数。那是对数的必不可少的部分,再加上一个。例如:floor(log10(123456)) + 1是6,因为123456有6位数字。

  2. 如果您需要的只是对数的整数部分,可以在这里停止:只需从步骤1的结果中减去1。

  3. 要获得对数的小数部分,请将数字除以10^(number of digits),然后使用math.log10()(或其他方法;计算对数的对数)(如果没有其他可用方法,则使用简单的序列近似),然后将其加到整数部分。示例:要获取的小数部分log10(123456),请计算math.log10(0.123456) = -0.908...并将其添加到步骤1的结果中6 + -0.908 = 5.092,即log10(123456)。请注意,您基本上只是在大数前面加上小数点;在您的用例中可能有一种优化此方法的好方法,对于很大的数字,您甚至不必费心去抓住所有数字- log10(0.123)近似于log10(0.123456789)



查看完整回答
反对 回复 2019-12-16
?
HUWWW

TA贡献1874条经验 获得超12个赞

这是超级快的,因为:


没有 toString()

无BigInteger数学(牛顿/续分数)

甚至没有实例化一个新的 BigInteger

仅使用固定数量的非常快速的操作

一次通话大约需要20微秒(每秒大约5万次通话)


但:


仅适用于 BigInteger

解决方法BigDecimal(未测试速度):


移动小数点,直到值> 2 ^ 53

使用toBigInteger()(div内部使用)

该算法利用了可以将对数计算为指数和尾数对数之和的事实。例如:


12345有5位数字,因此以10为底的对数介于4和5之间。log(12345)= 4 + log(1.2345)= 4.09149 ...(以10为底的对数)


该函数计算以2为底的对数,因为找到占用的位数很简单。


public double log(BigInteger val)

{

    // Get the minimum number of bits necessary to hold this value.

    int n = val.bitLength();


    // Calculate the double-precision fraction of this number; as if the

    // binary point was left of the most significant '1' bit.

    // (Get the most significant 53 bits and divide by 2^53)

    long mask = 1L << 52; // mantissa is 53 bits (including hidden bit)

    long mantissa = 0;

    int j = 0;

    for (int i = 1; i < 54; i++)

    {

        j = n - i;

        if (j < 0) break;


        if (val.testBit(j)) mantissa |= mask;

        mask >>>= 1;

    }

    // Round up if next bit is 1.

    if (j > 0 && val.testBit(j - 1)) mantissa++;


    double f = mantissa / (double)(1L << 52);


    // Add the logarithm to the number of bits, and subtract 1 because the

    // number of bits is always higher than necessary for a number

    // (ie. log2(val)<n for every val).

    return (n - 1 + Math.log(f) * 1.44269504088896340735992468100189213742664595415298D);

    // Magic number converts from base e to base 2 before adding. For other

    // bases, correct the result, NOT this number!

}



查看完整回答
反对 回复 2019-12-16
  • 3 回答
  • 0 关注
  • 342 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信