Conserva la precisión con doble en Java

Respuestas:

151

Como otros han mencionado, probablemente querrás usar el BigDecimal clase, si quieres tener una representación exacta de 11.4.

Ahora, una pequeña explicación de por qué sucede esto:

Los tipos floaty doubleprimitivos 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 coma flotante de doble precisión como el doubletipo 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 binario).

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

(Fuente: Wikipedia: Doble precisión )

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 del lenguaje Java.

Los byte, char, int, longtipos son de punto fijo números, que son representions exactas de números. A diferencia de los números de punto fijo, los números de coma flotante algunas veces (es seguro asumir "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 terminas 11.399999999999como resultado de 5.6 + 5.8.

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

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

De la referencia de la API de Java para la BigDecimalclase:

Inmutables, números decimales con precisión arbitraria. Un BigDecimal consiste en un valor entero sin escala 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 Stack Overflow 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:

Si realmente quiere llegar a los detalles esenciales de los números de coma flotante, eche un vistazo a Lo que todo informático debe saber sobre la aritmética de coma flotante .

Coobird
fuente
3
De hecho, generalmente hay 53 bits significativos porque el 1 antes del punto "decimal" está implícito para todos los valores, excepto los desnormalizados, lo que proporciona un bit adicional de precisión. por ejemplo, 3 se almacena como (1.) 1000 ... x 2 ^ 1 mientras que 0.5 se almacena como (1.) 0000 ... x 2 ^ -1 Cuando el valor está desnormalizado (todos los bits exponentes son cero) puede, y generalmente será menos dígitos significativos, por ejemplo, 1 x 2 ^ -1030 se almacena como (0.) 00000001 x 2 ^ -1022, por lo que se han sacrificado siete dígitos significativos a escala.
Sarah Phillips
1
Cabe señalar que, si bien BigDecimales mucho más lento que doubleen este caso, no es necesario ya que el doble tiene 15 decimales de precisión, solo necesita redondear.
Peter Lawrey
2
@PeterLawrey Tiene 15 dígitos decimales de precisión, si todos están antes del punto decimal. Cualquier cosa puede suceder después del punto decimal, debido a la inconmensurabilidad de las fracciones decimales y binarias.
Marqués de Lorne
@EJP Tienes razón, tiene alrededor de 15 dígitos significativos de precisión. Puede ser 16 pero es más seguro asumir que es 15 o tal vez 14.
Peter Lawrey
La corrección de @PeterLawrey EJP se debió a mi pregunta: stackoverflow.com/questions/36344758/… ¿ podría explicar por qué no es exactamente 15 y qué situaciones pueden ser 16 o 14?
Shivam Sinha
103

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

33.3333333333333285963817615993320941925048828125

Dividiendo eso por 100 da:

0.333333333333333285963817615993320941925048828125

que tampoco es representable como un número de doble precisión, 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
Stephen Canon
fuente
114
Para cualquiera que lea esto en el futuro y se pregunte por qué la respuesta no tiene nada que ver con la pregunta: un moderador decidió fusionar la pregunta que yo (y otros) había respondido con esta pregunta bastante diferente.
Stephen Canon
¿Cómo sabes el valor doble exacto?
Michael Yaworski,
@mikeyaworski en.wikipedia.org/wiki/Double-precision_floating-point_format Ver ejemplos de doble precisión
Jaydee
23

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

Escriba métodos para sumar, restar, multiplicar y dividir, así como un método toDouble. De esta manera puede evitar flotadores 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);
    }
}

}
Shah viral
fuente
1
Seguramente numeratory denominatordebe ser ints? ¿Por qué querrías precisión de punto flotante?
Samir Talwar
Supongo que no es realmente necesario, pero evita la conversión a la función toDouble para que el código se lea mejor.
Viral Shah
55
ViralShah: también introduce potencialmente un error de punto flotante cuando se trata de operaciones matemáticas. Dado que el objetivo de este ejercicio es evitar exactamente eso, parece prudente alterarlo.
Samir Talwar
Editado para usar ints en lugar de dobles, por las razones mencionadas anteriormente por Samir Talwar.
Viral Shah
3
Esta implementación de fracciones tiene problemas ya que no las reduce a una forma más simple. 2/3 * 1/2 da 2/6 donde realmente quieres que la respuesta sea 1/3. Idealmente, en el constructor, desea encontrar el mcd de numerador y divisor y dividir ambos por eso.
Salix alba
15

