Java BigDecimal 的对数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/739532/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 18:55:22  来源:igfitidea点击:

Logarithm of a BigDecimal

javabigdecimallogarithm

提问by masher

How can I calculate the logarithm of a BigDecimal? Does anyone know of any algorithms I can use?

如何计算 BigDecimal 的对数?有谁知道我可以使用的任何算法?

My googling so far has come up with the (useless) idea of just converting to a double and using Math.log.

到目前为止,我的谷歌搜索已经提出了(无用的)想法,即转换为双精度并使用 Math.log。

I will provide the precision of the answer required.

我将提供所需答案的准确性。

edit: any base will do. If it's easier in base x, I'll do that.

编辑:任何基地都可以。如果在基数 x 中更容易,我会这样做。

采纳答案by Peter

Java Number Cruncher: The Java Programmer's Guide to Numerical Computingprovides a solution using Newton's Method. Source code from the book is available here. The following has been taken from chapter 12.5 Big Decimal Functions(p330 & p331):

Java Number Cruncher: The Java Programmer's Guide to Numerical Computing提供了一个使用牛顿法的解决方案。本书的源代码可在此处获得。以下内容摘自第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);
}

回答by kquinn

A hacky little algorithm that works great for large numbers uses the relation log(AB) = log(A) + log(B). Here's how to do it in base 10 (which you can trivially convert to any other logarithm base):

一个非常适合大数字的hacky小算法使用了relation log(AB) = log(A) + log(B)。这是以 10 为底的方法(您可以轻松地将其转换为任何其他对数底):

  1. Count the number of decimal digits in the answer. That's the integral part of your logarithm, plus one. Example: floor(log10(123456)) + 1is 6, since 123456 has 6 digits.

  2. You can stop here if all you need is the integer part of the logarithm: just subtract 1 from the result of step 1.

  3. To get the fractional part of the logarithm, divide the number by 10^(number of digits), then compute the log of that using math.log10()(or whatever; use a simple series approximation if nothing else is available), and add it to the integer part. Example: to get the fractional part of log10(123456), compute math.log10(0.123456) = -0.908..., and add it to the result of step 1: 6 + -0.908 = 5.092, which is log10(123456). Note that you're basically just tacking on a decimal point to the front of the large number; there is probably a nice way to optimize this in your use case, and for really big numbers you don't even need to bother with grabbing all of the digits -- log10(0.123)is a great approximation to log10(0.123456789).

  1. 计算答案中的小数位数。这是对数的组成部分,加上 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).

回答by David Z

You could decompose it using

你可以分解它使用

log(a * 10^b) = log(a) + b * log(10)

Basically b+1is going to be the number of digits in the number, and awill be a value between 0 and 1 which you could compute the logarithm of by using regular doublearithmetic.

基本上b+1将是数字中的位数,并且a是一个介于 0 和 1 之间的值,您可以使用常规double算术计算其对数。

Or there are mathematical tricks you can use - for instance, logarithms of numbers close to 1 can be computed by a series expansion

或者您可以使用一些数学技巧 - 例如,可以通过级数展开计算接近 1 的数字的对数

ln(x + 1) = x - x^2/2 + x^3/3 - x^4/4 + ...

Depending on what kind of number you're trying to take the logarithm of, there may be something like this you can use.

根据您尝试取对数的类型,您可以使用类似的东西。

EDIT: To get the logarithm in base 10, you can divide the natural logarithm by ln(10), or similarly for any other base.

编辑:要获得以 10 为底的对数,您可以将自然对数除以ln(10),或类似地用于任何其他底数。

回答by masher

This is what I've come up with:

这是我想出的:

