BigDecimal的对数

我怎样才能计算一个BigDecimal的对数? 有谁知道我可以使用的任何algorithm?

到目前为止,我的谷歌search已经提出了(无用)的想法,只是转换为双重使用Math.log。

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

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

Java Number Cruncher:Java程序员数值计算指南提供了使用牛顿法的解决scheme。 这本书的源代码可以在这里find 。 以下是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); } 

一个很好用于大数的哈克小algorithm使用关系log(AB) = log(A) + log(B) 。 这里是如何做到这一点在10(你可以trivially转换为任何其他对数基地):

  1. 计算答案中的小数位数。 这是你的对数的组成部分, 加上一个 。 例如: floor(log10(123456)) + 1是6,因为123456有6个数字。

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

  3. 为了得到对数的小数部分,将数字除以10^(number of digits) ,然后使用math.log10() (或其他;如果没有其他可用的话,使用一个简单的系列近似值math.log10()来计算它的日志,以及将其添加到整数部分。 例如:得到log10(123456)的小数部分,计算math.log10(0.123456) = -0.908... ,并将其加到步骤1的结果: 6 + -0.908 = 5.092 ,即log10(123456) 。 请注意,你基本上只是在大数的前面加上一个小数点; 在你的用例中可能有一个很好的方法来优化它,而对于真正的大数字,你甚至不需要费心去抓取所有的数字 – log10(0.123)log10(0.123456789)一个很好的近似值。

这是我所想到的:

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

这个是超快的,因为:

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

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

但:

  • 只适用于BigInteger

BigDecimal解决方法(未testing速度):

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

该algorithm利用日志可以计算为指数和尾数日志之和的事实。 例如:

日志(12345)= 4 +日志(1.2345)= 4.09149 …(基本10日志)


该函数计算基本2日志,因为find占用的位数是微不足道的。

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

你可以使用它来分解它

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

基本上b+1将成为数字中的位数, a将是0到1之间的一个值,您可以使用常规doublealgorithm计算其对数。

或者可以使用math技巧 – 例如,数字接近1的对数可以通过一系列扩展来计算

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

根据你想要采取什么样的数字取对数,可能有这样的东西,你可以使用。

编辑 :以10为底的对数,你可以将自然对数除以ln(10) ,或类似的任何其他基数。

Meower68伪代码的Java实现,我用几个数字testing:

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

伪码algorithm做对数。

假设我们需要x的log_n

 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; 

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

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

如果input到1,我们通过。 1的日志,对于任何基地,是0,这意味着我们不需要再添加。

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

很显然,如果你正在使用一个任意多位精度的系统,那么你会想在其中join一些其他的东西来限制循环。

如果你所需要的是find你可以使用的数字10的权力:

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

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

码:

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

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

 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方法)是从我的这个其他答案复制的。