2012-10-10 13 views
10

Tengo que almacenar el producto de varios valores probabilty que son realmente bajos (por ejemplo, 1E-80). El uso del doble java primitivo daría como resultado cero debido al flujo inferior. No quiero que el valor vaya a cero porque más adelante habrá un número mayor (por ejemplo, 1E100) que traerá los valores dentro del rango que puede manejar el doble.Java doble y funciona con valores muy pequeños

Así que creé una clase diferente (MyDouble) que funciona para guardar la parte base y las partes del exponente. Al hacer cálculos, por ejemplo, multiplicación, multiplico las partes de base y agrego los exponentes.

El programa es rápido con el tipo doble primitivo. Sin embargo, cuando uso mi propia clase (MyDouble) el programa es realmente lento. Creo que esto se debe a los nuevos objetos que tengo que crear cada vez para crear operaciones simples y el recolector de basura tiene que trabajar mucho cuando los objetos ya no son necesarios.

Mi pregunta es, ¿hay una mejor manera de pensar que puedo resolver este problema? Si no, ¿hay alguna manera de acelerar el programa con mi propia clase (MyDouble)?

[Nota: tomar el registro y luego tomar el exponente no resuelve mi problema]

clase MiDouble:

public class MyDouble { 
    public MyDouble(double base, int power){ 
    this.base = base; 
    this.power = power; 
    } 

    public static MyDouble multiply(double... values) { 
    MyDouble returnMyDouble = new MyDouble(0); 
    double prodBase = 1; 
    int prodPower = 0; 
    for(double val : values) { 
      MyDouble ad = new MyDouble(val); 
      prodBase *= ad.base; 
      prodPower += ad.power; 
     } 
     String newBaseString = "" + prodBase; 
     String[] splitted = newBaseString.split("E"); 
     double newBase = 0; int newPower = 0; 
     if(splitted.length == 2) { 
      newBase = Double.parseDouble(splitted[0]); 
      newPower = Integer.parseInt(splitted[1]); 
     } else { 
      newBase = Double.parseDouble(splitted[0]); 
      newPower = 0; 
     } 
     returnMyDouble.base = newBase; 
     returnMyDouble.power = newPower + prodPower;   
     return returnMyDouble; 
    } 
} 
+4

