Logarithm of a Bigdecimal

Logarithm of a BigDecimal

Java Number Cruncher: The Java Programmer's Guide to Numerical Computing provides 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):

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

Logarithm Algorithm

Use this identity:

logb(n) = loge(n) / loge(b)

Where log can be a logarithm function in any base, n is the number and b is the base. For example, in Java this will find the base-2 logarithm of 256:

Math.log(256) / Math.log(2)
=> 8.0

Math.log() uses base e, by the way. And there's also Math.log10(), which uses base 10.

Aproximation of log(10^k)

Use the fact that

log(10^k) = k*log(10)

So:

System.out.println(10000 * Math.log(10));

Prints:

23025.850929940458

Logarithm of a BigInt

In case you don't want to return a BigInt, then the following might work for you too:

function log10(bigint) {
if (bigint < 0) return NaN;
const s = bigint.toString(10);

return s.length + Math.log10("0." + s.substring(0, 15))
}

function log(bigint) {
return log10(bigint) * Math.log(10);
}

function natlog(bigint) {
if (bigint < 0) return NaN;

const s = bigint.toString(16);
const s15 = s.substring(0, 15);

return Math.log(16) * (s.length - s15.length) + Math.log("0x" + s15);
}

const largeNumber = BigInt('9039845039485903949384755723427863486200719925474009384509283489374539477777093824750398247503894750384750238947502389475029384755555555555555555555555555555555555555554444444444444444444444444222222222222222222222255666666666666938475938475938475938408932475023847502384750923847502389475023987450238947509238475092384750923847502389457028394750293847509384570238497575938475938475938475938475555555555559843991');

console.log(natlog(largeNumber)); // 948.5641152531601
console.log(log10(largeNumber), log(largeNumber), log(-1))
// 411.95616098588766
// 948.5641152531603
// NaN

Logarithm-based solution calculates incorrect number of digits in large integers

The problem is that 99999999999999999 cannot be exactly represented as a (double precision) floating-point value in this case. The nearest value is 1.0E+17 when passed as a double parameter to log10.

The same would be true of the log10(n) value: 16.999999999999999995657... - the nearest value that can be represented is 17.

How to do a fractional power on BigDecimal in Java?

The solution for arguments under 1.7976931348623157E308 (Double.MAX_VALUE) but supporting results with MILLIONS of digits:

Since double supports numbers up to MAX_VALUE (for example, 100! in double looks like this: 9.332621544394415E157), there is no problem to use BigDecimal.doubleValue(). But you shouldn't just do Math.pow(double, double) because if the result is bigger than MAX_VALUE you will just get infinity. SO: use the formula X^(A+B)=X^A*X^B to separate the calculation to TWO powers, the big, using BigDecimal.pow, and the small (remainder of the 2nd argument), using Math.pow, then multiply. X will be copied to DOUBLE - make sure it's not bigger than MAX_VALUE, A will be INT (maximum 2147483647 but the BigDecimal.pow doesn't support integers more than a billion anyway), and B will be double, always less than 1. This way you can do the following (ignore my private constants etc):

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

Results examples:

50!^10! = 12.50911317862076252364259*10^233996181

50!^0.06 = 7395.788659356498101260513


Related Topics



Leave a reply



Submit