web-dev-qa-db-fra.com

Exemples d'inexactitude en virgule flottante

Comment expliquez-vous l'inexactitude des virgules flottantes aux nouveaux programmeurs et non-initiés qui pensent encore que les ordinateurs sont infiniment sages et précis?
Avez-vous un exemple favori ou une anecdote qui semble mieux faire comprendre l’idée qu’une explication précise, mais sèche?
Comment est-ce enseigné dans les cours d'informatique?

29
David Rutten

Il y a essentiellement deux pièges majeurs dans lesquels les gens trébuchent avec des nombres à virgule flottante.

  1. Le problème d'échelle. Chaque numéro FP a un exposant qui détermine «l'échelle» globale du nombre afin que vous puissiez représenter des valeurs très petites ou très grandes, bien que le nombre de chiffres que vous pouvez consacrer à ce nombre soit limité. L'ajout de deux nombres d'échelle différente aura parfois pour effet de «manger» le plus petit, car il n'y a aucun moyen de l'adapter à une échelle plus grande.

    PS> $a = 1; $b = 0.0000000000000000000000001
    PS> Write-Host a=$a b=$b
    a=1 b=1E-25
    PS> $a + $b
    1
    

    Comme analogie avec ce cas, vous pouvez imaginer une grande piscine et une cuillère à café d’eau. Les deux sont de tailles très différentes, mais individuellement, vous pouvez facilement comprendre à quel point ils sont grossièrement. En versant la cuillère à thé dans la piscine, vous resterez immobile avec une piscine remplie d’eau.

    (Si les personnes qui apprennent cela ont des problèmes avec la notation exponentielle, on peut également utiliser les valeurs 1 et 100000000000000000000 ou plus.)

  2. Vient ensuite le problème de la représentation binaire et décimale. Un nombre tel que 0.1 ne peut pas être représenté exactement avec un nombre limité de chiffres binaires. Certaines langues masquent cela, cependant:

    PS> "{0:N50}" -f 0.1
    0.10000000000000000000000000000000000000000000000000
    

    Mais vous pouvez “amplifier” l’erreur de représentation en additionnant plusieurs fois les nombres:

    PS> $sum = 0; for ($i = 0; $i -lt 100; $i++) { $sum += 0.1 }; $sum
    9,99999999999998
    

    Je ne peux pas penser à une analogie de Nice pour expliquer correctement cela, cependant. C'est fondamentalement le même problème pourquoi vous pouvez représenter 1/3 approximativement en décimal, car pour obtenir la valeur exacte, vous devez répéter les 3 indéfiniment à la fin de la fraction décimale.

    De même, les fractions binaires sont bonnes pour représenter les moitiés, les quarts, les huitièmes, etc., mais des choses comme un dixième donneront un flux de chiffres binaires se répétant à l'infini.

  3. Ensuite, il y a un autre problème, bien que la plupart des gens ne s'y aventurent pas, à moins qu'ils ne fassent des quantités énormes de calculs. Mais alors, ceux qui connaissent déjà le problème. Comme beaucoup de nombres à virgule flottante ne sont que des approximations de la valeur exacte, cela signifie que pour une approximation donnée f d'un nombre réel r, il peut y avoir une infinité de nombres réels r.1, r2, ... qui correspond exactement à la même approximation. Ces chiffres se situent dans un certain intervalle. Disons que rmin est la valeur minimale possible de r qui donne f et rmax la valeur maximale possible de r pour laquelle cela est valable, alors vous avez un intervalle [rmin, rmax] où n'importe quel nombre dans cet intervalle peut être votre nombre réel r.

    Désormais, si vous effectuez des calculs sur ce nombre (addition, soustraction, multiplication, etc.), vous perdez en précision. Chaque nombre n'étant qu'une approximation, vous effectuez donc des calculs avec intervalles. Le résultat est aussi un intervalle et l'erreur d'approximation ne fait que grandir, élargissant ainsi l'intervalle. Vous pouvez récupérer un nombre unique à partir de ce calcul. Mais il ne s'agit que de un nombre compris dans l'intervalle de résultats possibles, en tenant compte de la précision de vos opérandes d'origine et de la perte de précision due au calcul.

    Ce genre de chose s'appelle Arithmétique d'intervalle et au moins pour moi, cela faisait partie de notre cours de mathématiques à l'université.

26
Joey

Montrez-leur que le système base-10 souffre du même problème, exactement.

Essayez de représenter 1/3 sous forme de représentation décimale en base 10. Vous ne pourrez pas le faire exactement.

Donc, si vous écrivez "0.3333", vous aurez une représentation raisonnablement exacte pour de nombreux cas d'utilisation.

