Frage

Wie kann ich den Logarithmus einer BigDecimal berechnen? Kennt jemand von irgendwelchen Algorithmen kann ich verwenden?

Meine Googeln hat sich mit dem (nutzlosen) Idee nur das Umwandeln in einen doppelten und mit Math.log so weit kommen.

Ich werde die Genauigkeit der Antwort liefert erforderlich.

edit: jede Base tun wird. Wenn es in der Basis x einfacher ist, werde ich das tun.

War es hilfreich?

Lösung

Java Number Cruncher: das Handbuch für Java-Programmierer zu Numerical Computing eine Lösung Newton-Verfahren . Der Quellcode aus dem Buch ist verfügbar hier . Folgendes wurde von Kapitel genommen 12.5 Big Decmial Funktionen (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);
}

Andere Tipps

Ein Hacky wenig Algorithmus, der für eine große Anzahl verwendet die Beziehung log(AB) = log(A) + log(B) große Werke. Hier ist, wie es zu tun in der Basis 10 (die man trivialerweise auf andere Logarithmusbasis umwandeln kann):

  1. Die Anzahl der Dezimalstellen in der Antwort. Das ist der integrale Bestandteil Ihres Logarithmus, und ein . Beispiel: floor(log10(123456)) + 1 6, da 123456 6 Stellen hat

  2. .
  3. Sie können hier aufhören, wenn alles, was Sie der ganzzahligen Teil des Logarithmus benötigen. Nur 1 subtrahiert aus dem Ergebnis von Schritt 1

  4. Um den Bruchteil des Logarithmus zu erhalten, die Anzahl von 10^(number of digits) teilen, dann berechnen Sie das Protokoll dieser mit math.log10() (oder was auch immer, eine einfache Reihennäherung zu verwenden, wenn nichts anderes verfügbar ist), und fügen Sie ihn in die ganzzahligen Teil. Beispiel: den Bruchteil von log10(123456) zu erhalten, berechnen math.log10(0.123456) = -0.908..., und fügen Sie das Ergebnis von Schritt 1: 6 + -0.908 = 5.092, die log10(123456) ist. Beachten Sie, dass Sie im Grunde nur auf einem Dezimalpunkt Anheften an die Vorderseite der großen Zahl; gibt es wahrscheinlich eine schöne Möglichkeit, dies in Ihrem Anwendungsfall zu optimieren und für wirklich große Zahlen, die Sie noch nicht einmal mit ihnen ziehen alle Ziffern zu kümmern brauchen -. log10(0.123) ist eine große Annäherung an log10(0.123456789)

Dies ist super schnell, denn:

  • Keine toString()
  • Keine BigInteger Mathematik (Newtons / Kettenbruch)
  • Nicht einmal ein neues BigInteger instanziieren
  • verwendet nur eine feste Anzahl von sehr schnellen Operationen

Ein Anruf dauert etwa 20 Mikrosekunden (ca. 50k Anrufe pro Sekunde)

Aber:

  • Dies funktioniert nur für BigInteger

Behelfslösung für BigDecimal (nicht auf Geschwindigkeit getestet):

  • Verschieben des Dezimalpunktes, bis der Wert> 2 ^ 53
  • Verwenden Sie toBigInteger() (verwendet ein div intern)

Dieser Algorithmus nutzt die Tatsache, dass das Protokoll als die Summe des Exponenten und das Protokoll der Mantisse berechnet werden kann. zB:

12345 hat 5 Stellen, so dass die Basis 10 log zwischen 4 und 5. log (12345) = 4 + log (1,2345) = 4,09149 ... (Basis 10 log)


Diese Funktion berechnet Base 2 log, weil der Suche nach der Anzahl der belegten Bits trivial ist.

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

Sie könnten zersetzen sie mit

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

Im Grunde b+1 wird die Anzahl der Stellen in der Zahl sein, und a wird ein Wert zwischen 0 und 1 liegt, die Sie den Logarithmus mit dem normalen double Arithmetik berechnen könnten.

Oder gibt es mathematische Tricks können Sie verwenden - zum Beispiel, Logarithmen von Zahlen nahe 1 kann durch eine Reihenentwicklung berechnet werden

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

Je nachdem, welche Art von Zahlen Sie versuchen, den Logarithmus, es zu nehmen so etwas wie dieses können Sie verwenden können.

EDIT . Um den Logarithmus in der Basis 10 zu erhalten, können Sie den natürlichen Logarithmus von ln(10) teilen, oder in ähnlicher Weise für jede andere Basis

Das ist, was ich habe kommen mit:

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

Eine Java-Implementierung von Meower68 pseudcode, die ich mit ein paar Zahlen getestet:

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

Wenn alles, was Sie brauchen, ist die Befugnisse von 10 in der Zahl finden Sie verwenden können:

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

Pseudocode-Algorithmus einen Logarithmus zu tun.

Unter der Annahme, wir log_n von x wollen

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

Die große while-Schleife ein wenig verwirrend erscheinen mag.

Auf jedem Durchlauf können Sie entweder Ihre Eingabe quadratisch oder Sie können die Quadratwurzel Ihrer Basis nehmen; So oder so, Sie müssen Ihre Fraktion durch 2. teile ich den Eingang finden quadriert und Verlassen der Basis allein, um genauer zu sein.

Wenn der Eingang auf 1 geht, sind wir durch. Das Protokoll von 1, für jede Base, ist 0, was bedeutet, dass wir nicht hinzufügen müssen mehr.

if (Ergebnis + Fraktion) nicht größer ist als Ergebnis, dann haben wir die Grenzen der Präzision für unser Nummerierungssystem getroffen. Wir halten können.

Selbstverständlich, wenn Sie mit einem System arbeiten, die beliebig viele Stellen hinter dem Komma hat, werden Sie etwas anderes dort zu setzen, die Schleife zu begrenzen.

Ich war für diese genaue Sache gesucht und schließlich mit einem Kettenbruch Ansatz ging. Die fortgesetzte Fraktion kann unter hier oder hier

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

Alte Frage, aber ich glaube tatsächlich, diese Antwort ist vorzuziehen. Es hat eine gute Präzision und unterstützt Argumente von praktisch jeder Größe.

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

Die Kernlogik (logBigInteger Methode) von diese anderen Antwort von mir kopiert .

Ich habe eine Funktion für BigInteger aber es kann leicht für BigDecimal modifiziert werden. Zersetzen des Protokoll- und mit einigen Eigenschaften des Stammes ist, was ich tue, aber ich bekomme nur mit doppelter Genauigkeit. Aber es funktioniert für jede Basis. :)

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);
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top