Pregunta

    

Esta pregunta ya tiene una respuesta aquí:

    
            
  •              Conserva la precisión con doble en Java                                      20 respuestas                          
  •     
    

Parece que la resta está desencadenando algún tipo de problema y el valor resultante es incorrecto.

double tempCommission = targetPremium.doubleValue()*rate.doubleValue()/100d;

78.75 = 787.5 * 10.0 / 100d

double netToCompany = targetPremium.doubleValue() - tempCommission;

708.75 = 787.5 - 78.75

double dCommission = request.getPremium().doubleValue() - netToCompany;

877.8499999999999 = 1586.6 - 708.75

El valor esperado resultante sería 877.85.

¿Qué se debe hacer para garantizar el cálculo correcto?

¿Fue útil?

Solución

Para controlar la precisión de la aritmética de coma flotante, debe usar java.math.BigDecimal . Lea La necesidad de BigDecimal por John Zukowski para obtener más información.

Dado su ejemplo, la última línea sería la siguiente usando BigDecimal.

import java.math.BigDecimal;

BigDecimal premium = BigDecimal.valueOf("1586.6");
BigDecimal netToCompany = BigDecimal.valueOf("708.75");
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);

Esto da como resultado el siguiente resultado.

877.85 = 1586.6 - 708.75

Otros consejos

Como se indicó en las respuestas anteriores, esto es una consecuencia de hacer aritmética de coma flotante.

Como sugirió un póster anterior, cuando haga cálculos numéricos, use java.math.BigDecimal.

Sin embargo, hay una necesidad de usar BigDecimal. Cuando está convirtiendo del valor doble a BigDecimal(double), tiene la opción de usar un nuevo constructor BigDecimal.valueOf(double) o el método de fábrica estático double. Utilice el método estático de fábrica.

El constructor doble convierte toda la precisión de String en <=> mientras que la fábrica estática la convierte efectivamente en <=>, luego la convierte en <=>.

Esto se vuelve relevante cuando te encuentras con esos sutiles errores de redondeo. Un número puede aparecer como .585, pero internamente su valor es '0.58499999999999996447286321199499070644378662109375'. Si utilizó el constructor <=>, obtendría el número que NO es igual a 0.585, mientras que el método estático le daría un valor igual a 0.585.

double value = 0.585;
System.out.println(new BigDecimal(value));
System.out.println(BigDecimal.valueOf(value));

en mi sistema da

0.58499999999999996447286321199499070644378662109375
0.585

Otro ejemplo:

double d = 0;
for (int i = 1; i <= 10; i++) {
    d += 0.1;
}
System.out.println(d);    // prints 0.9999999999999999 not 1.0

Utilice BigDecimal en su lugar.

EDITAR:

Además, solo para señalar que esto no es un problema de redondeo 'Java'. Exhibición de otros idiomas comportamiento similar (aunque no necesariamente consistente). Java al menos garantiza un comportamiento coherente a este respecto.

Modificaría el ejemplo anterior de la siguiente manera:

import java.math.BigDecimal;

BigDecimal premium = new BigDecimal("1586.6");
BigDecimal netToCompany = new BigDecimal("708.75");
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);

De esta manera evitas las trampas de usar cuerdas para empezar. Otra alternativa:

import java.math.BigDecimal;

BigDecimal premium = BigDecimal.valueOf(158660, 2);
BigDecimal netToCompany = BigDecimal.valueOf(70875, 2);
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);

Creo que estas opciones son mejores que usar dobles. En webapps, los números comienzan como cadenas de todos modos.

Cada vez que haces cálculos con dobles, esto puede suceder. Este código le daría 877.85:

respuesta doble = Math.round (dCommission * 100000) / 100000.0;

Guarde el número de centavos en lugar de dólares, y solo haga el formato en dólares cuando lo envíe. De esa manera, puede usar un número entero que no sufra problemas de precisión.

