web-dev-qa-db-fra.com

Pourquoi l'ajout de 0.1 fois multiples reste sans perte?

Je connais le 0.1 Le nombre décimal ne peut pas être représenté exactement par un nombre binaire fini ( explication ), donc double n = 0.1 va perdre de la précision et ne sera pas exactement 0.1. D'autre part 0.5 _ peut être représenté exactement parce que c'est 0.5 = 1/2 = 0.1b.

Cela dit, il est compréhensible d’ajouter 0.1 trois fois ne donnera pas exactement 0.3 donc le code suivant affiche 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 donnera 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’ajouter 5 fois donne exactement 0.5 qui peut être représenté précisément?

146
icza

L'erreur d'arrondi n'est pas aléatoire et sa 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 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);
}

empreintes

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: que 0.3 Est légèrement désactivé, mais lorsque vous atteignez 0.4, Les bits doivent en décaler d’un pour s’adapter à la limite de 53 bits et l’erreur est supprimée. Encore une fois, une erreur réapparaît pour 0.6 Et 0.7, Mais pour 0.8 À 1.0, L'erreur est supprimée.

L'ajouter 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. c'est-à-dire 53 bits. Cela signifie que, comme le nombre utilise plus de bits à mesure qu'il augmente, il faut supprimer les bits à la fin. Cela provoque un arrondi qui dans ce cas est en votre faveur.
Vous pouvez obtenir l’effet inverse en obtenant un nombre inférieur, par exemple. 0.1-0.0999 => 1.0000000000000286E-4 Et vous voyez plus d'erreur qu'avant.

Voici un exemple: Java 6 Pourquoi Math.round (0.49999999999999994) renvoie-t-il 1 ) Dans ce cas, la perte d’un bit dans le calcul entraîne une différence importante à la réponse.

150
Peter Lawrey

Sauf en cas de dépassement, en virgule flottante, x + x + x Est exactement le nombre à virgule flottante correctement arrondi (c'est-à-dire le plus proche) du nombre réel 3 * x, x + x + x + x Est exactement 4 * x, et x + x + x + x + x est à nouveau l'approximation en 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, on en discute une démonstration ici (et Stephen Canon fait allusion à une autre analyse cas par cas sur les 3 derniers chiffres de x). Pour résumer, soit 3 * x est dans le même binade que 2 * x ou 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 ajout (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 deuxième de la même manière que le premier dérive de l'exactitude de x + x.


Le deuxième 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 approximés de la même manière, avec la même erreur relative, lorsqu’ils sont convertis en point flottant. Ces nombres à virgule flottante ont un rapport d'exactement 4 entre eux. Les premier et troisième résultats montrent que l'on peut s'attendre à ce que 0.1 + 0.1 + 0.1 Et 0.1 + 0.1 + 0.1 + 0.1 + 0.1 Aient moins d'erreur que ce qui pourrait en être déduit par l'analyse d'erreur naïve, mais, en eux-mêmes, ils ne font que relier les résultats à 3 * 0.1 Et 5 * 0.1, Qui peuvent être proches mais pas nécessairement identiques à 0.3 Et 0.5.

Si vous continuez d’ajouter 0.1 Après le quatrième ajout, vous remarquerez enfin des erreurs d’arrondi qui font que "0.1 S’ajoute n fois" diverge de n * 0.1, Et 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 du nième ajout est destiné à tomber dans un binade particulier, on peut s’attendre à ce que les propriétés de l’addition soient similaires aux additions précédentes produisant un résultat dans le même binade). Au sein d’un même binade, l’erreur augmentera ou diminuera. Si vous regardiez la séquence des pentes de binade en binade, vous reconnaîtriez les chiffres répétés de 0.1 En binaire pendant un moment. Après cela, l'absorption commencerait à se faire et la courbe deviendrait plate.

46
Pascal Cuoq