Les nombres à virgule flottante, comme tous les nombres, doivent être stockés en mémoire sous la forme d'une chaîne de 0 et de 1. Ce sont tous des bits pour l'ordinateur. La différence entre les nombres à virgule flottante et les nombres entiers réside dans la manière dont nous interprétons les 0 et les 1 lorsque nous voulons les consulter.
Un bit est le "signe" (0 = positif, 1 = négatif), 8 bits sont l'exposant (allant de -128 à +127), 23 bits sont le nombre appelé "mantisse" (fraction). Ainsi, la représentation binaire de (S1)(P8)(M23) a la valeur (-1^S)M*2^P
La "mantisse" prend une forme particulière. En notation scientifique normale, nous affichons la "place de l'un" avec la fraction. Par exemple :
4.39 x 10^2 = 439
En binaire, la "place du un" est un seul bit. Puisque nous ignorons tous les 0 les plus à gauche dans la notation scientifique (nous ignorons tous les chiffres non significatifs), le premier bit est garanti comme étant un 1.
1.101 x 2^3 = 1101 = 13
Puisque nous avons la garantie que le premier bit sera un 1, nous supprimons ce bit lors du stockage du nombre pour gagner de la place. Le nombre ci-dessus est donc stocké sous la forme 101 (pour la mantisse). Le 1 de tête est supposé
À titre d'exemple, prenons la chaîne binaire
00000010010110000000000000000000
Le décomposer en ses composants :
Sign Power Mantissa
0 00000100 10110000000000000000000
+ +4 1.1011
+ +4 1 + .5 + .125 + .0625
+ +4 1.6875
En appliquant notre formule simple :
(-1^S)M*2^P
(-1^0)(1.6875)*2^(+4)
(1)(1.6875)*(16)
27
En d'autres termes, 00000010010110000000000000000000 est 27 en virgule flottante (selon les normes IEEE-754).
Pour de nombreux nombres, il n'existe cependant pas de représentation binaire exacte. De la même manière que 1/3 = 0,333.... se répétant indéfiniment, 1/100 est 0,000000101000111101110000..... avec un "10100011110101110000" répété. Un ordinateur 32 bits ne peut cependant pas stocker le nombre entier en virgule flottante. Il fait donc sa meilleure estimation.
0.0000001010001111010111000010100011110101110000
Sign Power Mantissa
+ -7 1.01000111101011100001010
0 -00000111 01000111101011100001010
0 11111001 01000111101011100001010
01111100101000111101011100001010
(notez que le 7 négatif est produit en utilisant le complément à 2)
Il devrait être immédiatement clair que 01111100101000111101011100001010 ne ressemble en rien à 0.01
Mais surtout, il contient une version tronquée d'une décimale répétitive. La décimale originale contenait une répétition de "10100011110101110000". Nous l'avons simplifié en 01000111101011100001010.
En retraduisant ce nombre à virgule flottante en décimal à l'aide de notre formule, nous obtenons 0,0099999979 (notez que ceci est pour un ordinateur 32 bits. Un ordinateur 64 bits aurait une précision bien supérieure)
Un équivalent décimal
Si cela permet de mieux comprendre le problème, examinons la notation scientifique décimale lorsqu'il s'agit de décimales répétées.
Supposons que nous ayons 10 "cases" pour stocker les chiffres. Par conséquent, si nous voulons stocker un nombre comme 1/16, nous devons écrire :
+---+---+---+---+---+---+---+---+---+---+
| + | 6 | . | 2 | 5 | 0 | 0 | e | - | 2 |
+---+---+---+---+---+---+---+---+---+---+
Ce qui est clairement juste 6.25 e -2
, donde e
est un raccourci pour *10^(
. Nous avons alloué 4 cases pour la décimale alors que nous n'en avons besoin que de 2 (remplissage avec des zéros), et nous avons alloué 2 cases pour les signes (une pour le signe du nombre, une pour le signe de l'exposant).
En utilisant 10 cases comme celle-ci, nous pouvons afficher des chiffres allant de -9.9999 e -9
a +9.9999 e +9
Cela fonctionne bien pour tout ce qui comporte 4 décimales ou moins, mais que se passe-t-il lorsque nous essayons de stocker un nombre tel que 2/3
?
+---+---+---+---+---+---+---+---+---+---+
| + | 6 | . | 6 | 6 | 6 | 7 | e | - | 1 |
+---+---+---+---+---+---+---+---+---+---+
Ce nouveau numéro 0.66667
n'est pas exactement égal à 2/3
. En fait, il est décalé de 0.000003333...
. Si nous devions essayer d'écrire 0.66667
en base 3, on obtiendrait 0.2000000000012...
au lieu de 0.2
Ce problème peut devenir plus apparent si nous prenons quelque chose avec une décimale répétée plus grande, comme 1/7
. Celui-ci comporte 6 chiffres répétitifs : 0.142857142857...
En stockant ceci dans notre ordinateur décimal, nous ne pouvons montrer que 5 de ces chiffres :
+---+---+---+---+---+---+---+---+---+---+
| + | 1 | . | 4 | 2 | 8 | 6 | e | - | 1 |
+---+---+---+---+---+---+---+---+---+---+
Ce numéro, 0.14286
, s'éteint par .000002857...
C'est "presque correct", mais ce n'est pas exactement correct et donc, si nous essayions d'écrire ce nombre en base 7, nous obtiendrions un nombre hideux au lieu de 0.1
. En fait, en entrant dans Wolfram Alpha, on obtient : .10000022320335...
Ces petites différences fractionnaires devraient vous sembler familières. 0.0099999979
(par opposition à 0.01
)
16 votes
Je vous suggère de lire nombres à virgule flottante . Plus précisément, les sections "Nombres représentables, conversion et arrondis" y "Problèmes de précision" . Le reste de l'article est bon si vous voulez comprendre comment ils fonctionnent, mais ces deux sections s'appliquent spécifiquement à votre question...
9 votes
Il est également intéressant de noter que vous utilisez des chaînes de caractères au lieu de nombres (ils sont implicitement convertis, mais quand même). Faites
$a = 35; $b = -34.99
à la place.0 votes
Vérifiez : stackoverflow.com/questions/17210787/ y stackoverflow.com/questions/588004/