Vea las respuestas a esta pregunta . Esencialmente, lo que está viendo es una consecuencia natural del uso de la aritmética de coma flotante.

Podría elegir una precisión arbitraria (¿dígitos significativos de sus entradas?) y redondear su resultado, si se siente cómodo haciendo eso.

Este es un tema divertido.

La idea detrás de la respuesta de Timons es que especifique un épsilon que represente la precisión más pequeña que puede ser un doble legal. Si sabe en su solicitud que nunca necesitará una precisión por debajo de 0.00000001, entonces lo que él sugiere es suficiente para obtener un resultado más preciso muy cercano a la verdad. Útil en aplicaciones donde conocen por adelantado su máxima precisión (por ejemplo, financiación para precisiones de divisas, etc.)

Sin embargo, el problema fundamental al tratar de redondearlo es que cuando se divide por un factor para reescalarlo, se introduce otra posibilidad para problemas de precisión. Cualquier manipulación de dobles puede introducir problemas de imprecisión con frecuencia variable. Especialmente si está intentando redondear en un dígito muy significativo (por lo que sus operandos son & Lt; 0), por ejemplo, si ejecuta lo siguiente con el código de Timons:

System.out.println(round((1515476.0) * 0.00001) / 0.00001);

resultará en 1499999.9999999998 donde el objetivo aquí es redondear a las unidades de 500000 (es decir, queremos 1500000)

De hecho, la única manera de estar completamente seguro de que ha eliminado la imprecisión es pasar por un BigDecimal para reducir la escala. por ejemplo,

System.out.println(BigDecimal.valueOf(1515476.0).setScale(-5, RoundingMode.HALF_UP).doubleValue());

El uso de una combinación de la estrategia epsilon y la estrategia BigDecimal le dará un control preciso sobre su precisión. La idea de ser el épsilon te acerca mucho y luego el BigDecimal eliminará cualquier imprecisión causada por el cambio de escala posterior. Aunque el uso de BigDecimal reducirá el rendimiento esperado de su aplicación.

Se me ha señalado que el paso final de usar BigDecimal para reescalar no siempre es necesario para algunos casos de uso cuando puede determinar que no hay un valor de entrada que la división final pueda reintroducir un error. Actualmente no sé cómo determinar esto correctamente, así que si alguien sabe cómo, me encantaría saberlo.

Hasta ahora, la forma más elegante y eficiente de hacerlo en Java:

double newNum = Math.floor(num * 100 + 0.5) / 100;

Mejor aún use JScience ya que BigDecimal es bastante limitado (por ejemplo, no sqrt función)

double dCommission = 1586.6 - 708.75;
System.out.println(dCommission);
> 877.8499999999999

Real dCommissionR = Real.valueOf(1586.6 - 708.75);
System.out.println(dCommissionR);
> 877.850000000000
double rounded = Math.rint(toround * 100) / 100;

Aunque no deberías usar dobles para cálculos precisos, el siguiente truco me ayudó si estás redondeando los resultados de todos modos.

public static int round(Double i) {
    return (int) Math.round(i + ((i > 0.0) ? 0.00000001 : -0.00000001));
}

Ejemplo:

    Double foo = 0.0;
    for (int i = 1; i <= 150; i++) {
        foo += 0.00010;
    }
    System.out.println(foo);
    System.out.println(Math.round(foo * 100.0) / 100.0);
    System.out.println(round(foo*100.0) / 100.0);

Que imprime:

0.014999999999999965
0.01
0.02

Más información: http://en.wikipedia.org/wiki/Double_precision

Es bastante simple.

Utilice el operador% .2f para la salida. ¡Problema resuelto!

Por ejemplo:

int a = 877.8499999999999;
System.out.printf("Formatted Output is: %.2f", a);

El código anterior da como resultado una salida de impresión de: 877.85

El operador% .2f define que solo se deben usar DOS decimales.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top