2015-04-16 2 views
3

Quand j'exécute le code ci-dessous:Java DecimalFormat perte de précision lors du formatage à double

public class Test { 
    public static void main(String args[]){ 
     DecimalFormat format = new DecimalFormat(); 
     Double value = new Double(-1350825904190559999913623552.00); 

     StringBuffer buffer = new StringBuffer(); 
     FieldPosition position = new FieldPosition(0); 
     format.format(new BigDecimal(value), buffer, position); 
     System.out.println(buffer); 
    } 
} 

Cette impression est correcte -1,350,825,904,190,559,999,913,623,552. J'ai du code qui passe par beaucoup de doubles donc je ne veux pas la conversion du double en bigdecimal. J'ai pensé que le temps de traitement pour BigDecimal est grand. Donc je fais format.format(value, buffer, position) Et je vois la précision est perdue. La sortie obtenue est -1,350,825,904,190,560,000,000,000,000.

Qu'est-ce que je fais mal ici? Y a-t-il une meilleure façon de gérer cela et de conserver la précision? Je ne veux pas traiter avec BigDecimals ici, mais juste travailler avec les décimales.

Des suggestions?

+0

peut-être il est logique d'afficher le code qui fournit des résultats incorrects aussi? –

+0

Etes-vous sûr que cela compte réellement? Si vous aviez 'Double valeur = new Double (0.1)', voulez-vous voir la précision complète (qui est '0.1000000000000000055511151231257827021181583404541015625')? – user2357112

+0

@ user2357112 umm im parler de grands nombres comme 1350825904190559999913623552.00 ici. Alors oui, c'est important. –

Répondre

2

double n'a pas une précision infinie, et vous ne pouvez pas gagner plus de précision qu'un double a en convertissant un double à un BigDecimal (comme vous ne pouvez pas gagner plus de précision avec un int quand vous faites double r = 1/3; qui est 0.0 parce que élargit et int à double). Au lieu de cela, vous pouvez utiliser un String. Quelque chose comme

DecimalFormat format = new DecimalFormat(); 
String value = "-1350825904190559999913623552.00"; 
System.out.println(format.format(new BigDecimal(value))); 
+0

Mais je pense que la précision est seulement perdue pendant le formatage. Si la précision a été perdue lors de la création du double, pourquoi la conversion en bigdécimal le retient-elle. Donc, il ne tombe pas dans le même cas que "vous ne pouvez pas gagner plus de précision qu'un double a en convertissant un double en un BigDecimal", n'est-ce pas? –

+0

Vous avez dit "beaucoup de doubles". Quoi de 0.1 + 0.2? –

0

Vous ne pouvez pas représenter 1350825904190559999913623552,00 avec précision avec un Double. Si vous voulez savoir pourquoi, explorez ce article.

Si vous voulez représenter la valeur, je vous conseille d'utiliser le code que vous avez utilisé dans votre question: new BigDecimal(value), où value est en fait une représentation String.

+0

Certains, mais pas tous, les nombres entre 2^53 et Double.MAX_VALUE sont exactement représentables, et cela me semble être l'un d'entre eux. –

+0

http://en.wikipedia.org/wiki/Floating_point#IEEE_754%3a_floating_point_in_modern_computers – Zyn

+1

