Conservar la precisión con doble en Java

public class doublePrecision { public static void main(String[] args) { double total = 0; total += 5.6; total += 5.8; System.out.println(total); } } 

El código anterior se imprime:

 11.399999999999 

¿Cómo obtendría esto para simplemente imprimir (o poder usarlo como) 11.4?

Como otros han mencionado, es probable que desee utilizar la clase BigDecimal , si desea tener una representación exacta de 11.4.

Ahora, una pequeña explicación de por qué está sucediendo esto:

Los tipos float y double primitive en Java son números de coma flotante , donde el número se almacena como una representación binaria de una fracción y un exponente.

Más específicamente, un valor de punto flotante de precisión doble como el tipo double es un valor de 64 bits, donde:

  • 1 bit denota el signo (positivo o negativo).
  • 11 bits para el exponente
  • 52 bits para los dígitos significativos (la parte fraccionaria como un binario).

Estas partes se combinan para producir una double representación de un valor.

(Fuente: Wikipedia: precisión doble )

Para obtener una descripción detallada de cómo se manejan los valores de punto flotante en Java, consulte la Sección 4.2.3: Tipos, formatos y valores de punto flotante de la especificación de lenguaje Java.

Los tipos byte , char , int , long son números de punto fijo , que son representaciones exactas de números. A diferencia de los números de punto fijo, los números de punto flotante algunas veces (seguro de suponer “la mayoría de las veces”) no podrán devolver una representación exacta de un número. Esta es la razón por la que termina con 11.399999999999 como resultado de 5.6 + 5.8 .

Cuando requiera un valor que sea exacto, como 1.5 o 150.1005, querrá usar uno de los tipos de punto fijo, que podrá representar exactamente el número.

Como se ha mencionado varias veces, Java tiene una clase BigDecimal que manejará números muy grandes y números muy pequeños.

De la referencia de la API de Java para la clase BigDecimal :

Números decimales firmados de precisión arbitraria e inmutable. Un BigDecimal consiste en un valor sin escalar entero de precisión arbitraria y una escala entera de 32 bits. Si es cero o positivo, la escala es el número de dígitos a la derecha del punto decimal. Si es negativo, el valor sin escala del número se multiplica por diez a la potencia de la negación de la escala. El valor del número representado por BigDecimal es por lo tanto (unscaledValue × 10 ^ -scale).

Ha habido muchas preguntas sobre el desbordamiento de stack relacionadas con la cuestión de los números de coma flotante y su precisión. Aquí hay una lista de preguntas relacionadas que pueden ser de interés:

  • ¿Por qué veo una variable doble inicializada a algún valor como 21.4 como 21.399999618530273?
  • Cómo imprimir números realmente grandes en C ++
  • ¿Cómo se almacena el punto flotante? ¿Cuándo importa?
  • ¿Usar Float o Decimal para la cantidad de dólares de la aplicación de contabilidad?

Si realmente desea conocer los detalles esenciales de los números flotantes, eche un vistazo a Lo que todo científico debería saber sobre la aritmética de coma flotante .

Cuando 33.33333333333333 un número doble, por ejemplo, 33.33333333333333 , el valor que obtiene es en realidad el valor de precisión doble representable más cercano, que es exactamente:

 33.3333333333333285963817615993320941925048828125 

Dividir eso por 100 da:

 0.333333333333333285963817615993320941925048828125 

que tampoco es representable como un número de precisión doble, por lo que nuevamente se redondea al valor representable más cercano, que es exactamente:

 0.3333333333333332593184650249895639717578887939453125 

Cuando imprime este valor, se redondea una vez más a 17 dígitos decimales, dando:

 0.33333333333333326 

Si solo desea procesar valores como fracciones, puede crear una clase Fraction que contenga un numerador y un campo denominador.

Escriba métodos para sumr, restar, multiplicar y dividir, así como un método toDoble. De esta forma puede evitar flotantes durante los cálculos.

EDITAR: Implementación rápida,

 public class Fraction { private int numerator; private int denominator; public Fraction(int n, int d){ numerator = n; denominator = d; } public double toDouble(){ return ((double)numerator)/((double)denominator); } public static Fraction add(Fraction a, Fraction b){ if(a.denominator != b.denominator){ double aTop = b.denominator * a.numerator; double bTop = a.denominator * b.numerator; return new Fraction(aTop + bTop, a.denominator * b.denominator); } else{ return new Fraction(a.numerator + b.numerator, a.denominator); } } public static Fraction divide(Fraction a, Fraction b){ return new Fraction(a.numerator * b.denominator, a.denominator * b.numerator); } public static Fraction multiply(Fraction a, Fraction b){ return new Fraction(a.numerator * b.numerator, a.denominator * b.denominator); } public static Fraction subtract(Fraction a, Fraction b){ if(a.denominator != b.denominator){ double aTop = b.denominator * a.numerator; double bTop = a.denominator * b.numerator; return new Fraction(aTop-bTop, a.denominator*b.denominator); } else{ return new Fraction(a.numerator - b.numerator, a.denominator); } } } 

