Représentation binaire de la partie décimale d'un nombre
Soit le nombre décimal suivant : 33,125. On peut l'écrire sous la forme :
33,125 = 3 × 10 + 3 × 1 + 1 × 0,1 + 2 × 0,01 + 5 × 0,001
Soit : 33,125 = 3 × 101 + 3 × 100 + 1 × 10-1 + 2 × 10-2 + 5 × 10-3
Nous savons déjà représenter un nombre entier en binaire (voir la page sur les bases de numération) et on a donc : (33)10 = (10 0001)2 Il nous reste donc à trouver la représentation binaire de la partie décimale du nombre, c'est-à-dire : 0,125. Pour cela on va réaliser des multiplications par 2 successives :
0,125 × 2 = 0,250 ⇒ on note la partie entière (ici 0) et on reprend la partie décimale (ici 0,250).
0,250 × 2 = 0,500 ⇒ on note la partie entière (ici 0) et on reprend la partie décimale (ici 0,500).
0,500 × 2 = 1,000 ⇒ on note la partie entière (ici 1) et on reprend la partie décimale (ici 0,000).
La partie décimale étant nulle, on s'arrête. Les parties entières successives, prises dans l'ordre, nous donnent 001.
On a donc : (0,125)10 = (0,001)2
On peut facilement vérifier que 0 × 2-1 + 0 × 2-2 + 1 × 2-3 = 0,125.
On peut donc en déduire, en collant les deux morceaux, que (33,125)10 = (10 0001,001)2
Exercices :
- Trouver la représentation décimale du nombre binaire (100,01)2.
- Trouver la représentation binaire de (6,625)10.
- Même question pour le nombre (0,1)10. Que remarque-t-on ?
Solutions :
- 4,25
- 101,101
- 0,0001 1001 1001 1001 1001... On remarque que l'écriture binaire de ce nombre ne peut pas être écrite car il y a un nombre infini de chiffres après la virgule. On a le même problème en base 10, avec par exemple 1 ÷ 3.
Pour écrire des nombres très grands ou très petits en base 10, on peut utiliser la notation scientifique. Par exemple, on a : 32654,234 = 3,2654234 × 104 ou : 0,00008591 = 8,591 × 10-5
Il est possible de faire la même chose en binaire. On a, par exemple : (85,125)10 = (1010101,001)2 = (1,010101001)2 × 26
Représentation des flottants dans un ordinateur
Les principes vus précédemment vont nous permettre de coder un nombre à virgule sous forme binaire. Comme ce codage va se faire avec un certain nombre de bits, on ne pourra représenter qu'une quantité finie de nombres. On ne peut donc pas représenter l'ensemble des nombres réels, qui est un continuum contenant une infinité de valeurs, mais un sous-ensemble de celui-ci, les nombres flottants.
Il existe plusieurs façons de coder un nombre flottant, mais la norme IEEE 754 est la plus répandue. Cette norme définit plusieurs formats. Nous allons en étudier un plus précisément, le format simple précision sur 32 bits.
Dans ce format, chaque nombre est codé sur 32 bits. Un bit est réservé pour le signe du nombre, 8 bits pour l'exposant et 23 bits pour la mantisse (nous allons voir à quoi correspondent ces termes).
Le bit de signe sera égal à 0 si le nombre est positif, et égal à 1 si le nombre est négatif.
Pour les autres bits, il est nécessaire d'écrire notre nombre en binaire en notation scientifique, sous la forme 1,XXXXXXXX × 2exposant (avec XXXXXXXX la suite de bits de la représentation du nombre).
La partie 1,XXXXXXXX correspond à la mantisse du nombre.
La puissance de 2 correspond à son exposant. Si le nombre est plus grand que 1, l'exposant sera positif ; si le nombre est plus petit que 1, l'exposant sera négatif. Cela pose un problème, car le codage d'un nombre entier négatif est assez complexe, avec l'histoire du complément à deux (voir la page sur le codage des nombres entiers relatifs). On ajoutera donc 127 à la valeur de notre exposant, obtenant ce que l'on appelle exposant biaisé.
Voyons tout cela sur un exemple : on va essayer de trouver la représentation binaire du nombre (-255,77734375)10.
Vous pouvez vérifier que (255)10 = (1111 1111)2 et que (0,77734375)10 = (0,1100 0111)2.
On a donc : (255,77734375)10 = (1111 1111,1100 0111)2 Soit : 1,1111 1111 1000 111 × 27
Nous avons tous les éléments pour pouvoir trouver notre représentation utilisant la norme IEE 754 :
- Le nombre est négatif, donc le bit de signe sera égal à 1.
- L'exposant est égal à 7. 7 + 127 = 134. La représentation de 134 sur 8 bits est 1000 0110. On inscrira ces bits dans la partie "exposant".
- La mantisse aura toujours un bit égal à 1 devant la virgule. Comme ce bit est toujours présent, nous n'avons pas besoin de le stocker. Nous allons donc stocker les bits suivants : 1111 1111 1000 1110 0000 000 (on complète jusqu'à 23 bits avec des 0).
La représentation en simple précision 32 bits du nombre (-255,77734375)10 est donc (1100 0011 0111 1111 1100 0111 0000 0000)2. Autre exemple : Soit le nombre binaire simple précision 32 bits suivant : (0100 0000 0100 1001 0000 1111 1101 1011)2 
- Le bit de signe est égal à 0 : le nombre est positif.
- La valeur de l'exposant biaisé est (1000 0000)2, soit 128. 128 - 127 = 1. L'exposant est donc de 1.
- La mantisse est constituée d'un bit égal à 1, suivi d'une virgule et de tous les autres bits. La mantisse est donc : (1,1001 0010 0001 1111 1011 011)2.
Notre nombre est donc égal à : (1,1001 0010 0001 1111 1011 011)2 × 27
Soit : (11, 0010 0100 0011 1111 0110 11)2
La valeur décimal de ce nombre est donc : 21 + 20 + 2-3 + 2-6 + 2-11 + 2-12 + 2-13 + 2-14 + 2-15 + 2-16 + 2-18 + 2-19 + 2-21 + 2-22 = 3,141592741012573...
Ce qui est une approximation du nombre pi correcte à 10-6 près, ce qui veut dire que 6 des décimales sont correctes. La vraie valeur est de : 3,141592653589793... En règle générale, on considère qu'on a une 6 à 7 décimales correctes pour la représentation binaire en simple précision 32 bits.
Vous pouvez trouver ici un convertisseur "valeur décimale" vers "IEEE simple précision 32 bits".
Il existe d'autres modélisation : demi-précision (16 bits), double précision (64 bits), quadruple précision (128 bits), ... Ce qui change, c'est le nombre de bits pour coder l'exposant et la mantisse :
Une autre différence, c'est le décalage qu'il faut ajouter à l'exposant : 15 pour la demi-précision, 1023 pour la double, 16383 pour la quadruple... Ce nombre s'obtient en calculant 2n-1 - 1, avec n le nombre de bits nécessaires pour représenter l'exposant. (Il existe aussi une simple précision étendue (sur 40 bits), et une double précision étendue (sur 80 bits) : plus d'information sur l'article de Wikipédia).
On remarque aussi que plus le nombre de bits utilisés est important, plus l'étendue des nombres disponibles (différence entre le plus petit et le plus grand possible) est grande, et plus la précision (nombre de décimales) est grande. Mais les nombres occupent alors plus de place en mémoire. Il faut donc trouver le meilleur compromis performances / taille mémoire en fonction de l'application voulue.
Conséquence lors de l'emploi des float en Python
Le fait que certains nombres ne peuvent pas avoir une représentation finie en binaire va entrainer des conséquences qui peuvent être fâcheuses lors de l'emploi des float. On tape les calculs suivants dans la console Python :
>>> 1.1+2.2
3.3000000000000003
>>> 0.1+0.1+0.1-0.3
5.551115123125783e-17
Le premier calcul devrait nous donner 3,3 et le deuxième exactement 0,0. La différence avec le résultat est très faible, mais elle existe, et on peut l'expliquer par le fait que Python va travailler avec des approximations de certains nombres, car il a un nombre de bits limité pour les représenter. Un peu comme quand on arrondit le résultat d'un calcul qui ne "se finit pas" :
10 ÷ 3 = 3,333 ⇒ 3,333 × 3 = 9,999 ≠ 10
Une autre conséquence est le fait que l'addition des float n'est pas associative, comme celle des entiers :
>>> (1e10 + 5e-7) + 5e-7
10000000000.0
>>> 1e10 + (5e-7 + 5e-7)
10000000000.000002
On remarquera au passage qu'aucun des deux résultats précédents n'est correct !
Pour toutes ses raisons, il est recommandé, lorsqu'on travaille avec des float, de ne jamais tester l'égalité de deux nombres avec l'opérateur ==, mais de plutôt calculer la valeur absolue de la différence entre ces deux nombres, et de regarder si cette valeur est plus petite qu'une valeur ε donnée :
# a et b sont deux flottants issus de calculs précédents
if abs(b - a) < epsilon :
on exécute la séquence en cas d'égalité
la valeur de epsilon doit être choisie suffisament petite,
mais pas trop (exemple : 1e-10)
if a == b :
à proscrire, car selon le cas, on n'aura jamais égalité parfaite
A savoir que ce problème n'est pas le fait de Python, mais de tous les langages de programmation : aucun ordinateur ne peut faire de calcul infiniment précis sur des nombres flottants ! Ce problème est simplement masqué dans certaines applications, comme les calculatrice, qui vont simplement afficher un nombre limité de chiffres significatifs.
La précision des float utilisés en Python (qui sont en double précision sur 64 bits) est en général suffisante pour la plupart des applications de calcul. Si vous vouliez cependant plus de précisions, vous pouvez vous tourner vers le module decimal, qui permet de faire des calculs précis sur des nombres ayant un nombre arbitraire de décimales.
