java - jpa bigdecimal
Logaritmo de un BigDecimal (10)
Algoritmo de pseudocódigo para hacer un logaritmo.
Suponiendo que queremos 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;
El ciclo grande puede parecer un poco confuso.
En cada pasada, puede cuadrar su entrada o puede tomar la raíz cuadrada de su base; de cualquier forma, debes dividir tu fracción por 2. Encuentro cuadrar la entrada, y dejar la base solo, para ser más preciso.
Si la entrada va a 1, hemos terminado. El registro de 1, para cualquier base, es 0, lo que significa que no necesitamos agregar más.
si (resultado + fracción) no es mayor que el resultado, entonces hemos alcanzado los límites de precisión para nuestro sistema de numeración. Nosotros podemos parar.
Obviamente, si está trabajando con un sistema que tiene arbitrariamente muchos dígitos de precisión, querrá poner algo más allí para limitar el ciclo.
¿Cómo puedo calcular el logaritmo de un BigDecimal? ¿Alguien sabe de algún algoritmo que pueda usar?
Hasta ahora, mis búsquedas en Google me han dado la idea (inútil) de convertirme a un doble y usar Math.log.
Proporcionaré la precisión de la respuesta requerida.
editar: cualquier base servirá. Si es más fácil en la base x, lo haré.
Estaba buscando exactamente esto y eventualmente fui con un enfoque de fracción continua. La fracción continua se puede encontrar here o here
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;
}
Este es super rápido, porque:
- No
toString()
- Sin matemáticas
BigInteger
(fracción de Newton / continuación) - Ni siquiera instanciando un nuevo
BigInteger
- Solo usa un número fijo de operaciones muy rápidas
Una llamada demora aproximadamente 20 microsegundos (aproximadamente 50k de llamadas por segundo)
Pero:
- Solo funciona para
BigInteger
Solución para BigDecimal
(no probado para la velocidad):
- Cambie el punto decimal hasta que el valor sea> 2 ^ 53
- Usar
toBigInteger()
(usa undiv
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. p.ej:
12345 tiene 5 dígitos, por lo que el registro base 10 está entre 4 y 5. log (12345) = 4 + log (1.2345) = 4.09149 ... (base 10 log)
Esta función calcula el registro de la base 2 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!
}
Esto es lo que se me ocurrió:
//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;
}
Podrías descomponerlo usando
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, con el que se puede calcular el logaritmo utilizando aritmética double
regular.
O hay trucos matemáticos que puede usar; por ejemplo, los logaritmos de números cercanos a 1 se pueden calcular mediante una expansión en serie
ln(x + 1) = x - x^2/2 + x^3/3 - x^4/4 + ...
Dependiendo de qué tipo de número está tratando de tomar el logaritmo, puede haber algo como esto que puede usar.
EDITAR : Para obtener el logaritmo en la base 10, puede dividir el logaritmo natural por ln(10)
, o de forma similar para cualquier otra base.
Si todo lo que necesitas es encontrar los poderes de 10 en el número que puedes usar:
public int calculatePowersOf10(BigDecimal value)
{
return value.round(new MathContext(1)).scale() * -1;
}
Un pequeño algoritmo hacky que funciona muy bien para grandes números usa la relación log(AB) = log(A) + log(B)
. Aquí le mostramos cómo hacerlo en la base 10 (que puede convertir trivialmente en cualquier otra base de logaritmo):
Cuenta el número de dígitos decimales en la respuesta. Esa es la parte integral de tu logaritmo, más uno . Ejemplo:
floor(log10(123456)) + 1
es 6, ya que 123456 tiene 6 dígitos.Puede detenerse aquí si todo lo que necesita es la parte entera del logaritmo: simplemente resta 1 del resultado del paso 1.
Para obtener la parte fraccionaria del logaritmo, divida el número por
10^(number of digits)
, luego calcule el registro de eso usandomath.log10()
(o lo que sea, use una aproximación de serie simple si no hay nada más disponible), y agréguelo a la parte entera. Ejemplo: para obtener la parte fraccionaria delog10(123456)
, calculemath.log10(0.123456) = -0.908...
y agréguelo al resultado del paso 1:6 + -0.908 = 5.092
, que eslog10(123456)
. Tenga en cuenta que básicamente está haciendo tachuelas en un punto decimal al frente del número grande; probablemente haya una buena manera de optimizar esto en su caso de uso, y para números realmente grandes ni siquiera necesita molestarse en tomar todos los dígitos -log10(0.123)
es una gran aproximación alog10(0.123456789)
.
Una implementación Java del pseudcode Meower68 que probé con unos pocos 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;
}
Una vieja pregunta, pero en realidad creo que esta respuesta es preferible. Tiene buena precisión y admite 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 del núcleo (método logBigInteger
) se copia de esta otra respuesta mía.
Java Number Cruncher: La Guía del Programador Java para Computación Numérica proporciona una solución usando el Método de Newton . El código fuente del libro está disponible here . Lo 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);
}