Observe que tendría el mismo problema si utilizara aritmética decimal de precisión limitada, y quisiera tratar con 1/3: 0.333333333 * 3 es 0.999999999, no 1.00000000.

Desafortunadamente, 5.6, 5.8 y 11.4 simplemente no son números redondos en binario, porque implican quintos. Entonces la representación flotante de ellos no es exacta, así como 0.3333 no es exactamente 1/3.

Si todos los números que usa son decimales no recurrentes y desea obtener resultados exactos, use BigDecimal. O como han dicho otros, si sus valores son como el dinero en el sentido de que son todos un múltiplo de 0.01, o 0.001, o algo así, multiplique todo por una potencia fija de 10 y use int o long (la sum y la resta son trivial: cuidado con la multiplicación).

Sin embargo, si está satisfecho con el método binario para el cálculo, pero solo desea imprimir las cosas en un formato ligeramente más amigable, pruebe java.util.Formatter o String.format . En la cadena de formato, especifique una precisión menor que la precisión completa de un doble. Para 10 cifras significativas, por ejemplo, 11.399999999999 es 11.4, por lo que el resultado será casi tan preciso y más legible en humanos en los casos en que el resultado binario sea muy cercano a un valor que requiera solo unas pocas cifras decimales.

La precisión para especificar depende un poco de la cantidad de cálculos que haya hecho con sus números: en general, cuanto más haga, más errores acumulará, pero algunos algoritmos lo acumularán mucho más rápido que otros (se denominan “inestables” como opuesto a “estable” con respecto a los errores de redondeo). Si todo lo que hace es agregar algunos valores, entonces supongo que al eliminar solo un decimal de precisión resolverá las cosas. Experimentar.

Es posible que desee estudiar el uso de la clase java.math.BigDecimal de Java si realmente necesita matemática de precisión. Aquí hay un buen artículo de Oracle / Sun sobre el caso de BigDecimal . Aunque nunca se puede representar 1/3 como alguien mencionado, puede tener el poder de decidir exactamente qué tan preciso quiere que sea el resultado. setScale () es tu amigo .. 🙂