Je suis familier avec cette page web, et il n'y a rien qui contredise l'affirmation selon laquelle certains grands nombres sont exactement représentables, et c'est l'un des leur. Vous pouvez le vérifier en utilisant un [Convertisseur décimal en virgule flottante] (http://www.exploringbinary.com/floating-point-converter/) –

1

Il n'est pas perdu pendant le formatage. Il est perdu ici:

Double value = new Double(-1350825904190559999913623552.00); 

A double a seulement environ 15,9 chiffres décimaux significatifs. Ça ne va pas. Il y avait une perte de précision au moment de la compilation lorsque le littéral à virgule flottante était converti.

1

Le problème est dans le formatage de sortie, en particulier la façon dont les doublons sont convertis en chaînes par défaut. Chaque double nombre a une valeur exacte, mais c'est aussi le résultat d'une chaîne à double conversion pour une gamme de fractions décimales. Dans ce cas, la valeur exacte du double est -1350825904190559999913623552, mais la plage est [-1350825904190560137352577024, -1350825904190559862474670080].

La conversion Double toString sélectionne le nombre de cette plage avec le moins de chiffres significatifs, -1.35082590419056E27. Cette chaîne retourne à la valeur d'origine.

Si vous voulez vraiment voir la valeur exacte, pas seulement assez de chiffres pour identifier le double de façon unique, votre approche BigDecimal actuelle fonctionne bien.

Voici le programme que j'utilisé pour calculer les chiffres dans cette réponse:

import java.math.BigDecimal; 

public class Test { 
    public static void main(String args[]) { 
    double value = -1350825904190559999913623552.00; 
    /* Get an exact printout of the double by conversion to BigDecimal 
    * followed by BigDecimal output. Both those operations are exact. 
    */ 
    BigDecimal bdValue = new BigDecimal(value); 
    System.out.println("Exact value: " + bdValue); 
    /* Determine whether the range is open or closed. The half way 
    * points round to even, so they are included in the range for a number 
    * with an even significand, but not for one with an odd significand. 
    */ 
    boolean isEven = (Double.doubleToLongBits(value) & 1) == 0; 
    /* Find the lower bound of the range, by taking the mean, in 
    * BigDecimal arithmetic for exactness, of the value and the next 
    * exactly representable value in the negative infinity direction. 
    */ 
    BigDecimal nextDown = new BigDecimal(Math.nextAfter(value, 
     Double.NEGATIVE_INFINITY)); 
    BigDecimal lowerBound = bdValue.add(nextDown).divide(BigDecimal.valueOf(2)); 
    /* Similarly, find the upper bound of the range by going in the 
    * positive infinity direction. 
    */ 
    BigDecimal nextUp = new BigDecimal(Math.nextAfter(value, 
     Double.POSITIVE_INFINITY)); 
    BigDecimal upperBound = bdValue.add(nextUp).divide(BigDecimal.valueOf(2)); 
    /* Output the range, with [] if closed,() if open.*/ 
    System.out.println("Range: " + (isEven ? "[" : "(") + lowerBound + "," 
     + upperBound + (isEven ? "]" : ")")); 
    /* Output the result of applying Double's toString to the value.*/ 
    String valueString = Double.toString(value); 
    System.out.println("toString result: " + valueString); 
    /* And use BigDecimal as above to print the exact value of the result 
    * of converting the toString result back again. 
    */ 
    System.out.println("exact value of toString result as double: " 
     + new BigDecimal(Double.parseDouble(valueString))); 
    } 
} 

Sortie:

Exact value: -1350825904190559999913623552 
Range: [-1350825904190560137352577024,-1350825904190559862474670080] 
toString result: -1.35082590419056E27 
exact value of toString result as double: -1350825904190559999913623552 
+0

J'aime votre explication. le programme est un peu consufing pour moi though.So deux questions que j'ai est 1) quelle est cette gamme [-1350825904190560137352577024, -1350825904190559862474670080] 2) D'après ce que vous dites, le formatage du double dans un tel cas montre seulement les chiffres assez pour identifier de façon unique de la gamme? même si le double est dans la gamme de 2^53 et Double.MAX_VALUE, le formatage ne tiendra pas compte de tous les chiffres corrects? –

+0

Désolé, je pense que je l'ai compris! C'est une bonne explication. Je suis intrigué par le fait que la chaîne pour doubler les conversions pour la gamme de décimales que vous avez spécifiée est la même. De meilleurs liens pour lire à ce sujet? –

+0

Le fichier JLS fait référence à la documentation API de java.lang.Double [valueOf] (https://docs.oracle.com/javase/8/docs/api/java/lang/Double.html#valueOf-java.lang. String-) pour la chaîne à double conversion. En particulier, "cette valeur numérique exacte est ensuite convertie conceptuellement en une valeur binaire" infiniment précise "qui est ensuite arrondie au type double par la règle arrondie habituelle de l'arithmétique en virgule flottante IEEE 754". –