Pregunta

¿Cómo puedo calcular el logaritmo de un BigDecimal? ¿Alguien sabe de cualquier algoritmo que pueda usar?

Mi google ha llegado tan lejos con la idea (inútil) de simplemente convertir a un doble y utilizando Math.log.

Voy a ofrecer la precisión de la respuesta requerida.

editar: cualquier base va a hacer. Si es más fácil en la base de x, lo haré.

¿Fue útil?

Solución

Número Java Cruncher: La Guía del programador de Java para Computing Numerical proporciona una solución usando el método de Newton. El código fuente del libro está disponible aquí . El siguiente ha sido tomado del capítulo 12.5 Funciones de Big Decmial (P330 y 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);
}

Otros consejos

Un poco hacky algoritmo que funciona muy bien para un gran número utiliza el log(AB) = log(A) + log(B) relación. He aquí cómo lo hacen en base 10 (que se puede convertir trivialmente a cualquier otra base de logaritmo):

  1. Contar el número de dígitos decimales de la respuesta. Esa es la parte integral de su logaritmo, más uno . Ejemplo: floor(log10(123456)) + 1 es 6, desde 123.456 tiene 6 dígitos

  2. .
  3. Puede detener aquí si todo lo que necesita es la parte entera del logaritmo:. Simplemente restar 1 del resultado del paso 1

  4. Para obtener la parte fraccionaria del logaritmo, se divide el número por 10^(number of digits), a continuación, calcular el logaritmo de que el uso de math.log10() (o lo que sea, el uso de un simple aproximación serie si nada más está disponible), y añadirlo a la parte entera. Ejemplo: para obtener la parte fraccionaria de log10(123456), math.log10(0.123456) = -0.908... cálculo, y añadirlo al resultado del paso 1: 6 + -0.908 = 5.092, que es log10(123456). Tenga en cuenta que usted está básicamente sólo viradas en un punto decimal en la parte delantera de la gran cantidad; es probable que haya una buena manera de optimizar este en su caso de uso, y para números muy grandes que ni siquiera necesita molestarse con el acaparamiento de todos los dígitos -. log10(0.123) es una gran aproximación a log10(0.123456789)

Éste es muy rápido, ya que:

  • No se toString()
  • No BigInteger matemáticas (/ fracción continua de Newton)
  • Ni siquiera una instancia de un nuevo BigInteger
  • Sólo utiliza un número fijo de operaciones muy rápidas

Una llamada tarda unos 20 microsegundos (unos 50 mil llamadas por segundo)

Pero:

  • Sólo funciona para BigInteger

Solución para BigDecimal (no probado para la velocidad):

  • desplazar el punto decimal hasta que el valor es> 2 ^ 53
  • Uso toBigInteger() (utiliza uno div internamente)

Este algoritmo hace uso del hecho de que el registro se puede calcular como la suma del exponente y el registro de la mantisa. por ejemplo:

12345 tiene 5 dígitos, por lo que el registro de base 10 es de entre 4 y 5. log (12.345) = 4 + log (1,2345) = 4,09149 ... (base 10 log)


Esta función calcula la base 2 log porque encontrar el número de bits ocupados es trivial.

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

Se puede descomponer utilizando

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

Básicamente b+1 va a ser el número de dígitos en el número y a será un valor entre 0 y 1, que se podía calcular el logaritmo de la aritmética utilizando double regular.

O hay trucos matemáticos se pueden utilizar - por ejemplo, logaritmos de los números cercanos a 1 puede ser calculado por un desarrollo en serie

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

Dependiendo de qué tipo de número que está tratando de tomar el logaritmo de, puede haber algo como esto se puede utilizar.

editar . Para obtener el logaritmo en base 10, se puede dividir el logaritmo natural por ln(10), o de manera similar para cualquier otra base

Esto es lo que he llegado con:

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

Una implementación Java de Meower68 pseudcode que he probado con algunos números:

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

Si todo lo que necesita es dar con la fuerza de 10 en el número puede utilizar:

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

algoritmo Pseudocódigo para hacer un logaritmo.

Suponiendo que queremos log_n de x

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

El gran bucle while puede parecer un poco confuso.

En cada pase, puede cuadrar su entrada o puede tomar la raíz cuadrada de su base; De cualquier manera, debe dividir su fracción por 2. Me parece cuadrar la entrada, y dejando la base solo, para ser más exactos.

Si la entrada se pone a 1, estamos a través. El registro de 1, para cualquier base, es 0, lo que significa que no es necesario añadir nada más.

Si (resultado + fracción) es no mayor de resultado, entonces hemos alcanzado los límites de precisión para nuestro sistema de numeración. Podemos parar.

Obviamente, si se trabaja con un sistema que tiene arbitrariamente muchos dígitos de precisión, tendrá que poner algo más ahí para limitar el bucle.

Estaba buscando esta cosa exacta y, finalmente, fui con un enfoque fracción continua. La fracción continua se puede encontrar en herp aquí o aquí

Código:

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

vieja pregunta, pero en realidad que esta respuesta es preferible. Tiene buena precisión y apoya los argumentos de prácticamente cualquier tamaño.

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

La lógica de la base (método logBigInteger) se copia de este otro respuesta de mina .

He creado una función para BigInteger pero se puede modificar fácilmente para BigDecimal. Descomponer el registro y el uso de algunas propiedades del registro es lo que hago pero me da sólo el doble precisión. Pero funciona para cualquier base. :)

public double BigIntLog(BigInteger bi, double base) {
    // Convert the BigInteger to BigDecimal
    BigDecimal bd = new BigDecimal(bi);
    // Calculate the exponent 10^exp
    BigDecimal diviser = new BigDecimal(10);
    diviser = diviser.pow(bi.toString().length()-1);
    // Convert the BigDecimal from Integer to a decimal value
    bd = bd.divide(diviser);
    // Convert the BigDecimal to double
    double bd_dbl = bd.doubleValue();
    // return the log value
    return (Math.log10(bd_dbl)+bi.toString().length()-1)/Math.log10(base);
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top