Pourquoi l’ajout de plusieurs fois à 0,1 rest-t-il sans perte?

Je sais que le nombre décimal de 0.1 ne peut pas être représenté exactement avec un nombre binary fini ( explication ), donc le double n = 0.1 perdra une certaine précision et ne sera pas exactement 0.1 . Par contre, 0.5 peut être représenté exactement parce que c’est 0.5 = 1/2 = 0.1b .

Cela dit, il est compréhensible que l’ajout de 0.1 trois fois ne donne pas exactement 0.3 donc le code suivant s’imprime false :

 double sum = 0, d = 0.1; for (int i = 0; i < 3; i++) sum += d; System.out.println(sum == 0.3); // Prints false, OK 

Mais alors comment se fait-il que l’ajout de 0.1 cinq fois donne exactement 0.5 ? Le code suivant imprime true :

 double sum = 0, d = 0.1; for (int i = 0; i < 5; i++) sum += d; System.out.println(sum == 0.5); // Prints true, WHY? 

Si 0.1 ne peut pas être représenté exactement, comment se fait-il que l’ajout de 5 fois donne exactement 0.5 ce qui peut être représenté avec précision?

L’erreur d’arrondi n’est pas aléatoire et la façon dont elle est implémentée tente de minimiser l’erreur. Cela signifie que parfois l’erreur n’est pas visible ou qu’il n’y a pas d’erreur.

Par exemple, 0.1 n’est pas exactement 0.1 c’est-à-dire new BigDecimal("0.1") < new BigDecimal(0.1) mais 0.5 est exactement 1.0/2

Ce programme vous montre les vraies valeurs impliquées.

 BigDecimal _0_1 = new BigDecimal(0.1); BigDecimal x = _0_1; for(int i = 1; i <= 10; i ++) { System.out.println(i+" x 0.1 is "+x+", as double "+x.doubleValue()); x = x.add(_0_1); } 

estampes

 0.1000000000000000055511151231257827021181583404541015625, as double 0.1 0.2000000000000000111022302462515654042363166809082031250, as double 0.2 0.3000000000000000166533453693773481063544750213623046875, as double 0.30000000000000004 0.4000000000000000222044604925031308084726333618164062500, as double 0.4 0.5000000000000000277555756156289135105907917022705078125, as double 0.5 0.6000000000000000333066907387546962127089500427246093750, as double 0.6000000000000001 0.7000000000000000388578058618804789148271083831787109375, as double 0.7000000000000001 0.8000000000000000444089209850062616169452667236328125000, as double 0.8 0.9000000000000000499600361081320443190634250640869140625, as double 0.9 1.0000000000000000555111512312578270211815834045410156250, as double 1.0 

Remarque: cette valeur est légèrement inférieure à 0.3 , mais lorsque vous atteignez 0.4 les bits doivent être abaissés pour correspondre à la limite de 53 bits et l'erreur est rejetée. Encore une fois, une erreur revient pour 0.6 et 0.7 mais pour 0.8 à 1.0 l'erreur est rejetée.

L'append 5 fois devrait cumuler l'erreur, pas l'annuler.

La raison pour laquelle il y a une erreur est due à une précision limitée. à savoir 53 bits. Cela signifie que lorsque le nombre utilise plus de bits à mesure qu’il grossit, les bits doivent être supprimés de la fin. Cela provoque l'arrondi qui dans ce cas est en votre faveur.
Vous pouvez obtenir l'effet inverse lorsque vous obtenez un nombre plus petit, par exemple 0.1-0.0999 => 1.0000000000000286E-4 et que vous voyez plus d'erreurs qu'auparavant.

Un exemple de ceci est pourquoi dans Java 6 Pourquoi Math.round (0.49999999999999994) renvoie 1 Dans ce cas, la perte d'un bit de calcul entraîne une grande différence par rapport à la réponse.

Limitation du dépassement, en virgule flottante, x + x + x est exactement le nombre à virgule flottante correctement arrondi (c’est-à-dire le plus proche) au réel 3 * x , x + x + x + x est exactement 4 * x et x + x + x + x + x est à nouveau l’approximation à virgule flottante correctement arrondie pour 5 * x .

