studio programacion para móviles libro edición desarrollo desarrollar curso aprende aplicaciones java math rounding

programacion - Cómo resolver un problema de Java Rounding Double



manual de programacion android pdf (13)

Ahorre la cantidad de centavos en lugar de dólares, y simplemente haga el formato en dólares cuando lo muestre. De esta forma, puede usar un número entero que no tenga problemas de precisión.

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?


Aunque no deberías usar dobles para cálculos precisos, el siguiente truco me ayudó si redondeas 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 impresiones:

0.014999999999999965 0.01 0.02

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


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

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


Como las respuestas anteriores indicaron, 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 un problema con el uso de BigDecimal . Cuando está convirtiendo del valor doble a un BigDecimal , tiene la opción de usar un nuevo BigDecimal(double) o el método de fábrica estático BigDecimal.valueOf(double) . Use el método de fábrica estático.

El constructor doble convierte la precisión completa del double en un BigDecimal mientras que la fábrica estática lo convierte efectivamente en una String , y luego lo convierte en un BigDecimal .

Esto se vuelve relevante cuando te encuentras con esos sutiles errores de redondeo. Un número puede mostrar como .585, pero internamente su valor es ''0.58499999999999996447286321199499070644378662109375''. Si utilizó el constructor BigDecimal , 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


Es bastante simple.

Use 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 lugares decimales.


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 un doble legal puede ser. Si sabe en su aplicación que nunca necesitará una precisión inferior a 0.00000001, entonces lo que 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, en finanzas para precisiones de moneda, etc.)

Sin embargo, el problema fundamental al tratar de redondearlo es que cuando se divide por un factor para reescalarlo, en realidad 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 a un dígito muy significativo (por lo que sus operandos son <0), por ejemplo, si ejecuta lo siguiente con el código de Timons:

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

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

De hecho, la única forma de estar completamente seguro de haber eliminado la imprecisión es pasar por un BigDecimal para escalar. p.ej

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

Usar una combinación de la estrategia épsilon y la estrategia BigDecimal le dará un control fino sobre su precisión. La idea es que el épsilon te acerca mucho y luego el BigDecimal eliminará cualquier imprecisión causada al volver a escalar después. Aunque usar BigDecimal reducirá el rendimiento esperado de su aplicación.

Se me ha señalado que el paso final de usar BigDecimal para reescalarlo no siempre es necesario en algunos casos de usos en los que puede determinar que no hay ningún valor de entrada que la división final pueda reintroducir un error. Actualmente no sé cómo determinarlo adecuadamente, así que si alguien sabe cómo, me gustarí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 (p. Ej., Sin función sqrt)

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


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

Use BigDecimal en su lugar.

EDITAR:

Además, solo para señalar que esto no es un problema de "Java" de redondeo. Otros idiomas exhiben un comportamiento similar (aunque no necesariamente consistente). Java al menos garantiza un comportamiento consistente en este sentido.


Para controlar la precisión de la aritmética de punto flotante, debe usar java.math.BigDecimal . Lea la necesidad de BigDecimal por John Zukowski para 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 la siguiente salida.

877.85 = 1586.6 - 708.75


Ver respuestas a esta pregunta . Esencialmente, lo que estás 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 haciéndolo.


Yo 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 forma, evitas los peligros 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 de todas formas.


double rounded = Math.rint(toround * 100) / 100;