2009-04-11 75 views
35

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

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

我会提供所需答案的精确度。

编辑:任何基地都会做。如果在x基础上更容易,我会这样做。

+0

以什么为底的对数? 2,10,e? – paxdiablo 2009-04-11 05:06:56

+2

任何基地。一旦我有一个实现基地之间的转换是微不足道的 – masher 2009-04-11 07:01:21

+0

我已经给出了解决方案 http://stackoverflow.com/questions/11848887/bigdecimal-to-the-power-of-bigdecimal-on-java-android/ 22556217#22556217 – 2014-03-21 10:35:46

回答

19

Java Number Cruncher: The Java Programmer's Guide to Numerical Computing提供使用Newton's Method的解决方案。源自该书的源代码可用here。以下内容摘自12。5个大Decmial功能(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); 
} 
8

对于大数字很有用的一个小巧的算法使用关系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)非常接近。

+1

+1,但是你的地下城主人还在和你说话吗? – ojblass 2009-04-11 07:01:03

4

你可以使用

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

基本上b+1将是数字的数数分解它,a将是0和1之间的值,你可以计算的对数通过使用常规的double算术。

或者有数学技巧,你可以使用 - 例如,靠近1号的对数可以通过一系列扩张

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

根据你试图采取对数什么样的数量来计算的,可能有这样的东西,你可以使用。

编辑:为了获得基础10对数,您可以通过ln(10)划分的自然对数,或类似的任何其他基地。

4

这是我想出来的:

//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; 
} 
2

做一个对数伪代码算法。

假设我们希望X

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; 

大的log_n while循环可能会显得有点混乱。

在每次通过时,您可以对输入进行平方,也可以取基数的平方根;无论哪种方式,你必须将你的分数除以2.我发现平方输入,并离开基地,更准确。

如果输入变为1,我们就通过了。对于任何基数,1的记录为0,这意味着我们不需要再添加任何基数。

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

很明显,如果你正在使用一个任意多位精度的系统,那么你需要在其中加入其他的东西来限制循环。

3

Java实现Meower68 pseudcode的,我用了几个数字测试:

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; 
    } 
1

如果你需要的是找到的10个数的权力,你可以使用:

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

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

代码中找到:

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; 
} 
4

这一个是超级快,这是因为:

  • 没有toString()
  • 没有BigInteger数学(牛顿/续分数)
  • 甚至没有实例化新的BigInteger
  • 只使用一个固定数量非常快的操作

一个呼叫花费(大约每秒50K的呼叫)

大约20微秒但是:

  • 仅适用于BigInteger

解决方法对于BigDecimal(未针对速度进行测试):

  • 移位小数点直至值为> 2^53
  • 使用toBigInteger()(内部使用一个div

该算法利用了该日志可以计算的事实作为尾数的指数和日志的总和。例如:

12345有5个数字,所以基座10的日志是4和5 日志(12345)= 4 +日志(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! 
} 
1

老问题,但我实际上认为这个答案是可取的。它具有很好的精确度并支持几乎任何规模的论点。

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

核心逻辑(logBigInteger方法)从矿井的this other answer复制。