Le premier résultat, pour x + x + x , découle du fait que x + x est exact. x + x + x est donc le résultat d’un seul arrondi.

Le deuxième résultat est plus difficile, une démonstration en est discutée ici (et Stephen Canon fait allusion à une autre parsing de preuve par cas sur les 3 derniers chiffres de x ). Pour résumer, soit 3 * x est dans la même binade que 2 * x ou il est dans la même binade que 4 * x , et dans chaque cas, il est possible de déduire que l’erreur sur la troisième addition annule l’erreur sur la seconde addition (le premier ajout étant exact, comme nous l’avons déjà dit).

Le troisième résultat, ” x + x + x + x + x est correctement arrondi”, dérive du second de la même manière que le premier dérive de l’exactitude de x + x .


Le second résultat explique pourquoi 0.1 + 0.1 + 0.1 + 0.1 est exactement le nombre à virgule flottante 0.4 : les nombres rationnels 1/10 et 4/10 sont approchés de la même façon, avec la même erreur relative, lorsqu’ils sont convertis en virgule flottante. Ces nombres à virgule flottante ont un ratio de exactement 4 entre eux. Les premier et troisième résultats montrent que 0.1 + 0.1 + 0.1 et 0.1 + 0.1 + 0.1 + 0.1 + 0.1 peuvent présenter moins d’erreurs que l’parsing naïve des erreurs, mais en eux-mêmes, ils ne concernent que les résultats respectivement. 3 * 0.1 et 5 * 0.1 , qui peuvent être proches mais pas nécessairement identiques à 0.3 et 0.5 .

Si vous continuez à append 0.1 après la quasortingème addition, vous observerez enfin des erreurs d’arrondi qui font que « 0.1 ajouté à lui-même n fois» divergent de n * 0.1 et divergent encore plus de n / 10. Si vous deviez tracer les valeurs de «0.1 ajouté à lui-même n fois» en fonction de n, vous observeriez des lignes de pente constante par des binades (dès que le résultat de la nième addition est destiné à tomber dans un binade particulier, on peut s’attendre à ce que les propriétés de l’addition soient similaires aux ajouts précédents qui ont produit un résultat dans la même binade). Dans une même binade, l’erreur augmentera ou diminuera. Si vous regardez la séquence des pentes de binade à binade, vous reconnaîtrez les chiffres répétés de 0.1 en binary pendant un moment. Après cela, l’absorption commencerait à se produire et la courbe deviendrait plate.

Les systèmes à virgule flottante effectuent diverses opérations de magie, y compris quelques points de précision supplémentaires pour l’arrondissement. Ainsi, la très petite erreur due à la représentation inexacte de 0,1 finit par être arrondie à 0,5.

Pensez à virgule flottante comme étant un excellent moyen INEXACT de représenter des nombres. Tous les nombres possibles ne sont pas facilement représentés sur un ordinateur. Des nombres irrationnels comme PI. Ou comme SQRT (2). (Les systèmes mathématiques symboliques peuvent les représenter, mais j’ai dit “facilement”.)

La valeur à virgule flottante peut être extrêmement proche, mais pas exacte. Il est peut-être si proche que vous pourriez naviguer vers Pluton et sortir de quelques millimètres. Mais toujours pas exact au sens mathématique.

N’utilisez pas de virgule flottante lorsque vous devez être exact plutôt qu’approximatif. Par exemple, les applications comptables veulent garder une trace exacte d’un certain nombre de centimes dans un compte. Les entiers sont bons pour cela car ils sont exacts. Le problème principal que vous devez surveiller avec les entiers est le débordement.

L’utilisation de BigDecimal pour la devise fonctionne bien car la représentation sous-jacente est un entier, même s’il est grand.

Reconnaissant que les nombres à virgule flottante sont inexacts, ils ont encore de nombreuses utilisations. Systèmes de coordonnées pour la navigation ou les coordonnées dans les systèmes graphiques. Valeurs astronomiques. Valeurs scientifiques (De toute façon, vous ne pouvez probablement pas connaître la masse exacte d’une balle de baseball dans une masse d’électron, alors l’inexactitude n’a pas vraiment d’importance.)

Pour compter les applications (y compris la comptabilité), utilisez un entier. Pour compter le nombre de personnes qui passent par une porte, utilisez int ou long.