Ok, porque tengo demasiado tiempo en mis manos en este momento aquí hay un ejemplo de código que se relaciona con su pregunta:

 import java.math.BigDecimal; /** * Created by a wonderful programmer known as: * Vincent Stoessel * xaymaca@gmail.com * on Mar 17, 2010 at 11:05:16 PM */ public class BigUp { public static void main(String[] args) { BigDecimal first, second, result ; first = new BigDecimal("33.33333333333333") ; second = new BigDecimal("100") ; result = first.divide(second); System.out.println("result is " + result); //will print : result is 0.3333333333333333 } } 

y para conectar mi nuevo idioma favorito, Groovy, aquí hay un ejemplo más claro de lo mismo:

 import java.math.BigDecimal def first = new BigDecimal("33.33333333333333") def second = new BigDecimal("100") println "result is " + first/second // will print: result is 0.33333333333333 

Como otros han notado, no todos los valores decimales pueden representarse como binarios, ya que el decimal se basa en potencias de 10 y el binario se basa en potencias de dos.

Si la precisión importa, use BigDecimal, pero si solo quiere resultados amigables:

 System.out.printf("%.2f\n", total); 

Te regalaré:

 11.40 

Seguro que podrías haberlo convertido en un ejemplo de tres líneas. 🙂

Si quiere precisión exacta, use BigDecimal. De lo contrario, puede usar enter multiplicado por 10 ^ cualquiera que sea la precisión que desee.

Estás corriendo contra la limitación de precisión del tipo doble.

Java.Math tiene algunas instalaciones aritméticas de precisión arbitraria.

No se puede, porque 7.3 no tiene una representación finita en binario. Lo más cercano que puede obtener es 2054767329987789/2 ** 48 = 7.3 + 1/1407374883553280.

Eche un vistazo a http://docs.python.org/tutorial/floatingpoint.html para obtener una explicación más detallada. (Está en el sitio web de Python, pero Java y C ++ tienen el mismo “problema”).

La solución depende de cuál es exactamente tu problema:

  • Si es que simplemente no te gusta ver todos esos dígitos de ruido, entonces arregla el formato de la cadena. No muestre más de 15 dígitos significativos (o 7 para flotar).
  • Si es que la inexactitud de tus números está rompiendo cosas como “si”, entonces debes escribir if (abs (x – 7.3)
  • Si trabajas con dinero, entonces lo que realmente quieres es el punto fijo decimal. Almacene un número entero de centavos o cualquiera que sea la unidad más pequeña de su moneda.
  • (MUY DESIGUAL) Si necesita más de 53 bits significativos (15-16 dígitos significativos) de precisión, utilice un tipo de coma flotante de alta precisión, como BigDecimal.
 private void getRound() { // this is very simple and interesting double a = 5, b = 3, c; c = a / b; System.out.println(" round val is " + c); // round val is : 1.6666666666666667 // if you want to only two precision point with double we // can use formate option in String // which takes 2 parameters one is formte specifier which // shows dicimal places another double value String s = String.format("%.2f", c); double val = Double.parseDouble(s); System.out.println(" val is :" + val); // now out put will be : val is :1.67 } 

Use java.math.BigDecimal

Los dobles son fracciones binarias internas, por lo que a veces no pueden representar fracciones decimales con el decimal exacto.

Multiplique todo por 100 y guárdelo en centavos largos.

Las computadoras almacenan números en binario y no pueden representar números como 33.333333333 o 100.0 exactamente. Esta es una de las cosas complicadas sobre el uso de dobles. Tendrá que redondear la respuesta antes de mostrársela a un usuario. Afortunadamente en la mayoría de las aplicaciones, no necesita tantos decimales de ninguna manera.

Los números de coma flotante difieren de los números reales en que para cualquier número de coma flotante dado hay un número de punto flotante siguiente. Igual que enteros. No hay un número entero entre 1 y 2.

No hay forma de representar 1/3 como un flotador. Hay un flotador debajo y hay una carroza encima, y ​​hay una cierta distancia entre ellos. Y 1/3 está en ese espacio.

Apfloat para Java afirma que funciona con números de punto flotante de precisión arbitraria, pero nunca lo he usado. Probablemente vale la pena echarle un vistazo. http://www.apfloat.org/apfloat_java/

Se hizo una pregunta similar aquí antes de la biblioteca de alta precisión de punto flotante de Java

Los dobles son aproximaciones de los números decimales en su fuente de Java. Estás viendo la consecuencia de la falta de coincidencia entre el doble (que es un valor codificado en binario) y tu fuente (que está codificada en decimales).

Java está produciendo la aproximación binaria más cercana. Puede usar java.text.DecimalFormat para mostrar un valor decimal que se vea mejor.

Use un BigDecimal. Incluso le permite especificar reglas de redondeo (como ROUND_HALF_EVEN, que minimizará el error estadístico al redondear al vecino par si ambas son de la misma distancia, es decir, tanto 1.5 y 2.5 redondas a 2).

Echa un vistazo a BigDecimal, maneja problemas relacionados con aritmética de punto flotante como ese.

La nueva llamada se vería así:

 term[number].coefficient.add(co); 

Use setScale () para establecer el número de precisión de lugar decimal que se utilizará.

¿Por qué no usar el método round () de la clase Math?

 // The number of 0s determines how many digits you want after the floating point // (here one digit) total = (double)Math.round(total * 10) / 10; System.out.println(total); // prints 11.4 

Si no tiene otra opción que no sea usar valores dobles, puede usar el siguiente código.

 public static double sumDouble(double value1, double value2) { double sum = 0.0; String value1Str = Double.toString(value1); int decimalIndex = value1Str.indexOf("."); int value1Precision = 0; if (decimalIndex != -1) { value1Precision = (value1Str.length() - 1) - decimalIndex; } String value2Str = Double.toString(value2); decimalIndex = value2Str.indexOf("."); int value2Precision = 0; if (decimalIndex != -1) { value2Precision = (value2Str.length() - 1) - decimalIndex; } int maxPrecision = value1Precision > value2Precision ? value1Precision : value2Precision; sum = value1 + value2; String s = String.format("%." + maxPrecision + "f", sum); sum = Double.parseDouble(s); return sum; } 

No desperdicie su esfuerzo usando BigDecimal. En 99.99999% de casos, no lo necesita. El tipo doble java es de código aproximado, pero en casi todos los casos, es lo suficientemente preciso. Tenga en cuenta que tiene un error en el 14 ° dígito significativo. ¡Esto es realmente insignificante!

Para obtener un buen resultado de salida:

 System.out.printf("%.2f\n", total);