Error de precisión con flotadores en Java

Me pregunto cuál es la mejor manera de corregir errores de precisión en Java. Como puede ver en el siguiente ejemplo, hay errores de precisión:

class FloatTest { public static void main(String[] args) { Float number1 = 1.89f; for(int i = 11; i < 800; i*=2) { System.out.println("loop value: " + i); System.out.println(i*number1); System.out.println(""); } } } 

El resultado que se muestra es:

valor de bucle: 11

20.789999

valor de bucle: 22

41.579998

valor de bucle: 44

83.159996

valor de bucle: 88

166.31999

valor de bucle: 176

332.63998

valor de bucle: 352

665.27997

valor de bucle: 704

1330.5599

Además, si alguien puede explicar por qué solo lo hace comenzando en 11 y doblando el valor cada vez. Creo que todos los demás valores (o muchos de ellos al menos) muestran el resultado correcto.

Problemas como este me han causado dolor de cabeza en el pasado y generalmente uso formateadores de números o los pongo en un String.

Editar: Como han mencionado las personas, podría usar un doble, pero después de intentarlo, parece que 1.89 como el doble de 792 aún genera un error (la salida es 1496.8799999999999).

Creo que probaré otras soluciones como BigDecimal

Si realmente te importa la precisión, debes usar BigDecimal

http://download.oracle.com/javase/1,5.0/docs/api/java/math/BigDecimal.html

El problema no está en Java, sino en el buen estándar flotante ( http://en.wikipedia.org/wiki/IEEE_floating-point_standard ).

Tu también puedes:

  • use Double y tenga un poco más de precisión (pero no es perfecto, por supuesto, también tiene una precisión limitada)

  • utilizar una biblioteca de precisión arbitraria

  • utilice algoritmos numéricamente estables y dígitos truncados / redondos de los que no esté seguro de que sean correctos (puede calcular la precisión numérica de las operaciones)

Cuando imprime el resultado de una operación doble, necesita usar el redondeo apropiado.

 System.out.printf("%.2f%n", 1.89 * 792); 

huellas dactilares

 1496.88 

Si desea redondear el resultado a una precisión, puede usar el redondeo.

 double d = 1.89 * 792; d = Math.round(d * 100) / 100.0; System.out.println(d); 

huellas dactilares

 1496.88 

Sin embargo, si ve a continuación, se imprime como se esperaba, ya que hay una pequeña cantidad de redondeo implícito.


No vale nada que (double) 1.89 no es exactamente 1.89 Es una aproximación cercana.

nuevo BigDecimal (doble) convierte el valor exacto de doble sin ningún redondeo implícito. Puede ser útil para encontrar el valor exacto de un doble.

 System.out.println(new BigDecimal(1.89)); System.out.println(new BigDecimal(1496.88)); 

huellas dactilares

 1.8899999999999999023003738329862244427204132080078125 1496.8800000000001091393642127513885498046875 

Puedes usar dobles en lugar de flotadores

Si realmente necesita una precisión arbitraria, use BigDecimal .

La mayoría de tu pregunta ha sido bastante bien tratada, aunque aún podrías beneficiarte leyendo la wiki de la etiqueta [floating-point] para entender por qué funcionan las otras respuestas.

Sin embargo, nadie ha abordado “por qué solo comienza a partir de las 11 y duplica el valor cada vez”, así que aquí está la respuesta a eso:

 for(int i = 11; i < 800; i*=2) ╚═══╤════╝ ╚╤═╝ │ └───── "double the value every time" │ └───── "start at 11" 

primero de Float es la clase de contenedor para el float primitivo

y los dobles tienen más precisión

pero si solo desea calcular hasta el segundo dígito (para fines monetarios, por ejemplo) use un número entero (como si estuviera usando centavos como unidad) y agregue cierta lógica de escala cuando está multiplicando / dividiendo

o si necesita una precisión arbitraria use BigDecimal

Si la precisión es vital, debe usar BigDecimal para asegurarse de que la precisión requerida permanezca. Cuando crea una instancia del cálculo, recuerde utilizar cadenas para crear una instancia de los valores en lugar de los dobles.