Observe que tendría el mismo problema si utilizara la 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 involucran 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 resultados exactos, use BigDecimal. O como han dicho otros, si sus valores son como el dinero en el sentido de que todos son múltiplos de 0.01, o 0.001, o algo así, multiplique todo por una potencia fija de 10 y use int o long (suma y resta son trivial: cuidado con la multiplicación).

Sin embargo, si está satisfecho con el binario para el cálculo, pero solo desea imprimir las cosas en un formato un poco más amigable, intente java.util.FormatteroString.format . En la cadena de formato, especifique una precisión menor que la precisión completa de un doble. Para 10 cifras significativas, digamos, 11.399999999999 es 11.4, por lo que el resultado será casi tan preciso y más legible para los humanos en los casos en que el resultado binario esté muy cerca de un valor que requiera solo unos pocos decimales.

La precisión para especificar depende un poco de la cantidad de matemáticas que haya hecho con sus números; en general, cuanto más lo haga, más error se acumulará, pero algunos algoritmos lo acumulan mucho más rápido que otros (se llaman "inestables" como opuesto a "estable" con respecto a los errores de redondeo). Si todo lo que está haciendo es agregar algunos valores, entonces supongo que soltar solo un lugar decimal de precisión resolverá las cosas. Experimentar.

Steve Jessop
fuente
3
¡No, no use doble con valores monetarios! Necesita precisión con el dinero, use BigDecimal en su lugar. De lo contrario, su respuesta es buena. Cualquier cosa con la que necesite precisión, use BigDecimal, si la precisión no es tan importante, puede usar flotante o doble.
MetroidFan2002
1
La pregunta ya no establece o implica que hay dinero involucrado. Digo específicamente que use BigDecimal o enteros por dinero. ¿Cuál es el problema?
Steve Jessop
1
E igual a "no use doble por dinero" es "no use BigDecimal o doble por tercios". Pero a veces un problema implica división, en cuyo caso todas las bases no divisibles por todos los factores primos de todos los denominadores son igualmente malas.
Steve Jessop
1
.9999 = 1 si su precisión es menor a 4 dígitos significativos
Brian Leahy
9

Es posible que desee considerar 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 . Si bien nunca puedes representar 1/3 como alguien mencionó, puedes tener el poder de decidir exactamente qué tan preciso desea que sea el resultado. setScale () es tu amigo .. :)

Ok, porque tengo mucho tiempo libre 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
 * [email protected]
 * 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 ordenado 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
Vinny
fuente
5

Estoy bastante seguro de que podrías haber hecho eso en un ejemplo de tres líneas. :)

Si desea precisión exacta, use BigDecimal. De lo contrario, puede usar ints multiplicados por 10 ^ cualquier precisión que desee.

Dustin
fuente
5

Como otros han señalado, no todos los valores decimales se pueden representar 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 es importante, usa BigDecimal, pero si solo quieres una salida amigable:

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

Te regalaré:

11.40
Draemon
fuente
5

No 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.

Echa 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 sea exactamente su problema:

  • Si es que no te gusta ver todos esos dígitos de ruido, entonces arregla el formato de tu cadena. No muestre más de 15 dígitos significativos (o 7 para flotante).
  • Si es que la inexactitud de sus números está rompiendo cosas como las declaraciones "if", entonces debe escribir if (abs (x - 7.3) <TOLERANCE) en lugar de if (x == 7.3).
  • Si está trabajando con dinero, entonces lo que probablemente quiera es un punto fijo decimal. Almacene un número entero de centavos o cualquiera que sea la unidad más pequeña de su moneda.
  • (MUY PROBABLE) Si necesita más de 53 bits significativos (15-16 dígitos significativos) de precisión, utilice un tipo de punto flotante de alta precisión, como BigDecimal.
dan04
fuente
7.3 pueden no tener una representación finita en binario, pero estoy seguro que se deje -7.3 cuando intento lo mismo en C ++
wrongusername
2
nombre de usuario incorrecto: No, no lo haces. Simplemente se muestra de esa manera. Use el formato "% .17g" (o mejor aún, "% .51g") para ver la respuesta real.
dan04
4
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
}
sravan
fuente
3

Use java.math.BigDecimal

Los dobles son fracciones binarias internamente, por lo que a veces no pueden representar fracciones decimales al decimal exacto.

