Logaritmo di un BigDecimal

Come posso calcolare il logaritmo di un BigDecimal? Qualcuno sa di algoritmi che posso usare?

Il mio googling finora ha escogitato l’idea (inutile) di convertire solo in un double e usare Math.log.

Fornirò la precisione della risposta richiesta.

modifica: qualsiasi base farà. Se è più semplice in base x, lo farò.

    Java Number Cruncher: la Guida del programmatore Java al calcolo numerico fornisce una soluzione utilizzando il metodo di Newton . Il codice sorgente del libro è disponibile qui . Quanto segue è stato tratto dal capitolo 12.5 Funzioni di Big Decmial (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); } 

    Un piccolo algoritmo che funziona alla grande per grandi numeri usa il rapporto log(AB) = log(A) + log(B) . Ecco come farlo in base 10 (che puoi convertire banalmente in qualsiasi altra base logaritmica):

    1. Contare il numero di cifre decimali nella risposta. Questa è la parte integrale del tuo logaritmo, più uno . Esempio: floor(log10(123456)) + 1 è 6, poiché 123456 ha 6 cifre.

    2. Puoi fermarti qui se tutto ciò di cui hai bisogno è la parte intera del logaritmo: basta sottrarre 1 dal risultato del passaggio 1.

    3. Per ottenere la parte frazionaria del logaritmo, dividi il numero per 10^(number of digits) , quindi calcola il log di quello usando math.log10() (o qualsiasi altra cosa: usa un’approssimazione di serie semplice se nient’altro è disponibile), e aggiungilo alla parte intera. Esempio: per ottenere la parte frazionaria di log10(123456) , calcolare math.log10(0.123456) = -0.908... e aggiungerlo al risultato del passo 1: 6 + -0.908 = 5.092 , che è log10(123456) . Nota che stai praticamente virando solo su un punto decimale verso la parte anteriore del grande numero; c’è probabilmente un buon modo per ottimizzarlo nel tuo caso d’uso, e per numeri davvero grandi non devi nemmeno preoccuparti di afferrare tutte le cifre – log10(0.123) è una grande approssimazione per log10(0.123456789) .

    Questo è super veloce, perché:

    • No toString()
    • No BigInteger math (Newton / Frazione continua)
    • Nemmeno istanziare un nuovo BigInteger
    • Utilizza solo un numero fisso di operazioni molto veloci

    Una chiamata richiede circa 20 microsecondi (circa 50.000 chiamate al secondo)

    Ma:

    • Funziona solo con BigInteger

    Soluzione alternativa per BigDecimal (non testato per la velocità):

    • Sposta il punto decimale fino a quando il valore è> 2 ^ 53
    • Usa toBigInteger() (usa un div internamente)

    Questo algoritmo sfrutta il fatto che il log può essere calcolato come la sum dell’esponente e il log della mantissa. per esempio:

    12345 ha 5 cifre, quindi il registro di base 10 è compreso tra 4 e 5. log (12345) = 4 + log (1.2345) = 4.09149 … (base 10 log)


    Questa funzione calcola il registro di base 2 perché trovare il numero di bit occupati è banale.

     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) 

    Potresti scomporlo usando

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

    Fondamentalmente, b+1 sarà il numero di cifre nel numero e a sarà un valore compreso tra 0 e 1 che è ansible calcolare il logaritmo utilizzando una normale aritmetica double .

    Oppure ci sono trucchi matematici che puoi usare – per esempio, i logaritmi di numeri vicini a 1 possono essere calcolati da un’espansione di serie

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

    A seconda del tipo di numero che stai cercando di usare il logaritmo, potrebbe esserci qualcosa di simile che puoi usare.

    MODIFICA : per ottenere il logaritmo in base 10, è ansible dividere il logaritmo naturale con ln(10) o in modo simile per qualsiasi altra base.

    Questo è quello che ho trovato:

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

    Un’implementazione Java di pseudcode Meower68 che ho testato con pochi numeri:

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

    Algoritmo pseudocodice per fare un logaritmo.

    Supponendo che vogliamo log_n di 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; 

    Il grande ciclo di tempo può sembrare un po ‘confuso.

    Ad ogni passaggio puoi quadrare il tuo input o puoi prendere la radice quadrata della tua base; in entrambi i casi, devi dividere la tua frazione di 2. Trovo che quadrare l’input e lasciare la base da solo sia più preciso.

    Se l’input va a 1, siamo passati. Il log di 1, per qualsiasi base, è 0, il che significa che non è necessario aggiungere altro.

    se (risultato + frazione) non è maggiore del risultato, allora abbiamo raggiunto i limiti di precisione per il nostro sistema di numerazione. Possiamo fermarci.

    Ovviamente, se stai lavorando con un sistema che ha arbitrariamente molte cifre di precisione, vorresti mettere qualcos’altro lì dentro per limitare il ciclo.

    Se tutto ciò di cui hai bisogno è trovare i poteri di 10 nel numero che puoi usare:

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

    Stavo cercando questa cosa esatta e alla fine è andato con un approccio frazione continua. La frazione continua può essere trovata qui o qui

    Codice:

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

    Vecchia domanda, ma in realtà penso che questa risposta sia preferibile. Ha una buona precisione e supporta argomenti praticamente di qualsiasi dimensione.

     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 Math.log() */ 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 logica di base (metodo logBigInteger ) viene copiata da questa altra risposta .