¿Por qué no usa [BigDecimal] (http://docs.oracle.com/javase/1.5.0/docs/api/java/math/BigDecimal.html)? – DaoWen

+0

o 'BigInteger' –

+0

ver http://stackoverflow.com/questions/277309/java-floating-point-high-precision-library –

Respuesta

1

La lentitud puede deberse a los objetos de cadena intermedios que se crean en concatetos de división y cuerda.

Prueba esto:

/** 
* value = base * 10^power. 
*/ 

public class MyDouble { 

    // Threshold values to determine whether given double is too small or not. 
private static final double SMALL_EPSILON = 1e-8; 
private static final double SMALL_EPSILON_MULTIPLIER = 1e8; 
private static final int SMALL_EPSILON_POWER = 8; 

private double myBase; 
private int myPower; 

public MyDouble(double base, int power){ 
    myBase = base; 
    myPower = power; 
} 

public MyDouble(double base) 
{ 
    myBase = base; 
    myPower = 0; 
    adjustPower(); 
} 

/** 
* If base value is too small, increase the base by multiplying with some number and 
* decrease the power accordingly. 
* <p> E.g 0.000 000 000 001 * 10^1 => 0.0001 * 10^8 
*/ 
private void adjustPower() 
{ 
    // Increase the base & decrease the power 
    // if given double value is less than threshold. 
    if (myBase < SMALL_EPSILON) { 
     myBase = myBase * SMALL_EPSILON_MULTIPLIER; 
     myPower -= SMALL_EPSILON_POWER; 
    } 
} 

/** 
* This method multiplies given double and updates this object. 
*/ 
public void multiply(MyDouble d) 
{ 
    myBase *= d.myBase; 
    myPower += d.myPower; 
    adjustPower(); 
} 

/** 
* This method multiplies given primitive double value with this object and update the 
* base and power. 
*/ 
public void multiply(double d) 
{ 
    multiply(new MyDouble(d)); 
} 

@Override 
public String toString() 
{ 
    return "Base:" + myBase + ", Power=" + myPower; 
} 

/** 
* This method multiplies given double values and returns MyDouble object. 
* It make sure that too small double values do not zero out the multiplication result. 
*/ 
public static MyDouble multiply(double...values) 
{ 
    MyDouble result = new MyDouble(1); 
    for (int i=0; i<values.length; i++) { 
     result.multiply(values[i]); 
    } 
    return result; 
} 

public static void main(String[] args) { 
    MyDouble r = MyDouble.multiply(1e-80, 1e100); 
    System.out.println(r); 
} 

}

Si esto sigue siendo lenta para su propósito, puede modificar el método se multiplican() para operar directamente sobre el doble primitiva en lugar de crear un objeto MiDouble.

1

Estoy seguro de que este será un buen negocio más lento que un doble, pero probablemente un gran factor contribuyente sería la manipulación de Cadenas. ¿Podrías deshacerte de eso y calcular el poder a través de la aritmética? Incluso la aritmética iterativa o recursiva podría ser más rápida que la conversión a String para tomar bits del número.

1

En una aplicación de rendimiento pesado, desea encontrar una manera de almacenar información básica en primitivos. En este caso, quizás pueda dividir los bytes de una variable larga u otra de manera que una parte fija sea la base.

Luego, puede crear métodos personalizados multiplicar long o Long como si fueran un doble. Agarras los bits que representan la base y exp, y truncas en consecuencia.

En cierto modo, aquí está reinventando la rueda, ya que quiere un código de bytes que realice de manera eficiente la operación que está buscando.

edición:

Si desea seguir con dos variables, se puede modificar el código para tomar simplemente una matriz, que será mucho más ligero que los objetos. Además, debe eliminar llamadas a cualquier función de análisis sintáctico de cadenas. Esos son extremadamente lentos

2

Está tratando de analizar cadenas cada vez que lo hace multiplicar. ¿Por qué no calcula todos los valores en una estructura como parte real y exponencial como paso previo al cálculo y luego crea algoritmos para multiplicación, suma, subdivisión, potencia y otros?

También podría agregar una bandera para números grandes/pequeños. Creo que no usará tanto 1e100 como 1e-100 en un solo cálculo (por lo que podría simplificar algunos cálculos) y podría mejorar el tiempo de cálculo para diferentes pares (grande, grande), (pequeño, pequeño), (grande, pequeño).

2

Puede utilizar

BigDecimal bd = BigDecimal.ONE.scaleByPowerOfTen(-309) 
     .multiply(BigDecimal.ONE.scaleByPowerOfTen(-300)) 
     .multiply(BigDecimal.ONE.scaleByPowerOfTen(300)); 
System.out.println(bd); 

impresiones

1E-309 

O si utiliza una escala log10

double d = -309 + -300 + 300; 
System.out.println("1E"+d); 

impresiones

1E-309.0 
4

La forma en que esto se resuelve es trabajar en el espacio de registro --- trivializa el problema. Cuando dice que no funciona, ¿puede dar detalles específicos de por qué? El subdesbordamiento de probabilidad es un problema común en los modelos probabilísticos, y no creo que alguna vez lo haya resuelto de otra manera.

Recuerde que el registro (a * b) es simplemente log (a) + log (b). De manera similar, log (a/b) es log (a) - log (b). Supongo que, dado que está trabajando con probabilidades, su multiplicación y división están causando los problemas de subdesbordamiento; El inconveniente del espacio de registro es que necesita utilizar rutinas especiales para calcular el registro (a + b), que puedo indicarle si este es su problema.

Así que la respuesta simple es trabajar en el espacio de registro y volver a exponenciar al final para obtener un número legible por humanos.

+0

Bueno, estaba pensando que no podía usar el registro porque mis términos también incluían sumas de los exponentes. Tuve el uso del truco logsumexp. Finalmente terminé usando todo en el espacio de registro porque mi propia implementación, aunque funcionó, fue muy lenta. –

+0

Sí, la mayoría de las cosas se pueden hacer funcionar en el espacio de registro, pero para ciertas operaciones (suma que es el problema obvio) no es trivial. –

Cuestiones relacionadas