//http://everything2.com/index.pl?node_id=946812        
public BigDecimal log10(BigDecimal b, int dp)
{
    final int NUM_OF_DIGITS = dp+2; // need to add one to get the right number of dp
                                    //  and then add one again to get the next number
                                    //  so I can round it correctly.

    MathContext mc = new MathContext(NUM_OF_DIGITS, RoundingMode.HALF_EVEN);

    //special conditions:
    // log(-x) -> exception
    // log(1) == 0 exactly;
    // log of a number lessthan one = -log(1/x)
    if(b.signum() <= 0)
        throw new ArithmeticException("log of a negative number! (or zero)");
    else if(b.compareTo(BigDecimal.ONE) == 0)
        return BigDecimal.ZERO;
    else if(b.compareTo(BigDecimal.ONE) < 0)
        return (log10((BigDecimal.ONE).divide(b,mc),dp)).negate();

    StringBuffer sb = new StringBuffer();
    //number of digits on the left of the decimal point
    int leftDigits = b.precision() - b.scale();

    //so, the first digits of the log10 are:
    sb.append(leftDigits - 1).append(".");

    //this is the algorithm outlined in the webpage
    int n = 0;
    while(n < NUM_OF_DIGITS)
    {
        b = (b.movePointLeft(leftDigits - 1)).pow(10, mc);
        leftDigits = b.precision() - b.scale();
        sb.append(leftDigits - 1);
        n++;
    }

    BigDecimal ans = new BigDecimal(sb.toString());

    //Round the number to the correct number of decimal places.
    ans = ans.round(new MathContext(ans.precision() - ans.scale() + dp, RoundingMode.HALF_EVEN));
    return ans;
}

回答by Meower68

Pseudocode algorithm for doing a logarithm.

做对数的伪代码算法。

Assuming we want log_n of x

假设我们想要 x 的 log_n

double fraction, input;
int base;
double result;

result = 0;
base = n;
input = x;

while (input > base){
  result++;
  input /= base;
}
fraction = 1/2;
input *= input;   

while (((result + fraction) > result) && (input > 1)){
  if (input > base){
    input /= base;
    result += fraction;
  }
  input *= input;
  fraction /= 2.0;
 }

The big while loop may seem a bit confusing.

大 while 循环可能看起来有点混乱。

On each pass, you can either square your input or you can take the square root of your base; either way, you must divide your fraction by 2. I find squaring the input, and leaving the base alone, to be more accurate.

在每次通过时,您可以对输入进行平方,也可以对基数取平方根;无论哪种方式,您都必须将您的分数除以 2。我发现对输入进行平方,并保留基数,这样会更准确。

If the input goes to 1, we're through. The log of 1, for any base, is 0, which means we don't need to add any more.

如果输入变为 1,我们就结束了。对于任何基数,1 的对数都是 0,这意味着我们不需要再添加了。

if (result + fraction) is not greater than result, then we've hit the limits of precision for our numbering system. We can stop.

如果(结果 + 分数)不大于结果,那么我们已经达到了编号系统的精度限制。我们可以停下来。

Obviously, if you're working with a system which has arbitrarily many digits of precision, you will want to put something else in there to limit the loop.

显然,如果您正在使用具有任意多位数精度的系统,您将需要在其中放置其他东西来限制循环。

回答by Andy Turner

A Java implementation of Meower68 pseudcode which I tested with a few numbers:

Meower68 伪代码的 Java 实现,我用几个数字进行了测试:

public static BigDecimal log(int base_int, BigDecimal x) {
        BigDecimal result = BigDecimal.ZERO;

        BigDecimal input = new BigDecimal(x.toString());
        int decimalPlaces = 100;
        int scale = input.precision() + decimalPlaces;

        int maxite = 10000;
        int ite = 0;
        BigDecimal maxError_BigDecimal = new BigDecimal(BigInteger.ONE,decimalPlaces + 1);
        System.out.println("maxError_BigDecimal " + maxError_BigDecimal);
        System.out.println("scale " + scale);

        RoundingMode a_RoundingMode = RoundingMode.UP;

        BigDecimal two_BigDecimal = new BigDecimal("2");
        BigDecimal base_BigDecimal = new BigDecimal(base_int);

        while (input.compareTo(base_BigDecimal) == 1) {
            result = result.add(BigDecimal.ONE);
            input = input.divide(base_BigDecimal, scale, a_RoundingMode);
        }

        BigDecimal fraction = new BigDecimal("0.5");
        input = input.multiply(input);
        BigDecimal resultplusfraction = result.add(fraction);
        while (((resultplusfraction).compareTo(result) == 1)
                && (input.compareTo(BigDecimal.ONE) == 1)) {
            if (input.compareTo(base_BigDecimal) == 1) {
                input = input.divide(base_BigDecimal, scale, a_RoundingMode);
                result = result.add(fraction);
            }
            input = input.multiply(input);
            fraction = fraction.divide(two_BigDecimal, scale, a_RoundingMode);
            resultplusfraction = result.add(fraction);
            if (fraction.abs().compareTo(maxError_BigDecimal) == -1){
                break;
            }
            if (maxite == ite){
                break;
            }
            ite ++;
        }

        MathContext a_MathContext = new MathContext(((decimalPlaces - 1) + (result.precision() - result.scale())),RoundingMode.HALF_UP);
        BigDecimal roundedResult = result.round(a_MathContext);
        BigDecimal strippedRoundedResult = roundedResult.stripTrailingZeros();
        //return result;
        //return result.round(a_MathContext);
        return strippedRoundedResult;
    }

