Je sais que le 0.1
nombre décimal ne peut pas être représenté exactement avec un nombre binaire fini ( explication ), donc double n = 0.1
perdra une certaine précision et ne sera pas exactement 0.1
. D'autre part 0.5
peut être représenté exactement parce que c'est le cas 0.5 = 1/2 = 0.1b
.
Cela dit, il est compréhensible que l'ajout de 0.1
trois fois ne donne pas exactement de 0.3
sorte que 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 s'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
on ne peut pas être représenté exactement, comment se fait-il que l'ajouter 5 fois donne exactement 0.5
ce qui peut être représenté précisément?
sum
avait la même valeur finale que si la boucle était vraiment exécutée. Dans la norme C ++, cela s'appelle la "règle as-si" ou "même comportement observable".Réponses:
L'erreur d'arrondi n'est pas aléatoire et la façon dont elle est mise en œuvre 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 exactement0.1
ienew BigDecimal("0.1") < new BigDecimal(0.1)
mais0.5
est exactement1.0/2
Ce programme vous montre les vraies valeurs impliquées.
impressions
Remarque: c'est
0.3
légèrement désactivé, mais lorsque vous arrivez aux0.4
bits, vous devez en décaler un pour s'adapter à la limite de 53 bits et l'erreur est écartée. Encore une fois, une chair de poule d'erreur se reconnecter pour0.6
et0.7
mais0.8
à1.0
l'erreur est mis au rebut.La raison d'une erreur est due à une précision limitée. c'est-à-dire 53 bits. Cela signifie que comme le nombre utilise plus de bits à mesure qu'il augmente, les bits doivent être supprimés à la fin. Cela provoque un arrondi qui dans ce cas est en votre faveur.
Vous pouvez obtenir l'effet inverse en obtenant un nombre plus petit, par exemple
0.1-0.0999
=>1.0000000000000286E-4
et vous voyez plus d'erreur 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 dans le calcul entraîne une grande différence dans la réponse.
la source
strictfp
Time pour considérer les entiers à virgule fixe, je pense. (ou BigDecimal)Le dépassement de capacité, en virgule flottante,
x + x + x
est exactement le nombre à virgule flottante correctement arrondi (c'est-à-dire le plus proche) du réel 3 *x
,x + x + x + x
est exactement 4 *x
, etx + x + x + x + x
est à nouveau l'approximation à virgule flottante correctement arrondie pour 5 *x
.Le premier résultat, car
x + x + x
, vient du fait quex + x
c'est exact.x + x + x
est donc le résultat d'un seul arrondi.Le second résultat est plus difficile, une démonstration en est discutée ici (et Stephen Canon fait allusion à une autre analyse de preuve par cas sur les 3 derniers chiffres de
x
). Pour résumer, soit 3 *x
est dans le même binade que 2 *x
soit il est dans le même binade que 4 *x
, et dans chaque cas il est possible de déduire que l'erreur sur le troisième ajout annule l'erreur sur le deuxième 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 dex + x
.Le deuxième résultat explique pourquoi
0.1 + 0.1 + 0.1 + 0.1
est exactement le nombre à virgule flottante0.4
: les nombres rationnels 1/10 et 4/10 sont approximés de la même manière, avec la même erreur relative, lorsqu'ils sont convertis en virgule flottante. Ces nombres à virgule flottante ont un rapport d'exactement 4 entre eux. Le premier et le troisième résultat montrent que0.1 + 0.1 + 0.1
et que l'0.1 + 0.1 + 0.1 + 0.1 + 0.1
on peut s'attendre à avoir moins d'erreur que ce qui pourrait être déduit par une analyse d'erreur naïve, mais, en eux-mêmes, ils ne relient que les résultats respectivement à3 * 0.1
et5 * 0.1
, dont on peut s'attendre à ce qu'ils soient proches mais pas nécessairement identiques à0.3
et0.5
.Si vous continuez à ajouter
0.1
après la quatrième addition, vous observerez finalement des erreurs d'arrondi qui font que «0.1
ajouté à lui-même n fois» divergen * 0.1
et diverge 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 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 le même binade). Dans un même binade, l'erreur augmentera ou diminuera. Si vous regardiez la séquence des pentes de binade à binade, vous reconnaîtriez les chiffres répétés de0.1
en binaire pendant un moment. Après cela, l'absorption commencerait à se produire et la courbe deviendrait plate.la source
x + x + x
c'est exactement le nombre à virgule flottante correctement arrondi au vrai 3 *x
. «Correctement arrondi» signifie «le plus proche» dans ce contexte.Les systèmes à virgule flottante font diverses magies, y compris avoir quelques bits supplémentaires de précision pour l'arrondi. Ainsi, la très petite erreur due à la représentation inexacte de 0,1 finit par être arrondie à 0,5.
Pensez à la virgule flottante comme étant une manière excellente mais INEXACTE de représenter les nombres. Tous les nombres possibles ne sont pas facilement représentés dans 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 en virgule flottante peut être extrêmement proche, mais pas exacte. Il est peut-être si proche que vous pourriez naviguer vers Pluton et vous éloigner 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 de comptabilité veulent garder une trace exacte d'un certain nombre de centimes dans un compte. Les nombres entiers sont bons pour cela car ils sont exacts. Le problème principal que vous devez surveiller avec les entiers est le dépassement de capacité.
L'utilisation de BigDecimal pour la devise fonctionne bien car la représentation sous-jacente est un entier, bien que gros.
Reconnaissant que les nombres à virgule flottante sont inexacts, ils ont encore de nombreuses utilisations. Systèmes de coordonnées pour la navigation ou 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 à une masse d'un électron, donc l'inexactitude n'a pas vraiment d'importance.)
Pour les applications de comptage (y compris la comptabilité), utilisez un entier. Pour compter le nombre de personnes qui franchissent une porte, utilisez int ou long.
la source
strictfp
). Ce n'est pas parce que vous avez renoncé à comprendre quelque chose que cela ne signifie pas qu'il est insondable ni que les autres devraient renoncer à le comprendre. Voir stackoverflow.com/questions/18496560 comme exemple des longueurs auxquelles les implémentations Java iront pour implémenter la définition du langage (qui n'inclut aucune disposition pour les bits de précision supplémentaires ni, avecstrictfp
, pour tout bit d'exp supplémentaire)