Kevin Crowell
fuente
1
-1 por recomendar ciegamente BigDecimal. Si realmente no necesita aritmética decimal (es decir, si está haciendo cálculos con dinero), BigDecimal no lo ayuda. No resuelve todos sus errores de coma flotante: aún debe tratar con 1/3 * 3 = 0.9999999999999999999999999999 y sqrt (2) ** 2 = 1.999999999999999999999999999. Además, BigDecimal conlleva una gran penalización de velocidad. Peor aún, debido a la falta de sobrecarga del operador de Java, debe reescribir todo su código.
dan04
2
@ dan04 - Si haces el cálculo con dinero, ¿por qué usar representación flotante sabiendo el error inherente en él? ... Dado que no hay fracción de centavos, puedes usar decimales y calcular centavos en lugar de usar un dólar aproximado, tienes una cantidad exacta de centavo. Si realmente quiere la fracción de centavo, use un largo y calcule miles de centavos. Además, el OP no mencionó los números irracionales, lo único que le preocupaba era la suma. Lea la publicación detenidamente y comprenda el problema antes de responder, podría ahorrarle vergüenza.
Newtopian
3
@Newtopian: No tengo nada de qué avergonzarme. El PO hizo NO mención de dinero, ni ninguna indicación de que su problema no tiene ninguna decimalness inherente.
dan04
@ Dan04 - Sin el PO no ... que hizo y A ciegas ofrecido fuera de contexto opinión de lo que lo más probable era una respuesta perfectamente aceptable dada la escasa cantidad de datos proporcionada
Newtopian
2

Multiplique todo por 100 y guárdelo en centavos.

Paul Tomblin
fuente
2
@Draemon - mira la publicación antes de la última edición - todo eso "shoppingTotal" y "calcGST" y "calcPST" me parecen dinero.
Paul Tomblin
2

Las computadoras almacenan números en binario y en realidad no pueden representar números como 33.333333333 o 100.0 exactamente. Esta es una de las cosas difíciles 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 todos modos.

Jay Askren
fuente
Estoy haciendo algunos cálculos de probabilidades. Preferiría tener la mayor precisión posible. Pero entiendo que hay limitaciones
Aly
2

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

No hay forma de representar 1/3 como flotante. Hay un flotador debajo y hay un flotador 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 coma flotante de precisión arbitraria, pero nunca lo he usado. Probablemente vale la pena echarle un vistazo. http://www.apfloat.org/apfloat_java/

Aquí se hizo una pregunta similar antes de la biblioteca de alta precisión de coma flotante de Java

Espiga
fuente
1

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

Java produce la aproximación binaria más cercana. Puede usar java.text.DecimalFormat para mostrar un valor decimal más atractivo.

S.Lott
fuente
1

Utiliza 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 ambos tienen la misma distancia; es decir, tanto 1.5 como 2.5 redondean a 2).

Adam Jaskiewicz
fuente
1

Respuesta corta: siempre usa BigDecimal y asegúrate de estar usando el constructor con el argumento String , no el doble.

Volviendo a su ejemplo, el siguiente código imprimirá 11.4, como lo desee.

public class doublePrecision {
    public static void main(String[] args) {
      BigDecimal total = new BigDecimal("0");
      total = total.add(new BigDecimal("5.6"));
      total = total.add(new BigDecimal("5.8"));
      System.out.println(total);
    }
}
jackycflau
fuente
0

Echa un vistazo a BigDecimal, maneja problemas relacionados con la aritmética de coma flotante como esa.

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á.

Castillo oscuro
fuente
0

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

// 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
Señor Gato
fuente
0

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;
}
usuario5734382
fuente
-1

No malgastes tu efford usando BigDecimal. En el 99.99999% de los casos no lo necesita. Java Double Type es de origen aproximado pero en casi todos los casos, es lo suficientemente preciso. Tenga en cuenta que tiene un error en el decimocuarto dígito significativo.¡Esto es realmente insignificante!

Para obtener un buen resultado de uso:

System.out.printf("%.2f\n", total);
Maciek D.
fuente
2
Creo que le preocupa la salida, no la precisión numérica. y BigDecimal no sería de ayuda si, por ejemplo. dividir por tres. Incluso puede empeorar las cosas ...
Maciek D.
Nunca, nunca, nunca debes usar el punto flotante por dinero. He visto grandes modificaciones en un contratista que violó esta regla a pesar de estar tan instruido.
Marqués de Lorne