回答by Carl Pritchett

If all you need is to find the powers of 10 in the number you can use:

如果您只需要在可以使用的数字中找到 10 的幂:

public int calculatePowersOf10(BigDecimal value)
{
    return value.round(new MathContext(1)).scale() * -1;
}

回答by deathly809

I was searching for this exact thing and eventually went with a continued fraction approach. The continued fraction can be found at hereor here

我正在寻找这个确切的东西,并最终采用连分数方法。连分数可以在此处此处找到

Code:

代码:

import java.math.BigDecimal;
import java.math.MathContext;

public static long ITER = 1000;
public static MathContext context = new MathContext( 100 );
public static BigDecimal ln(BigDecimal x) {
    if (x.equals(BigDecimal.ONE)) {
        return BigDecimal.ZERO;
    }

    x = x.subtract(BigDecimal.ONE);
    BigDecimal ret = new BigDecimal(ITER + 1);
    for (long i = ITER; i >= 0; i--) {
    BigDecimal N = new BigDecimal(i / 2 + 1).pow(2);
        N = N.multiply(x, context);
        ret = N.divide(ret, context);

        N = new BigDecimal(i + 1);
        ret = ret.add(N, context);

    }

    ret = x.divide(ret, context);
    return ret;
}

回答by Mark Jeronimus

This one is super fast, because:

这个速度非常快,因为:

  • No toString()
  • No BigIntegermath (Newton's/Continued fraction)
  • Not even instantiating a new BigInteger
  • Only uses a fixed number of very fast operations
  • toString()
  • 没有BigInteger数学(牛顿/连续分数)
  • 甚至没有实例化一个新的 BigInteger
  • 只使用固定数量的非常快速的操作

One call takes about 20 microseconds (about 50k calls per second)

一个调用大约需要 20 微秒(每秒大约 50k 个调用)

But:

但:

  • Only works for BigInteger
  • 只适用于 BigInteger

Workaround for BigDecimal(not tested for speed):

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

  • Shift the decimal point until the value is > 2^53
  • Use toBigInteger()(uses one divinternally)
  • 移动小数点直到值 > 2^53
  • 使用toBigInteger()div内部使用一个)


This algorithm makes use of the fact that the log can be calculated as the sum of the exponent and the log of the mantissa. eg:

该算法利用了这样一个事实,即可以将对数计算为指数和尾数对数的总和。例如:

12345 has 5 digits, so the base 10 log is between 4 and 5. log(12345) = 4 + log(1.2345) = 4.09149... (base 10 log)

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



This function calculates base 2 log because finding the number of occupied bits is trivial.

此函数计算基数为 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!
}

回答by leonbloy

Old question, but I actually think this answer is preferable. It has good precision and supports arguments of practically any size.

老问题,但我实际上认为这个答案更可取。它具有良好的精度并支持几乎任何大小的参数。

private static final double LOG10 = Math.log(10.0);

/**
 * Computes the natural logarithm of a BigDecimal 
 * 
 * @param val Argument: a positive BigDecimal
 * @return Natural logarithm, as in Math.log()
 */
public static double logBigDecimal(BigDecimal val) {
    return logBigInteger(val.unscaledValue()) + val.scale() * Math.log(10.0);
}

private static final double LOG2 = Math.log(2.0);

/**
 * Computes the natural logarithm of a BigInteger. Works for really big
 * integers (practically unlimited)
 * 
 * @param val Argument, positive integer
 * @return Natural logarithm, as in <tt>Math.log()</tt>
 */
public static double logBigInteger(BigInteger val) {
    int blex = val.bitLength() - 1022; // any value in 60..1023 is ok
    if (blex > 0)
        val = val.shiftRight(blex);
    double res = Math.log(val.doubleValue());
    return blex > 0 ? res + blex * LOG2 : res;
}

The core logic (logBigIntegermethod) is copied from this other answerof mine.

核心逻辑(logBigInteger方法)是从我的另一个答案中复制的。