Mais si vous ramenez cela à une fraction, vous obtiendrez "3333/10000", qui est not identique à "1/3".

D'autres fractions, telles que 1/2, peuvent facilement être représentées par une représentation décimale finie en base 10: "0.5"

Maintenant, les bases 2 et 10 souffrent essentiellement du même problème: les deux ont des nombres qu’ils ne peuvent pas représenter exactement.

Bien que la base 10 n’ait aucun problème à représenter 1/10 comme "0,1" en base 2, vous auriez besoin d’une représentation infinie commençant par "0,000110011 ..".

8
Joachim Sauer

Comment est-ce pour une explantation au profane. Les ordinateurs représentent les nombres en comptant les unités discrètes. Ce sont des ordinateurs numériques. Pour les nombres entiers, ceux sans fraction, les calculateurs numériques modernes comptent des puissances de deux: 1, 2, 4, 8. , Valeur de la position, chiffres binaires, bla, bla, bla. Pour les fractions, les calculateurs numériques comptent des puissances inverses égales à deux: 1/2, 1/4, 1/8, ... Le problème est que de nombreux nombres ne peuvent pas être représentés par la somme d'un nombre fini de ces puissances inverses. Utiliser plus de valeurs de position (plus de bits) augmentera la précision de la représentation de ces numéros de «problème», mais ne l'obtiendra jamais exactement car il ne comporte qu'un nombre limité de bits. Certains nombres ne peuvent pas être représentés avec un nombre infini de bits.

Roupillon...

OK, vous voulez mesurer le volume d'eau dans un récipient et vous n'avez que 3 tasses à mesurer: une tasse pleine, une demi-tasse et une tasse quart. Après avoir compté la dernière tasse complète, supposons qu’il reste un tiers de tasse. Pourtant, vous ne pouvez pas mesurer cela car cela ne remplit pas exactement une combinaison de gobelets disponibles. Il ne remplit pas la demi-tasse et le trop-plein de la tasse est trop petit pour remplir quoi que ce soit. Donc, vous avez une erreur - la différence entre 1/3 et 1/4. Cette erreur est aggravée lorsque vous la combinez avec des erreurs provenant d'autres mesures.

6
gary

En python:

>>> 1.0 / 10
0.10000000000000001

Explique comment certaines fractions ne peuvent pas être représentées précisément en binaire. Tout comme certaines fractions (comme 1/3) ne peuvent être représentées précisément en base 10.

2
codeape

Un autre exemple, en C

printf (" %.20f \n", 3.6);

donne incroyablement 

3.60000000000000008882

2
cibercitizen1

Voici ma compréhension simple.

Problème: La valeur 0,45 ne peut pas être représentée avec précision par un flottant et est arrondie à 0.450000018. Pourquoi donc?

Réponse: Une valeur int de 45 est représentée par la valeur binaire 101101. Pour obtenir la valeur 0,45, il serait exact de pouvoir prendre 45 x 10 ^ -2 (= 45/10 ^ 2 .) Mais c’est impossible parce que vous devez utiliser la base 2 au lieu de 10.

Donc, le plus proche de 10 ^ 2 = 100 serait 128 = 2 ^ 7. Le nombre total de bits dont vous avez besoin est 9: 6 pour la valeur 45 (101101) + 3 bits pour la valeur 7 (111) . Ensuite, la valeur 45 x 2 ^ -7 = 0,3515625. Vous avez maintenant un grave problème d'inexactitude. 0,3515625 n’est pas près de 0,45.

Comment pouvons-nous améliorer cette inexactitude? Eh bien, nous pourrions changer la valeur 45 et 7 en autre chose.

Que diriez-vous de 460 x 2 ^ -10 = 0.44921875. Vous utilisez maintenant 9 bits pour 460 et 4 bits pour 10. Ensuite, c’est un peu plus proche, mais pas aussi proche. Toutefois, si votre valeur initiale souhaitée était de 0,44921875, vous obtiendrez une correspondance exacte sans approximation.

La formule de votre valeur serait donc X = A x 2 ^ B. Où A et B sont des valeurs entières positives ou négatives. Évidemment, plus les nombres peuvent être élevés, plus votre précision deviendrait grande, cependant, comme vous le savez, le nombre de bits pour représenter les valeurs A et B est limité. Pour float, vous avez un nombre total de 32. Double a 64 et Decimal a 128.

1
Jan

Une jolie bizarrerie numérique peut être observée si on convertit 9999999.4999999999 en float et de nouveau en double. Le résultat est 10000000, même si cette valeur est manifestement plus proche de 9999999 et même si 9999999.499999999 arrondit correctement à 9999999.

0
supercat