2012-03-20 5 views
13

Je sais que le comportement suivant est un vieux problème, mais je ne comprends toujours pas.Java BigDecimal problèmes de précision

System.out.println(0.1 + 0.1 + 0.1);  

Ou même si j'utilise BigDecimal

System.out.println(new BigDecimal(0.1).doubleValue() 
    + new BigDecimal(0.1).doubleValue() 
    + new BigDecimal(0.1).doubleValue()); 

Pourquoi ce résultat est: 0.30000000000000004 au lieu de: 0.3?

Comment puis-je résoudre ce problème?

+12

... Vous n'utilisez pas 'BigDecimal'. Vous faites * exactement la même chose * que d'ajouter les doubles. 'doubleValue()' renvoie ... un double. Voir le Javadoc pour 'BigDecimal' sur comment ajouter/soustraire/etc –

+0

Re les calculs' 'double'', voir [Ce que tout scientifique informatique devrait savoir sur l'arithmétique en virgule flottante] (http://docs.oracle.com/cd/ E19957-01/806-3568/ncg_goldberg.html). Java résout cela en fournissant des options de format pour la sortie. –

+0

Pourquoi utilisez-vous 'doubleValue()' ??? Je pensais que tu voulais un type décimal. –

Répondre

26

Qu'est-ce que vous voulez réellement est

new BigDecimal("0.1") 
.add(new BigDecimal("0.1")) 
.add(new BigDecimal("0.1")); 

Le constructeur new BigDecimal(double) reçoit toute l'imprécision du double, donc le temps que vous avez dit 0.1, vous avez déjà présenté l'erreur d'arrondi. L'utilisation du constructeur String évite l'erreur d'arrondi associée au passage par le double.

+0

merci pour tout! Maintenant ... Je peux comprendre ça. ' // IL FONCTIONNE POUR MON PROBLÈME Double valeur = 0.1d; BigDecimal total = nouveau BigDecimal ("0.0"); pour (int i = 0; i <9; i ++) { \t total = total.add (nouveau BigDecimal (value.toString())); } System.out.print (total); \t // NE FONCTIONNE PAS System.out.print (0.1d + 0.1d + 0.1d); // NE TRAVAILLE PAS System.out.println (nouveau BigDecimal (0.1) .add (nouveau BigDecimal (0.1)) .add (nouveau BigDecimal (0.1))); // IT WORKS System.out.println (nouveau BigDecimal ("0.1"). Add (nouveau BigDecimal ("0.1")). Add (nouveau BigDecimal ("0.1"))); // IL FONCTIONNE, MAIS AVEC MOINS DE PRÉCISION System.out.println (0.1f + 0.1f + 0.1f); ' – Jefferson

+0

Une légère correction: _" imprécision du double "_ - ce n'est techniquement pas vrai. Le double peut parfaitement représenter '0.1' - avec une mantisse de 1 et un exposant de -1. C'est la conversion d'un «double» en un «BigDecimal» qui convertit la représentation en virgule flottante en une représentation binaire normale quelque part sur le chemin. C'est pourquoi le double constructeur est imparfait. – theeggman85

+2

Um, quoi? Non, ça ne peut pas. '0.1' ne peut pas être représenté comme une fraction en binaire, période, ce qui est la façon dont les doubles sont représentés. Une mantisse de 1 et un exposant de -1 vous donnent 0.5. –

3

Essayez ceci:

BigDecimal sum = new BigDecimal(0.1).add(new BigDecimal(0.1)).add(new BigDecimal(0.1)); 

EDIT: En fait, regardant par-dessus le Javadoc, cela aura le même problème que l'original. Le constructeur BigDecimal(double) fera un BigDecimal correspondant à la représentation exacte en virgule flottante de 0,1, ce qui n'est pas exactement égal à 0,1.

Ceci, cependant, donne le résultat exact, étant donné que les entiers peuvent toujours être exprimés exactement dans la représentation à virgule flottante:

BigDecimal one = new BigDecimal(1); 
BigDecimal oneTenth = one.divide(new BigDecimal(10)); 

BigDecimal sum = oneTenth.add(oneTenth).add(oneTenth); 
+5

N'utilisez JAMAIS le constructeur 'double' pour les grandes décimales (eh bien il peut y avoir des situations rares, mais c'est vraiment une mauvaise idée). Si vous le pouvez, utilisez le constructeur de la chaîne (ce qui sera exact), si vous avez déjà un double usage 'valueOf', de cette façon nous n'obtenons pas de précision supplémentaire ... – Voo

3

Ce n'est pas un problème de Java, mais plutôt un problème d'ordinateurs en général. Le problème central réside dans la conversion du format décimal (format humain) au format binaire (format informatique). Certains nombres au format décimal ne sont pas représentables au format binaire sans décimales répétitives infinies. Par exemple, 0,3 décimal est 0,01001100 ... binaire Mais un ordinateur a un nombre limité de "slots" (bits) pour enregistrer un nombre, il ne peut donc pas enregistrer toute la représentation infinie. Il enregistre seulement 0.01001100110011001100 (par exemple). Mais ce nombre en décimal n'est plus 0.3, mais 0.30000000000000004 à la place.

+0

http://en.wikipedia.org/ wiki/Binary-coded_decimal –

8

Premier jamais, n'utilisez jamais le double constructeur de BigDecimal. Cela peut être la bonne chose dans quelques situations mais la plupart du temps ce n'est pas le cas

Si vous pouvez contrôler votre entrée, utilisez le constructeur BigDecimal String comme cela a déjà été proposé. De cette façon, vous obtenez exactement ce que vous voulez. Si vous avez déjà un double (peut arriver après tout), n'utilisez pas le double constructeur mais plutôt la méthode statique valueOf. Cela a le bel avantage que nous obtenons la représentation canonique du double qui atténue au moins le problème .. et le résultat est généralement beaucoup plus intuitif.

2

Le problème que vous avez est que 0,1 est représenté avec un nombre légèrement plus élevé, par ex.

System.out.println(new BigDecimal(0.1)); 

impressions

0.1000000000000000055511151231257827021181583404541015625 

Le Double.toString() prend en compte cette erreur de représentation pour que vous ne la voyiez pas.

De même 0,3 est représenté par une valeur légèrement inférieure à ce qu'elle est réellement.

0.299999999999999988897769753748434595763683319091796875 

Si vous multipliez la valeur représentée de 0,1 par 3 vous ne recevez pas la valeur représentée pour 0,3, vous plutôt obtenir quelque chose d'un peu plus

0.3000000000000000166533453693773481063544750213623046875 

Ce n'est pas seulement une erreur de représentation, mais également une erreur d'arrondi provoquée par les opérations. C'est plus que le Double.toString() corrigera et ainsi vous voyez l'erreur d'arrondi.

La morale de l'histoire, si vous utilisez float ou double également autour de la solution de manière appropriée.

double d = 0.1 + 0.1 + 0.1; 
System.out.println(d); 
double d2 = (long)(d * 1e6 + 0.5)/1e6; // round to 6 decimal places. 
System.out.println(d2); 

impressions

0.30000000000000004 
0.3