web-dev-qa-db-fra.com

Logarithme d'un BigDecimal

Comment puis-je calculer le logarithme d'un BigDecimal? Est-ce que quelqu'un connaît des algorithmes que je peux utiliser?

Ma recherche sur Google a eu l’idée (inutile) de simplement convertir en double et d’utiliser Math.log.

Je vais fournir la précision de la réponse requise.

edit: n'importe quelle base fera l'affaire. Si c'est plus facile en base x, je le ferai.

40
masher

Java Number Cruncher: Le guide du programmeur Java pour l'informatique numérique fournit une solution utilisant la méthode de Newton . Le code source du livre est disponible ici . Ce qui suit est tiré du chapitre 12.5 Grandes fonctions numériques (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);
}
19
Peter

Un petit algorithme qui fonctionne très bien pour les grands nombres utilise la relation log(AB) = log(A) + log(B). Voici comment procéder en base 10 (que vous pouvez facilement convertir en une autre base de logarithme):

  1. Comptez le nombre de chiffres décimaux dans la réponse. C'est la partie intégrante de votre logarithme, plus un . Exemple: floor(log10(123456)) + 1 est 6, puisque 123456 a 6 chiffres.

  2. Vous pouvez vous arrêter ici si tout ce dont vous avez besoin est la partie entière du logarithme: soustrayez simplement 1 du résultat de l'étape 1.

  3. Pour obtenir la partie fractionnaire du logarithme, divisez le nombre par 10^(number of digits), puis calculez le journal à l'aide de math.log10() (ou peu importe; utilisez une approximation de série simple si rien d'autre n'est disponible) et ajoutez-le à la partie entière. Exemple: pour obtenir la partie décimale de log10(123456), calculez math.log10(0.123456) = -0.908... et ajoutez-la au résultat de l'étape 1: 6 + -0.908 = 5.092, qui est log10(123456). Notez que vous vous en tenez simplement à une virgule décimale devant le grand nombre; il existe probablement un bon moyen d'optimiser cela dans votre cas d'utilisation, et pour les très gros nombres, vous n'avez même pas besoin de vous préoccuper de saisir tous les chiffres - log10(0.123) est une excellente approximation de log10(0.123456789).

8
kquinn

Celui-ci est super rapide, car:

  • Non toString()
  • Non BigInteger math (fraction de Newton/suite)
  • Ne pas même instancier une nouvelle BigInteger
  • Utilise seulement un nombre fixe d'opérations très rapides

Un appel prend environ 20 microsecondes (environ 50 000 appels par seconde)

Mais:

  • Ne fonctionne que pour BigInteger

Solution de contournement pour BigDecimal (vitesse non testée):

  • Décalez le point décimal jusqu'à ce que la valeur soit> 2 ^ 53
  • Use toBigInteger() (utilise une div en interne)

Cet algorithme utilise le fait que le journal peut être calculé comme la somme de l'exposant et du journal de la mantisse. par exemple:

12345 a 5 chiffres, le log de base 10 est donc compris entre 4 et 5. Log (12345) = 4 + log (1.2345) = 4.09149 ... (log de base 10)


Cette fonction calcule le journal de base 2 car la recherche du nombre de bits occupés est triviale.

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!
}
5
Mark Jeronimus

C'est ce que je suis venu avec:

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

Vous pouvez le décomposer en utilisant

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

Fondamentalement, b+1 sera le nombre de chiffres du nombre et a sera une valeur comprise entre 0 et 1 dont vous pourriez calculer le logarithme en utilisant l'arithmétique régulière double.

Vous pouvez également utiliser des astuces mathématiques - par exemple, les logarithmes de nombres proches de 1 peuvent être calculés par un développement en série

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

Selon le type de nombre dont vous essayez de tenir le logarithme, vous pouvez utiliser ce type d’information.

EDIT: Pour obtenir le logarithme en base 10, vous pouvez diviser le logarithme népérien par ln(10) ou de la même manière pour toute autre base.

4
David Z

Une implémentation Java du pseudo Meower68 que j'ai testée avec quelques chiffres:

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;
    }
3
Andy Turner

Si tout ce dont vous avez besoin est de trouver les puissances de 10 dans le nombre que vous pouvez utiliser:

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

Pseudocode pour faire un logarithme.

En supposant que nous voulons log_n de 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;

La grosse boucle while peut sembler un peu déroutante. 

À chaque passage, vous pouvez aligner votre saisie ou prendre la racine carrée de votre base; Dans tous les cas, vous devez diviser votre fraction par 2. Je trouve que la quadrature de l'entrée et que la base est isolée pour être plus précis.

Si l'entrée passe à 1, nous y sommes. Le log de 1, quelle que soit la base, est 0, ce qui signifie que nous n'avons pas besoin d'en ajouter plus.

si (résultat + fraction) n'est pas supérieur au résultat, nous avons atteint les limites de précision de notre système de numérotation. Nous pouvons arrêter.

Évidemment, si vous travaillez avec un système qui a arbitrairement plusieurs chiffres de précision, vous voudrez ajouter quelque chose pour limiter la boucle.

2
Meower68

Vieille question, mais je pense en fait que cette réponse est préférable. Il a une bonne précision et supporte des arguments de pratiquement n'importe quelle taille.

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 logique de base (méthode logBigInteger) est copiée à partir de cette autre réponse of mine.

1
leonbloy

Je cherchais cette chose exacte et suis finalement allé avec une approche de fraction continue. La fraction continue peut être trouvée à ici ou ici

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

J'ai créé une fonction pour BigInteger mais elle peut être facilement modifiée pour BigDecimal. Décomposer le journal et utiliser certaines propriétés du journal est ce que je fais mais je n’obtiens qu’une double précision. Mais cela fonctionne pour n'importe quelle 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);
}
0
Latif