Wikilivres
frwikibooks
https://fr.wikibooks.org/wiki/Accueil
MediaWiki 1.48.0-wmf.1
first-letter
Média
Spécial
Discussion
Utilisateur
Discussion utilisateur
Wikilivres
Discussion Wikilivres
Fichier
Discussion fichier
MediaWiki
Discussion MediaWiki
Modèle
Discussion modèle
Aide
Discussion aide
Catégorie
Discussion catégorie
Transwiki
Discussion Transwiki
Wikijunior
Discussion Wikijunior
TimedText
TimedText talk
Module
Discussion module
Event
Event talk
Fonctionnement d'un ordinateur/Le codage des nombres
0
65779
775637
775309
2026-11-02T15:14:19Z
Mewtow
31375
/* Les nombres à virgule fixe */
775637
wikitext
text/x-wiki
Dans le chapitre précédent, nous avons vu que les ordinateurs actuels utilisent un codage binaire. Ce codage binaire ne vous est peut-être pas familier. Aussi, dans ce chapitre, nous allons apprendre comment coder des nombres en binaire. Nous allons commencer par le cas le plus simple : les nombres positifs. Par la suite, nous aborderons les nombres négatifs. Et nous terminerons par les nombres à virgules, appelés aussi nombres flottants.
==Le codage des nombres entiers positifs==
Pour coder des nombres entiers positifs, il existe plusieurs méthodes : le binaire, l’hexadécimal, le code Gray, le décimal codé binaire et bien d'autres encore. La plus connue est certainement le binaire, secondée par l'hexadécimal, les autres étant plus anecdotiques. Pour comprendre ce qu'est le binaire, il nous faut faire un rappel sur les nombres entiers tel que vous les avez appris en primaire, à savoir les entiers écrits en décimal. Prenons un nombre écrit en décimal : le chiffre le plus à droite est le chiffre des unités, celui à côté est pour les dizaines, suivi du chiffre des centaines, et ainsi de suite. Dans un tel nombre :
* on utilise une dizaine de chiffres, de 0 à 9 ;
* chaque chiffre est multiplié par une puissance de 10 : 1, 10, 100, 1000, etc. ;
* la position d'un chiffre dans le nombre indique par quelle puissance de 10 il faut le multiplier : le chiffre des unités doit être multiplié par 1, celui des dizaines par 10, celui des centaines par 100, et ainsi de suite.
Exemple avec le nombre 1337 :
<math>1337 = 1 \times 1000 + 3 \times 100 + 3 \times 10 + 7 \times 1</math>
Pour résumer, un nombre en décimal s'écrit comme la somme de produits, chaque produit multipliant un chiffre par une puissance de 10. On dit alors que le nombre est en base 10.
<math>1337 = 1 \times 10^3 + 3 \times 10^2 + 3 \times 10^1 + 7 \times 10^0</math>
Ce qui peut être fait avec des puissances de 10 peut être fait avec des puissances de 2, 3, 4, 125, etc : n'importe quel nombre entier strictement positif peut servir de '''base'''. En informatique, on utilise rarement la base 10 à laquelle nous sommes tant habitués. On utilise à la place deux autres bases :
* La base 2 (système binaire) : les chiffres utilisés sont 0 et 1 ;
* La base 16 (système hexadécimal) : les chiffres utilisés sont 0, 1, 2, 3, 4, 5, 6, 7, 8 et 9 ; auxquels s'ajoutent les six premières lettres de notre alphabet : A, B, C, D, E et F.
===Le système binaire===
En binaire, on compte en base 2. Cela veut dire qu'au lieu d'utiliser des puissances de 10 comme en décimal, on utilise des puissances de deux : n'importe quel nombre entier peut être écrit sous la forme d'une somme de puissances de 2. Par exemple 6 s'écrira donc 0110 en binaire : <math>0 \times 2^3 + 1 \times 2^2 + 1 \times 2^1 + 0 \times 2^0 = 6</math>. On peut remarquer que le binaire n'autorise que deux chiffres, à savoir 0 ou 1 : ces chiffres binaires sont appelés des '''bits''' (abréviation de Binary Digit). Pour simplifier, on peut dire qu'un bit est un truc qui vaut 0 ou 1. Pour résumer, tout nombre en binaire s'écrit sous la forme d'un produit entre bits et puissances de deux de la forme :
<math>a_0 \times 2^0 + a_1 \times 2^1 + a_2 \times 2^2 + a_3 \times 2^3 + a_4 \times 2^4 +...+ a_n \times 2^n</math>
Les coefficients <math>a_0, a_1, a_2</math> sont les bits, l'exposant n qui correspond à un bit <math>a_n</math> est appelé le '''poids''' du bit.
====La terminologie du binaire====
En informatique, il est rare que l'on code une information sur un seul bit. Dans la plupart des cas, l'ordinateur manipule des nombres codés sur plusieurs bits. Les informaticiens ont donné des noms aux groupes de bits suivant leur taille. Le plus connu est certainement l''''octet''', qui désigne un groupe de 8 bits. Moins connu, on parle de '''''nibble''''' pour un groupe de 4 bits (un demi-octet), de '''doublet''' pour un groupe de 16 bits (deux octets) et de '''quadruplet''' pour un groupe de 32 bits (quatre octets).
Précisons qu'en anglais, le terme ''byte'' n'est pas synonyme d'octet. En réalité, le terme ''octet'' marche aussi bien en français qu'en anglais. Quant au terme ''byte'', il désigne un concept complètement différent, que nous aborderons plus tard (c'est la plus petite unité de mémoire que le processeur peut adresser). Il a existé dans le passé des ordinateurs où le ''byte'' faisait 4, 7, 9, 16, voire 48 bits, par exemple. Il a même existé des ordinateurs où le ''byte'' faisait exactement 1 bit ! Mais sur presque tous les ordinateurs modernes, un ''byte'' fait effectivement 8 bits, ce qui fait que le terme ''byte'' est parfois utilisé en lieu et place d'''octet''. Mais c'est un abus de langage, attention aux confusions ! Dans ce cours, nous parlerons d'octet pour désigner un groupe de 8 bits, en réservant le terme ''byte'' pour sa véritable signification.
À l'intérieur d'un nombre, le '''bit de poids faible''' est celui qui est le plus à droite du nombre, alors que le '''bit de poids fort''' est celui non nul qui est placé le plus à gauche, comme illustré dans le schéma ci-dessous.
C'est le même principe que l'écriture des nombres en base décimale : le chiffre le plus significatif à gauche, et le moins significatif à droite (l'unité ou les décimales après la virgule).
[[File:Msb.svg|centre|vignette|upright=2|Bit de poids fort.]]
[[File:Lsb.svg|centre|vignette|upright=2|Bit de poids faible.]]
Cette terminologie s'applique aussi pour les bits à l'intérieur d'un octet, d'un ''nibble'', d'un doublet ou d'un quadruplet. Pour un nombre codés sur plusieurs octets, on peut aussi parler de l'octet de poids fort et de l'octet de poids faible, du doublet de poids fort ou de poids faible, etc.
====La traduction binaire→décimal====
Pour traduire un nombre binaire en décimal, il faut juste se rappeler que la position d'un bit indique par quelle puissance il faut le multiplier. Ainsi, le chiffre le plus à droite est le chiffre des unités : il doit être multiplié par 1 (<math>2^0</math>). Le chiffre situé immédiatement à gauche du chiffre des unités doit être multiplié par 2 (<math>2^1</math>). Le chiffre encore à gauche doit être multiplié par 4 (<math>2^2</math>), et ainsi de suite. Mathématiquement, on peut dire que le énième bit en partant de la droite doit être multiplié par <math>2^{n-1}</math>. Par exemple, la valeur du nombre noté 1011 en binaire est de <math>8 \times 1 + 4 \times 0 + 2 \times 1 + 1 \times 1 = 11</math>.
[[File:Value of digits in the Binary numeral system.svg|centre|vignette|upright=2|Valeur des chffres dans le système de numération binaire.]]
====La traduction décimal→binaire====
La traduction inverse, du décimal au binaire, demande d'effectuer des divisions successives par deux. Les divisions en question sont des divisions euclidiennes, avec un reste et un quotient. En lisant les restes des divisions dans un certain sens, on obtient le nombre en binaire. Voici comment il faut procéder, pour traduire le nombre 34 :
[[File:Exemple d'illustration de la méthode de conversion décimal vers binaire.png|centre|vignette|upright=2|Exemple d'illustration de la méthode de conversion décimal vers binaire.]]
====Quelques opérations en binaire====
Maintenant que l'on sait coder des nombres en binaire normal, il est utile de savoir comment faire quelques opérations usuelles en binaire. Nous utiliserons les acquis de cette section dans la suite du chapitre, bien que de manière assez marginale.
La première opération est assez spécifique au binaire. Il s'agit d'une opération qui inverse les bits d'un nombre : les 0 deviennent des 1 et réciproquement. Par exemple, le nombre 0001 1001 devient 1110 0110. Elle porte plusieurs noms : opération NOT, opération NON, complémentation, etc. Nous parlerons de '''complémentation''' ou d'opération NOT dans ce qui suit. Beaucoup d'ordinateurs gèrent cette opération, ils savent la faire en un seul calcul. Il faut dire que c'est une opération assez utile, bien que nous ne pouvons pas encore expliquer pourquoi.
[[File:Binary Addition Demonstration.svg|vignette|Exemple d'addition en binaire.]]
La seconde opération à aborder est l'addition. Elle se fait en binaire de la même manière qu'en décimal : Pour faire une addition en binaire, on additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. Sauf que l'on additionne des bits. Heureusement, la table d'addition est très simple en binaire :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
La troisième opération est une variante de l'addition appelée l''''opération XOR''', notée <math>\oplus</math>. Il s'agit d'une addition dans laquelle on ne propage pas les retenues. L'addition des deux bits des opérandes se fait normalement, mais les retenues sont simplement oubliées, on n'en tient pas compte. Le résultat est que l'addition se résume à appliquer la table d'addition précédente :
* 0 <math>\oplus</math> 0 = 0 ;
* 0 <math>\oplus</math> 1 = 1 ;
* 1 <math>\oplus</math> 0 = 1 ;
* 1 <math>\oplus</math> 1 = 0.
Pour résumer, le résultat vaut 1 si les deux bits sont différents, 0 s'ils sont identiques. L'opération XOR sera utilisée rapidement dans le chapitre suivant, quand nous parlerons rapidement du mot de parité. Et elle sera beaucoup utilisée dans la suite du cours, nous en feront fortement usage. Pour le moment, mémorisez juste cette opération, elle n'a rien de compliqué.
Une dernière opération est l'opération de '''''population count'''''. Il s'agit ni plus ni moins que de compter le nombre de bits qui sont à 1 dans un nombre. Par exemple, pour le nombre 0110 0010 1101 1110, elle donne pour résultat 9. Elle est utilisée dans certaines applications, comme le calcul de certains codes correcteurs d'erreur, comme on le verra dans le chapitre suivant. Elle est supportée sur de nombreux ordinateurs, encore que cela dépende du processeur considéré. Il s'agit cependant d'une opération assez courante, supportée par les processeurs ARM, les processeurs x86 modernes (ceux qui gèrent le SSE), et quelques autres.
===L'hexadécimal===
L’hexadécimal est basé sur le même principe que le binaire, sauf qu'il utilise les 16 chiffres suivants :
{| class="wikitable"
|- style="color:#8080ffff; text-align:center;"
! Chiffre hexadécimal
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || A || B || C || D || E || F
|- style="text-align:center;"
! Nombre décimal correspondant
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 10 || 11 || 12 || 13 || 14 || 15
|- style="text-align:center;"
! Notation binaire
| 0000 || 0001 || 0010 || 0011 || 0100 || 0101 || 0110 || 0111 || 1000 || 1001 || 1010 || 1011 || 1100 || 1101 || 1110 || 1111
|}
Dans les textes, afin de différencier les nombres décimaux des nombres hexadécimaux, les nombres hexadécimaux sont suivis par un petit h, indiqué en indice. Si cette notation n'existait pas, des nombres comme 2546 seraient ambigus : on ne saurait pas dire sans autre indication s'ils sont écrits en décimal ou en hexadécimal. Avec la notation, on sait de suite que 2546 est en décimal et que 2546h est en hexadécimal.
Dans les codes sources des programmes, la notation diffère selon le langage de programmation.
Certains supportent le suffixe <code>h</code> pour les nombres hexadécimaux, d'autres utilisent un préfixe <code>0x</code> ou <code>0h</code>.
Pour convertir un nombre hexadécimal en décimal, il suffit de multiplier chaque chiffre par la puissance de 16 qui lui est attribuée. Là encore, la position d'un chiffre indique par quelle puissance celui-ci doit être multiplié : le chiffre le plus à droite est celui des unités, le second chiffre le plus à droite doit être multiplié par 16, le troisième chiffre en partant de la droite doit être multiplié par 256 (16 * 16) et ainsi de suite. La technique pour convertir un nombre décimal vers de l’hexadécimal est similaire à celle utilisée pour traduire un nombre du décimal vers le binaire. On retrouve une suite de divisions successives, mais cette fois-ci les divisions ne sont pas des divisions par 2 : ce sont des divisions par 16.
La conversion inverse, de l'hexadécimal vers le binaire est très simple, nettement plus simple que les autres conversions. Pour passer de l'hexadécimal au binaire, il suffit de traduire chaque chiffre en sa valeur binaire, celle indiquée dans le tableau au tout début du paragraphe nommé « Hexadécimal ». Une fois cela fait, il suffit de faire le remplacement. La traduction inverse est tout aussi simple : il suffit de grouper les bits du nombre par 4, en commençant par la droite (si un groupe est incomplet, on le remplit avec des zéros). Il suffit alors de remplacer le groupe de 4 bits par le chiffre hexadécimal qui correspond.
==Interlude propédeutique : la capacité d'un entier et les débordements d'entiers==
Dans la section précédente, nous avons vu comment coder des entiers positifs en binaire ou dans des représentations proches. La logique voudrait que l'on aborde ensuite le codage des entiers négatifs. Mais nous allons déroger à cette logique simple, pour des raisons pédagogiques. Nous allons faire un interlude qui introduira des notions utiles pour la suite du chapitre. De plus, ces concepts seront abordés de nombreuses fois dans ce wikilivre et l'introduire ici est de loin la solution idéale.
===Les ordinateurs manipulent des nombres codés sur un nombre fixe de bits===
Vous avez certainement déjà entendu parler de processeurs 32 ou 64 bits. Et si vous avez joué aux jeux vidéos durant votre jeunesse et êtes assez agé, vous avez entendu parler de consoles de jeu 8 bits, 16 bits, 32 bits, voire 64 bits (pour la Jaguar, et c'était un peu trompeur). Derrière cette appellation qu'on retrouvait autrefois comme argument commercial dans la presse se cache un concept simple. Tout ordinateur manipule des nombres entiers dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Une console 16 bits manipulait des entiers codés en binaire sur 16 bits, pas un de plus, pas un de moins. Pareil pour les anciens ordinateurs 32 bits, qui manipulaient des nombres entiers codés sur 32 bits.
Aujourd'hui, les ordinateurs modernes utilisent presque un nombre de bits qui est une puissance de 2 : 8, 16, 32, 64, 128, 256, voire 512 bits. Mais cette règle souffre évidemment d'exceptions. Aux tout débuts de l'informatique, certaines machines utilisaient 3, 7, 13, 17, 23, 36 et 48 bits ; mais elles sont aujourd'hui tombées en désuétude. De nos jours, il ne reste que les processeurs dédiés au traitement de signal audio, que l'on trouve dans les chaînes HIFI, les décodeurs TNT, les lecteurs DVD, etc. Ceux-ci utilisent des nombres entiers de 24 bits, car l'information audio est souvent codée par des nombres de 24 bits.
Anecdote amusante, il a existé des ordinateurs de 1 bit, qui sont capables de manipuler des nombres codés sur 1 bit, pas plus. Un exemple est le Motorola MC14500B, commercialisé de 1976.
====Le lien entre nombre de bits et valeurs codables====
Évidemment, on ne peut pas coder tous les entiers possibles et imaginables avec seulement 8 bits, ou 16 bits. Et il parait intuitif que l'on ait plus de valeurs codables sur 16 bits qu'avec 8 bits, par exemple. Plus le nombre de bits est important, plus on pourra coder de valeurs entières différentes. Mais combien de plus ? Par exemple, si je passe de 8 bits à 16 bits, est-ce que le nombre de valeurs que l'on peut coder double, quadruple, pentuple ? De même, combien de valeurs différentes on peut coder avec <math>n</math> bits. Par exemple, combien de nombres différents peut-on coder avec 4, 8 ou 16 bits ? La section précédente vous l'expliquer.
Avec <math>n</math> bits, on peut coder <math>2^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>2^n - 1</math>. N'oubliez pas cette formule : elle sera assez utile dans la suite de ce tutoriel. Pour exemple, on peut coder 16 valeurs avec 4 bits, qui vont de 0 à 15. De même, on peut coder 256 valeurs avec un octet, qui vont de 0 à 255. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de bits
! Nombre de valeurs codables
|-
! 4
| 16
|-
! 8
| 256
|-
! 16
| 65 536
|-
! 32
| 4 294 967 296
|-
! 64
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien de bits il faut pour coder une valeur quelconque, que nous noterons N. Pour cela, il faut utiliser la formule précédente, mais à l'envers. On cherche alors <math>n</math> tel que <math>N = 2^n</math>. L'opération qui donne <math>n</math> est appelée le logarithme, et plus précisément un logarithme en base 2, noté <math>\log_2{(X)}</math>. Le problème est que le résultat du logarithme ne tombe juste que si le nombre X est une puissance de 2. Si ce n'est pas le cas, le résultat est un nombre à virgule, ce qui n'a pas de sens pratique. Par exemple, la formule nous dit que pour coder le nombre 13, on a besoin de 3,70043971814 bits, ce qui est impossible. Pour que le résultat ait un sens, il faut arrondir à l'entier supérieur. Pour l'exemple précédent, les 3,70043971814 bits s'arrondissent en 4 bits.
====Le lien entre nombre de chiffres hexadécimaux et valeurs codables====
Maintenant, voyons combien de valeurs peut-on coder avec <math>n</math> chiffres hexadécimaux. La réponse n'est pas très différente de celle obtenue en binaire, si ce n'est qu'il faut remplacer le 2 par un 16 dans la formule précédente. Avec <math>n</math> chiffres hexadécimaux, on peut coder <math>16^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>16^n - 1</math>. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de chiffres héxadécimaux
! Nombre de valeurs codables
|-
! 1 (4 bits)
| 16
|-
! 2 (8 bits)
| 256
|-
! 4 (16 bits)
| 65 536
|-
! 8 (32 bits)
| 4 294 967 296
|-
! 16 (64 bits)
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien faut-il de chiffres hexadécimaux pour coder une valeur quelconque en hexadécimal. La formule est là encore la même qu'en binaire, sauf qu'on remplace le 2 par un 16. Pour trouver le nombre de chiffres hexadécimaux pour encoder un nombre X, il faut calculer <math>\log_{16}{(X)}</math>. Notons que le logarithme utilisé est un logarithme en base 16, et non un logarithme en base 2, comme pour le binaire. Là encore, le résultat ne tombe juste que si le nombre X est une puissance de 16 et il faut arrondir à l'entier supérieur si ce n'est pas le cas.
Une propriété mathématique des logarithmes nous dit que l'on peut passer d'un logarithme en base X et à un logarithme en base Y avec une simple division, en utilisant la formule suivante :
: <math>\log_X{(N)} = {\log_{Y}{(N)} \over \log_Y(X)}</math>
Dans le cas qui nous intéresse, on a Y = 2 et X = 16, ce qui donne :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over \log_2(16)}</math>
Or, <math>\log_2(16)</math> est tout simplement égal à 4, car il faut 4 bits pour coder la valeur 16. On a donc :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over 4}</math>
En clair, il faut quatre fois moins de chiffres hexadécimaux que de bits, ce qui est assez intuitif vu qu'il faut 4 bits pour coder un chiffre hexadécimal.
===Les débordements d'entier===
On vient de voir que tout ordinateur manipule des nombres dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Et cela limite les valeurs qu'il peut encoder, qui sont comprises dans un intervalle bien précis. Mais que ce passe-t-il si jamais le résultat d'un calcul ne rentre pas dans cet intervalle ? Par exemple, pour du binaire normal, que faire si le résultat d'un calcul atteint ou dépasse <math>2^n</math> ? Dans ce cas, le résultat ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
On peut imaginer d'autres codages pour lesquels les entiers ne commencent pas à zéro ou ne terminent pas à <math>2^n - 1</math>. On peut prendre le cas où l'ordinateur gère les nombres négatifs, par exemple. Dans le cas général, l'ordinateur peut coder les valeurs comprises dans un intervalle, qui va de la valeur la plus basse <math>N_\text{min}</math> à la valeur la plus grande <math>N_\text{max}</math>. Et encore une fois, si un résultat de calcul sort de cet intervalle, on fait face à un débordement d'entier.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math>, avec <math>N_\text{max}</math> la plus grande valeur codable par l'ordinateur.
On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur basse de débordement vaut <math>N_\text{min} - 1</math>, avec <math>N_\text{min}</math> la plus petite valeur codable par l'ordinateur.
===La gestion des débordements d'entiers===
Face à un débordement d'entier, l'ordinateur peut utiliser deux méthodes : l'arithmétique saturée ou l'arithmétique modulaire.
L''''arithmétique saturée''' consiste à arrondir le résultat pour prendre la plus grande ou la plus petite valeur. Si le résultat d'un calcul dépasse la valeur haute de débordement, le résultat est remplacé par le plus grand entier supporté par l'ordinateur. La même chose est possible quand le résultat est inférieur à la plus petite valeur possible, par exemple lors d'une soustraction : l'ordinateur arrondit au plus petit entier possible.
Pour donner un exemple, voici ce que cela donne avec des entiers codés sur 4 bits, qui codent des nombres de 0 à 15. Si je fais le calcul 8 + 9, le résultat normal vaut 17, ce qui ne rentre pas dans l'intervalle. Le résultat est alors arrondi à 15. Inversement, si je fais le calcul 8 - 9, le résultat sera de -1, ce qui ne rentre pas dans l'intervalle : le résultat est alors arrondi à 0.
[[File:Odometer rollover.jpg|vignette|Exemple de débordement d'entier sur un compteur mécanique quelconque. L'image montre clairement que le compteur revient à zéro une fois la valeur maximale dépassée.]]
L''''arithmétique modulaire''' est plus compliquée et c'est elle qui va nous intéresser dans ce qui suit. Pour simplifier, imaginons que l'on décompte à partir de zéro. Quand on arrive à la valeur haute de débordement, on recommence à compter à partir de zéro. L'arithmétique modulaire n'est pas si contre-intuitive et vous l'utilisez sans doute au quotidien. Après tout, c'est comme cela que l'on compte les heures, les minutes et les secondes. Quand on compte les minutes, on revient à 0 au bout de 60 minutes. Pareil pour les heures : on revient à zéro quand on arrive à 24 heures. Divers compteurs mécaniques fonctionnent sur le même principe et reviennent à zéro quand ils dépassent la plus grande valeur possible, l'image ci-contre en montrant un exemple.
Mathématiquement, l'arithmétique modulaire implique des divisions euclidiennes, celles qui donnent un quotient et un reste. Lors d'un débordement, le résultat s'obtient comme suit : on divise le nombre qui déborde par la valeur haute de débordement, et que l'on conserve le reste de la division. Au passage, l'opération qui consiste à faire une division et à garder le reste au lieu du quotient est appelée le ''modulo''. Prenons l'exemple où l'ordinateur peut coder tous les nombres entre 0 et 1023, soit une valeur haute de débordement de 1024. Pour coder le nombre 4563, on fait le calcul 4563 / 1024. On obtient : <math>4563 = 4 \times 1024 + 467</math>. Le reste de la division est de 467 et c'est lui qui sera utilisé pour coder la valeur de départ, 4563. Le nombre 4563 est donc codé par la valeur 467 dans un tel ordinateur en arithmétique modulaire. Au passage, la valeur haute de débordement est toujours codée par un zéro dans ce genre d'arithmétique.
Les ordinateurs utilisent le plus souvent une valeur haute de débordement de <math>2^n</math>, avec n le nombre de bits utilisé pour coder les nombres entiers positifs. En faisant cela, l'opération modulo devient très simple et revient à éliminer les bits de poids forts au-delà du énième bit. Par exemple, reprenons l'exemple d'un ordinateur qui code ses nombres sur 4 bits. Imaginons qu'il fasse le calcul <math>13 + 3</math>, soit 1101 + 0011 = 1 0000 en binaire. Le résultat entraîne un débordement d'entier et l'ordinateur ne conserve que les 4 bits de poids faible. Cela donne : 1101 + 0011 = 0000. Comme autre exemple l'addition 1111 + 0010 ne donnera pas 17 (1 0001), mais 1 (0001).
L'avantage est que les calculs sont beaucoup plus simples avec cette méthode qu'avec les autres. L'ordinateur a juste à ne pas calculer les bits de poids fort. Pas besoin de faire une division pour calculer un modulo, pas besoin de corriger le résultat pour faire de l'arithmétique saturée.
===Aparté : quelques valeurs particulières en binaire===
Plus haut, on a dit qu'avec n bits, on peut encoder toutes les valeurs allant de <math>0</math> à <math>2^n - 1</math>. Ce simple fait permet de déterminer quelle est la valeur de certains entiers à vue d’œil. Dans ce qui va suivre, nous allons poser quelques bases que nous réutiliserons dans la suite du chapitre. Il s'agit de quelques trivias qui sont cependant assez utiles.
Le premier trivia concerne la valeur maximale <math>2^n - 1</math> : elle est encodée par un nombre dont tous les bits sont à 1.
Le deuxième trivia concerne les nombres de la forme 000...000 1111 1111, à savoir des nombres dont les x bits de poids faible sont à 1 et tous les autres valent 0. Par définition, de tels nombres codent la plus grande valeur possible sur x bits, ce qui fait qu'ils valent <math>2^x - 1</math>.
Le troisième trivia concerne les nombres de la forme 11111...0000. En clair, des nombres où on a une suite consécutive de 1 dans les bits de poids fort et les x bits de poids faibles à 0. De tels nombres valent : <math>2^n - 2^x</math>. La preuve est assez simple : ils s'obtiennent en prenant la valeur maximale <math>2^n - 1</math>, et en soustrayant un nombre de la forme <math>2^x - 1</math>.
[[File:Valeurs particulières en binaire.png|centre|vignette|upright=2|Valeurs particulières en binaire]]
Si on utilise l'arithmétique modulaire, la valeur <math>2^n</math> n'est autre que la valeur de débordement haute pour les nombres stockés sur n bits, et se code comme le zéro (il faudrait 1 bit de plus pour stocker le 1 de poids fort de <math>2^n</math>). Les 1er et 3ème nombres évoqués dans les paragraphes précédents peuvent donc se passer de <math>2^n</math> dans leur expression :
* <math>- 1</math> est codé 1111111111111111...111 (n bits à 1)
* <math>- 2^x</math> est codé 11111...111000..000000 (x bits à 0 précédés de n-x bits à 1)
==Les nombres entiers négatifs==
<noinclude>[[File:Binary Representations.svg|vignette|Représentations signées, exemples sur 4 bits.]]</noinclude>
Passons maintenant aux entiers négatifs en binaire : comment représenter le signe moins ("-") avec des 0 et des 1 ? Eh bien, il existe plusieurs méthodes :
* la représentation en signe-valeur absolue ;
* la représentation en complément à un ;
* la représentation en complément à deux;
* la représentation par excès ;
* la représentation dans une base négative ;
* d'autres représentations encore moins utilisées que les autres.
===La représentation en signe-valeur absolue===
La solution la plus simple pour représenter un entier négatif consiste à coder sa valeur absolue en binaire, et rajouter un bit qui précise si c'est un entier positif ou un entier négatif. Par convention, ce bit de signe vaut 0 si le nombre est positif et 1 s'il est négatif. On parle alors de représentation en signe-valeur absolue, aussi appelée '''représentation en signe-magnitude'''.
Avec cette technique, il y autant de nombres positifs que négatifs. Mieux : pour chaque nombre représentable en représentation signe-valeur absolue, son inverse l'est aussi. Ce qui fait qu'avec cette méthode, le zéro est codé deux fois : on a un -0, et un +0. Cela pose des problèmes lorsqu'on demande à notre ordinateur d'effectuer des calculs ou des comparaisons avec zéro.
[[File:Codage sur 4 bits en signe-valeur absolue.png|centre|vignette|upright=2|Codage sur 4 bits en signe-valeur absolue]]
===La représentation en complément à un===
La représentation en '''complément à un''' peut être vue, en première approximation, comme une variante de la représentation en signe-magnitude. Et je dis bien : "en première approximation", car il y a beaucoup à dire sur la représentation en complément à un, mais nous verrons cela dans la section suivante sur le complément à deux. Conceptuellement, la représentation en signe-magnitude et celle du complément à un sont drastiquement différentes et sont basées sur des concepts mathématiques totalement différents. Mais elles ont des ressemblances de surface, qui font que faire la comparaison entre les deux est assez utile pédagogiquement parlant.
En complément à un, les nombres sont codés en utilisant un bit de signe qui indique si le nombre de positif ou négatif, couplé à une valeur absolue. Par contre, la valeur absolue est codée différemment en binaire. Pour les nombres positifs, la valeur absolue est codée en binaire normal, pas de changement comparé aux autres représentations. Mais pour les valeurs négatives, la valeur absolue est codée en binaire normal, puis tous les bits sont inversés : les 0 deviennent des 1 et réciproquement. En clair, on utilise une opération de complémentation pour les nombres négatifs.
Prenons un exemple avec un nombre codé sur 4 bits, avec un cinquième bit de signe. La valeur 5 est codée comme suit : 0 pour le bit de signe, 5 donne 0101 en binaire, le résultat est 0 0101. Pour la valeur -5, le bit de signe est 1, 5 donne 0101 en binaire, on inverse les bits ce qui donne 1010 : cela donne 11010. Notez qu'on peut passer d'un résultat à l'autre avec une opération de complémentation, à savoir en inversant les bits de l'autre et réciproquement.
Il s'agit là d'une propriété générale avec le complément à 1 : l'opposé d'un nombre, à savoir passer de sa valeur positive à sa valeur négative ou inversement, se calcule avec une opération NOT, une opération de complémentation (d'où son nom). L'avantage est que les ordinateurs gèrent naturellement d'opération de complémentation. Par contre, en signe-magnitude, inverser le bit de signe est une opération spécifique et ne sert qu'à ça. Il faut donc rajouter une opération en plus pour calculer l'opposé d'un nombre.
La représentation en complément à un garde les défauts de la représentation en signe-magnitude. Le zéro est codé deux fois, avec un zéro positif et un zéro négatif. La différence est que les valeurs négatives sont dans l'ordre inverse, il y a une symétrie un peu meilleure. Les deux zéros sont d'ailleurs totalement éloignés, ils correspondent aux valeurs extrêmes encodables : le zéro positif a tous ses bits à 0, le zéro négatif a tous ses bits à 1. Le résultat est que l'implémentation des comparaisons et de certains calculs est plus complexe qu'avec le signe-magnitude, mais de peu.
[[File:Codage sur 4 bits en complément à 1.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 1.]]
===La représentation par excès===
La représentation par excès consiste à ajouter un biais aux nombres à encoder afin de les encoder par un entier positif. Pour encoder tous les nombres compris entre -X et Y en représentation par excès, il suffit de prendre la valeur du nombre à encoder, et de lui ajouter un biais égal à X. Ainsi, la valeur -X sera encodée par zéro, et toutes les autres valeurs le seront par un entier positif, le zéro sera encodé par X, 1, par X+1, etc. Par exemple, prenons des nombres compris entre -127 et 128. On va devoir ajouter un biais égal à 127, ce qui donne :
{|class="wikitable"
|-
!Valeur avant encodage!!Valeur après encodage
|-
||-127||0
|-
||-126||1
|-
||-125||2
|-
||…||…
|-
||0||127
|-
||…||…
|-
||127||254
|-
||128||255
|}
===La représentation en complément à deux===
La représentation en '''complément à deux''' est basée sur les mêmes mathématiques que le complément à un, mais fonctionne très différemment en pratique.
Si on regarde de loin, son principe est assez différent : il n'y a pas de bit de signe ni quoi que ce soit d'autre. À la place, un nombre en complément à deux est encodé comme en binaire normal, à un point près : le bit de poids fort est soustrait, et non additionné aux autres. Il a une valeur négative : on soustrait la puissance de deux associée s'il vaut 1, on ne la tient pas en compte s'il vaut 0.
Par exemple, la valeur du nombre noté 111001 en complément à deux s'obtient comme suit :
{|class="wikitable" style="margin:auto;"
|-
! -32 !! 16 !! 8 !! 4 !! 2 !! 1
|-
| 1 || 1 || 1 || 0 || 0 || 1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(8×1)+(4×0)+(2×1)+(1×1) = −32+16+8+1 = -7.
: ''Avec le complément à deux, comme avec le complément à un, le bit de poids fort vaut 0 pour les nombres positifs et 1 pour les négatifs.''
L'avantage de cette représentation est qu'elle n'a pas de double zéro : le zéro n'est encodé que par une seule valeur. Par contre, la valeur autrefois prise par le zéro négatif est réutilisée pour encoder une valeur négative. La conséquence est que l'on a un nombre négatif en plus d'encodé, il n'y a plus le même nombre de valeurs strictement positives et de valeurs négatives encodées. Le nombre négatif en question est appelé le '''nombre le plus négatif''', ce nom trahit le fait que c'est celui qui a la plus petite valeur (la plus grande valeur absolue). Il est impossible de coder l'entier positif associé, sa valeur absolue.
[[File:Codage sur 4 bits en complément à 2.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 2.]]
====Le calcul du complément à deux : première méthode====
Les représentations en complément à un et en complément à deux sont basées sur le même principe mathématique. Leur idée est de remplacer chaque nombre négatif par un nombre positif équivalent, appelé le ''complément''. Par équivalent, on veut dire que tout calcul donne le même résultat si on remplace un nombre négatif par son complément (idem avec un nombre positif, son complément aura un signe inverse). Et pour faire cela, elles se basent sur les débordements d'entier pour fonctionner, et plus précisément sur l'arithmétique modulaire abordée plus haut.
Si on fait les calculs avec le complément, les résultats du calcul entraînent un débordement d'entier, qui sera résolu par un '''modulo''' : l'ordinateur ne conserve que les bits de poids faible du résultat, les autres bits sont oubliés. Par exemple, prenons l'addition 15 + 2, 1111 + 0010 en binaire : le résultat ne sera pas 17 (10001), vu qu'on n'a pas assez de bits pour encoder le résultat, mais 1 (0001). Et le résultat après modulo sera identique au résultat qu'on aurait obtenu avec le nombre négatif sans modulo. En clair, c'est la gestion des débordements qui permet de corriger le résultat de manière à ce que l'opération avec le complément donne le même résultat qu'avec le nombre négatif voulu. Ainsi, on peut coder un nombre négatif en utilisant son complément positif.
: Cela ressemble beaucoup à la méthode de soustraction basée sur un complément à 9 pour ceux qui connaissent, sauf que c'est une version binaire qui nous intéresse ici.
Prenons un exemple, qui permettra d'introduire la suite. Encore une fois, on utilise un codage sur 4 bits dont la valeur haute de débordement est de 16. Prenons l'addition de 13 + 3 = 16. Avec l'arithmétique modulaire, 16 est équivalent à 0, ce qui donne : 13 + 3 = 0 ! On peut aussi reformuler en disant que 13 = -3, ou encore que 3 = -13. Dit autrement, 3 est le complément de -13 pour ce codage. Et ne croyez pas que ça marche uniquement dans cet exemple : cela se généralise assez rapidement à tout nombre négatif.
Prenons un nombre N dont un veut calculer le complément à deux K. Dans le cas général, on a :
: <math>N + K = 2^n</math>, vu que <math>2^n = 0</math> en utilisant le modulo.
En réorganisant très légèrement les termes pour isoler K, on a :
: <math>K = 2^n - N</math>
La formule précédent permet de calculer la complément à deux assez simplement, en faisant le calcul à la main.
Avant de poursuivre, prenons un exemple très intéressant : le cas où N = -1. Son complément à deux vaut donc :
: <math>K = 2^n - 1</math>
Le terme <math>2^n - 1</math> devrait vous rappeler quelque chose : il s'agit d'un nombre dont tous les bits sont à 1. En clair, le complément à deux de -1 est un nombre de la forme 1111...111. Et cela vaut quel que soit le nombre de bits n. La représentation de -1 est similaire, peu importe que l'on utilise des nombres de 16 bits, 32 bits, 64 bits, etc.
Voyons maintenant le cas des puissance de deux, par exemple 2, 4, 8, 16, etc. Leur complément à deux vaut :
: <math>K = 2^n - 2^x</math>
Nous avions vu précédemment dans le chapitre que ces nombres sont de la forme 11111...0000. En clair, des nombres dont les x bits de poids faible sont à 0, et tous les autres bits sont à 1. Par exemple, le complément à deux de -2 est un nombre dont les bits sont à 1, sauf le bit de poids faible. De même, le complément à deux de -4 a tous ses bits à 1, sauf les deux bits de poids faible. Et le complément à deux de -8 a tous ses bits à 1, sauf les trois bits de poids faible. Pour résumer, tout nombre de la forme <math>- 2^x</math> a tous ses bits à 1, sauf les x bits de poids faible qui sont à 0.
{|class="wikitable"
|+ Exemple avec des nombres de 8 bits
|-
! -1
| 1111 1111
|-
! -2
| 1111 1110
|-
! -4
| 1111 1100
|-
! -8
| 1111 1000
|-
! -16
| 1111 0000
|-
! -32
| 1110 0000
|-
! -64
| 1100 0000
|-
! -128
| 1000 0000
|-
! 0 / - 256
| 0000 0000
|}
====Le calcul du complément à deux : seconde méthode====
Il existe une seconde méthode pour calculer le complément à deux d'un nombre, la voici. Pour les nombres positifs, encodez-les comme en binaire normale. Pour les nombres négatifs, faites pareil, puis inversez tous les bits, avant d'ajouter un. La procédure est identique à celle du complément à un, sauf que l'on incrémente le résultat final. Et ce n'est pas une coïncidence, comme nous allons le voir immédiatement.
Pour comprendre pourquoi la méthode marche, repartons de la formule précédente <math>K = 2^n - N</math>. Elle peut se reformuler comme suit :
: <math>K = (2^n - 1 + 1) - N</math>
: <math>K = (2^n - 1) - N + 1</math>
La valeur <math>(2^n - 1)</math> est par définition un nombre dont tous les bits sont à 1. À cette valeur, on soustrait un nombre <math>|N|</math> dont certains bits sont à 1 et d'autres à 0. En clair, pour chaque colonne, on a deux possibilités : soit on doit faire la soustraction <math>1-1</math>, soit la soustraction <math>1-0</math>. Or, les règles de l'arithmétique binaire disent que <math>1-1=0</math> et <math>1-0=1</math>. En regardant attentivement, on se rend compte que le bit du résultat est l'inverse du bit de départ. De plus, les deux cas ne donnent pas de retenue : le calcul pour chaque bit n'influence pas les bits voisins.
Le terme <math>(2^n - 1) - N</math> est donc le complément à un du nombre N, un nombre égal à sa représentation en binaire dont tous les bits sont inversés. Notons <math>\overline{|N|}</math> le nombre formé en inversant tous les bits de N. On a alors :
: <math>K = \overline{N} + 1</math>
En clair, le complément à deux s'obtient en prenant le complément à un et en ajoutant 1. Dit autrement, il faut prendre le nombre N, en inverser tous les bits et ajouter 1.
Une autre manière équivalente consiste à faire le calcul suivant :
: <math>K = \overline{N - 1}</math>
On prend le nombre dont on veut le complément, on soustrait 1 et on inverse les bits.
: Notons que tout ce qui a été dit plus haut marche aussi pour le complément à un, avec cependant une petite différence : la valeur haute de débordement n'est pas la même, ce qui change les calculs. Pour des nombres codés sur <math>n</math> bits, la valeur haute de débordement est égale à <math>2^n</math> en complément à deux, alors qu'elle est de <math>2^n - 1</math> en complément à un. De ce fait, la gestion des débordements est plus simple en complément à deux.
====L'extension de signe====
Dans les ordinateurs, tous les nombres sont codés sur un nombre fixé et constant de bits. Ainsi, les circuits d'un ordinateur ne peuvent manipuler que des nombres de 4, 8, 12, 16, 32, 48, 64 bits, suivant la machine. Si l'on veut utiliser un entier codé sur 16 bits et que l'ordinateur ne peut manipuler que des nombres de 32 bits, il faut bien trouver un moyen de convertir le nombre de 16 bits en un nombre de 32 bits, sans changer sa valeur et en conservant son signe. Cette conversion d'un entier en un entier plus grand, qui conserve valeur et signe s'appelle l''''extension de signe'''.
L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
L'explication plus simple tient dans la manière de coder le bit de poids fort. Prenons l'exemple de la conversion d'un entier de 5 bits en un entier de 8bits. Les 4 bits de poids faible ont un poids positif (on les additionne), alors que le bit de poids fort a un poids négatif. Le nombre encodé vaut : <math>\text{Bit de poids fort} \times -16 + \text{Valeur encodée sur les 4 bits de poids faible}</math>. La valeur encodée sur 4 bits reste la même après extension de signe, car les poids des bits de poids faible ne changent pas. Par contre, le bit de poids fort change. Sur 8 bits, la valeur -16 est encodée par un nombre de la forme 1111 0000. En remplaçant le bit de poids fort par sa valeur calculée sur plus de bits, on remarque que les bits de poids fort ont été remplacés par des 1.
===La représentation négabinaire===
Enfin, il existe une dernière méthode, assez simple à comprendre, appelée '''représentation négabinaire'''. Dans cette méthode, les nombres sont codés non en base 2, mais en base -2. Oui, vous avez bien lu : la base est un nombre négatif. Dans les faits, la base -2 est similaire à la base 2 : il y a toujours deux chiffres (0 et 1), et la position dans un chiffre indique toujours par quelle puissance de 2 il faut multiplier, sauf qu'il faudra ajouter un signe moins une fois sur 2. Concrètement, les puissances de -2 sont les suivantes : 1, -2, 4, -8, 16, -32, 64, etc. En effet, un nombre négatif multiplié par un nombre négatif donne un nombre positif, ce qui fait qu'une puissance sur deux est négative, alors que les autres sont positives. Ainsi, on peut représenter des nombres négatifs, mais aussi des nombres positifs dans une puissance négative.
Par exemple, la valeur du nombre noté 11011 en base -2 s'obtient comme suit :
{|class="wikitable"
|-
!…
!-32
!16
!-8
!4
!-2
!1
|-
|…
|1
|1
|1
|0
|1
|1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(−8×1)+(4×0)+(−2×1)+(1×1)=−32+16−8−2+1=−25.
==Les nombres à virgule fixe==
On sait donc comment sont stockés nos nombres entiers dans un ordinateur. Néanmoins, les nombres entiers ne sont pas les seuls nombres que l'on utilise au quotidien : il nous arrive d'en utiliser à virgule. Notre ordinateur n'est pas en reste : il est lui aussi capable de manipuler de tels nombres. Dans les grandes lignes, il peut utiliser deux méthodes pour coder des nombres à virgule en binaire : La virgule fixe et la virgule flottante. Parlons d'abord de la virgule fixe, plus simple à comprendre.
===La virgule fixe : des entiers cachés===
La méthode de la virgule fixe consiste à émuler les nombres à virgule à partir de nombres entiers. Un nombre à virgule fixe est codé par un nombre entier proportionnel au nombre à virgule fixe. Pour obtenir la valeur de notre nombre à virgule fixe, il suffit de diviser l'entier servant à le représenter par le facteur de proportionnalité. Par exemple, pour coder 1,23 en virgule fixe, on peut choisir comme « facteur de conversion » 1000, ce qui donne l'entier 1230.
Généralement, les informaticiens utilisent une puissance de deux comme facteur de conversion, pour simplifier les calculs. En faisant cela, on peut écrire les nombres en binaire et les traduire en décimal facilement. Pour l'exemple, cela permet d'écrire des nombres à virgule en binaire comme ceci : 1011101,1011001. Et ces nombres peuvent se traduire en décimal avec la même méthode que des nombres entier, modulo une petite différence. Comme pour les chiffres situés à gauche de la virgule, chaque bit situé à droite de la virgule doit être multiplié par la puissance de deux adéquate. La différence, c'est que les chiffres situés à droite de la virgule sont multipliés par une puissance négative de deux, c'est à dire par <math>1 \over 2</math>, <math>1 \over 4</math>, <math>1 \over 8</math>, <math>1 \over 16</math>, <math>1 \over 32</math>, ...
Cette méthode est assez peu utilisée de nos jours, quoiqu'elle puisse avoir quelques rares applications relativement connue. Un bon exemple est celui des banques : les sommes d'argent déposées sur les comptes ou transférées sont codés en virgule fixe. Les sommes manipulées par les ordinateurs ne sont pas exprimées en euros, mais en centimes d'euros. Et c'est une forme de codage en virgule fixe dont le facteur de conversion est égal à 100. La raison de ce choix est que les autres méthodes de codage des nombres à virgule peuvent donner des résultats imprécis : il se peut que les résultats doivent être tronqués ou arrondis, suivant les opérandes. Cela n'arrive jamais en virgule fixe, du moins quand on se limite aux additions et soustractions.
===Les nombres à virgule non-représentables en binaire===
Quel que soit la façon de représenter les nombres à virgules, il existe des nombres qui ne peuvent être représentés de manière exacte, à savoir avec un nombre fini de décimales après la virgule. En soi, ce n'est pas spécifique au binaire, on a la même chose en décimal. Par exemple, la fraction 1/3 en décimal s'écrit 0.3333333..., avec une infinité de 3. La même chose existe en binaire, mais pour des nombres différents.
Déjà, évacuons le cas des nombres irrationnels, à savoir les nombres qui ne peuvent pas s'écrire sous la forme d'une fraction, comme <math>pi</math> ou <math>e</math>. Ils ont une infinité de décimales que ce soit en binaire, en décimal, en hexadécimal, ou autre. Ils ne sont pas représentables avec un nombre fini de décimales quelle que soit la base utilisée. Concentrons-nous sur des nombres qui ne sont pas dans ce cas, et qui ont un nombre fini ou infini de décimales.
Le passage de la base 10 à la base 2 change le nombre de décimales, et peut faire passer d'un nombre fini de décimales à un nombre infini. Par exemple, <math>\frac{1}{5} = 0.2</math> est représenté en binaire avec une séquence infinie de bits : <math>0.001100110011...</math>. Les nombres étant en base binaire et représenté avec un nombre limité de bits, il existe certains nombres décimaux triviaux qui ne sont pas représentables avec un nombre fini de décimales.
Et la réciproque n'est pas vraie : tout nombre binaire avec un nombre fini de décimales en binaire est représentable avec un nombre fini de décimales en base 10. Ceci est lié à la décomposition en facteur premier des bases utilisées :
* <math>2 = 2^1</math>
* <math>10 = 2^1 \times 5^1</math>
Le nombre 10 possède tous les facteurs premiers de 2, mais 2 n'a pas de 5 dans sa décomposition.
==Les nombres flottants==
Les nombres à virgule fixe ont aujourd'hui été remplacés par les '''nombres à virgule flottante''', où le nombre de chiffres après la virgule est variable. Le codage d'un nombre flottant est basée sur son ''écriture scientifique''. Pour rappel, en décimal, l’écriture scientifique d'un nombre consiste à écrire celui-ci comme un produit entre un nombre <math>a</math> et une puissance de 10. Ce qui donne :
: <math>a \times 10^e</math>, avec <math>a \in [1, 10 [</math>
Le nombre <math>a</math> est appelé le ''significande'' et il est compris entre 1 (inclus) et 10 (exclu). Cette contrainte garantit que l'écriture scientifique d'un nombre est unique, qu'il n'y a qu'une seule façon d'écrire un nombre en notation scientifique. Pour cela, on impose le nombre de chiffre à gauche de la virgule et le plus simple est que celui-ci soit égal à 1. Mais il faut aussi que celui-ci ne soit pas nul. En effet, si on autorise de mettre un 0 à gauche de la virgule, il y a plusieurs manières équivalentes d'écrire un nombre. Ces deux contraintes font que le significande doit être égal ou plus grand que 1, mais strictement inférieur à 10. Par contre, on peut mettre autant de décimales que l'on veut.
En binaire, c'est la même chose, mais avec une puissance de deux. Cela implique de modifier la puissance utilisée : au lieu d'utiliser une puissance de 10, on utilise une puissance de 2.
: <math>a \times 2^e</math>, avec <math>a \in [1, 2 [</math>
Le significande est aussi altéré, au même titre que la puissance, même si les contraintes sont similaires à celles en base 10. En effet, le nombre <math>a</math> ne possède toujours qu'un seul chiffre à gauche de la virgule, comme en base 10. Vu que seuls deux chiffres sont possibles (0 et 1) en binaire, on s'attend à ce que le chiffre situé à gauche de la virgule soit un zéro ou un 1. Mais rappelons que le chiffre à gauche doit être non-nul, pour les mêmes raisons qu'en décimal. En clair, le significande a forcément un bit à 1 à gauche de la virgule. Pour récapituler, l'écriture scientifique binaire d'un nombre consiste à écrire celui-ci sous la forme :
: <math>1,m \times 2^e</math>, avec <math>m \in [0, 1 [</math>
La partie fractionnaire du nombre <math>a</math>, qu'on appelle la '''mantisse'''.
[[File:Mantissa.svg|centre|vignette|Écriture scientifique (anglais).]]
===Traduire un nombre en écriture scientifique binaire===
Pour déterminer l'écriture scientifique en binaire d'un nombre quelconque, la procédure dépend de la valeur du nombre en question. Tout dépend s'il est dans l'intervalle <math>[1, 2 [</math>, au-delà de 2 ou en-dessous de 1.
* Pour un nombre entre 1 (inclus) et 2 (exclu), il suffit de le traduire en binaire. Son exposant est 0.
* Pour un nombre au-delà de 2, il faut le diviser par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant est alors le nombre de fois qu'il a fallu diviser par 2.
* Pour un nombre plus petit que 1, il faut le multiplier par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant se calcule en prenant le nombre de fois qu'il a fallu multiplier par 2, et en prenant l'opposé (en mettant un signe - devant le résultat).
===Le codage des nombres flottants et la norme IEEE 754===
Pour coder cette écriture scientifique avec des nombres, l'idée la plus simple est d'utiliser trois nombres, pour coder respectivement la mantisse, l'exposant et un bit de signe. Coder la mantisse implique que le bit à gauche de la virgule vaut toujours 1, mais nous verrons qu'il y a quelques rares exceptions à cette règle.
Quelques nombres flottants spécialisés, les dénormaux, ne sont pas codés en respectant les règles pour le significande et ont un 0 à gauche de la virgule. Un bon exemple est tout simplement la valeur zéro, que l'on peut coder en virgule flottante, mais seulement en passant outre les règles sur le significande.
Toujours est-il que le bit à gauche de la virgule n'est pas codé, que ce soit pour les flottants normaux ou les fameux dénormaux qui font exception. On verra que ce bit peut se déduire en fonction de l'exposant utilisé pour encoder le nombre à virgule, ce qui lui vaut le nom de '''bit implicite'''. L'exposant peut être aussi bien positif que négatif (pour permettre de coder des nombres très petits), et est encodé en représentation par excès sur n bits avec un biais égal à <math>2^{n-1} - 1</math>.
[[File:IEEE754 Format General.png|centre|vignette|upright=2|IEEE754 Format Général]]
Le standard pour le codage des nombres à virgule flottante est la '''norme IEEE 754'''. Cette norme va (entre autres) définir quatre types de flottants différents, qui pourront stocker plus ou moins de valeurs différentes.
{|class="wikitable"
|-
!Classe de nombre flottant
!Nombre de bits utilisés pour coder un flottant
!Nombre de bits de l'exposant
!Nombre de bits pour la mantisse
!Décalage
|-
!Simple précision
| 32
| 8
| 23
| 127
|-
!Double précision
| 64
| 11
| 52
| 1023
|-
!Double précision étendue
| 80 ou plus
| 15 ou plus
| 64 ou plus
| 16383 ou plus
|}
Voici à peu-près comment les nombres flottants sont répartis sur une ligne numérique. Vous remarquerez que les flottants sont de plus en plus éloignés au fur et à mesure qu'on s'éloigne de zéro. La raison est l'encodage avec un exposant et une mantisse. L'écart entre deux flottants reste stable pour un même exposant, mais il y a une cassure quand on passe d'un exposant au suivant.
[[File:FloatingPointPrecisionAugmented.png|centre|vignette|upright=3|Floating Point Precision.]]
IEEE754 impose aussi le support de certains nombres flottants spéciaux qui servent notamment à stocker des valeurs comme l'infini. Commençons notre revue des flottants spéciaux par les '''dénormaux''', aussi appelés flottants dénormalisés. Ces flottants ont une particularité : leur bit implicite vaut 0. Ces dénormaux sont des nombres flottants où l'exposant est le plus petit possible. Le '''zéro''' est un dénormal particulier dont la mantisse est nulle. Au fait, remarquez que le zéro est codé deux fois à cause du bit de signe : on se retrouve avec un -0 et un +0.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur minimale (0 en binaire) || Mantisse différente de zéro (dénormal strict) ou égale à zéro (zéro)
|}
Fait étrange, la norme IEEE754 permet de représenter '''l'infini''', aussi bien en positif qu'en négatif. Celui-ci est codé en mettant l'exposant à sa valeur maximale et la mantisse à zéro. Et le pire, c'est qu'on peut effectuer des calculs sur ces flottants infinis. Mais cela a peu d'utilité.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse égale à zéro
|}
Mais malheureusement, l'invention des flottants infinis n'a pas réglé tous les problèmes. Par exemple, quel est le résultat de <math>\infty - \infty</math> ? Ou encore <math>0 - \infty</math> ? Autant prévenir tout de suite : mathématiquement, on ne peut pas savoir quel est le résultat de ces opérations. Pour pouvoir résoudre ces calculs, il a fallu inventer un nombre flottant qui signifie « je ne sais pas quel est le résultat de ton calcul pourri ». Ce nombre, c'est '''NaN'''. NaN est l'abréviation de ''Not A Number'', ce qui signifie : n'est pas un nombre. Ce NaN a un exposant dont la valeur est maximale, mais une mantisse différente de zéro. Pour être plus précis, il existe différents types de NaN, qui diffèrent par la valeur de leur mantisse, ainsi que par les effets qu'ils peuvent avoir. Malgré son nom explicite, on peut faire des opérations avec NaN, mais cela ne sert pas vraiment à grand chose : une opération arithmétique appliquée avec un NaN aura un résultat toujours égal à NaN.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse différente de zéro
|}
===Les arrondis et exceptions===
La norme impose aussi une gestion des arrondis ou erreurs, qui arrivent lors de calculs particuliers. En voici la liste :
{|class="wikitable"
|-
!Nom de l’exception
!Description
|-
!Invalid operation
|Opération qui produit un NAN. Elle est levée dans le cas de calculs ayant un résultat qui est un nombre complexe, ou quand le calcul est une forme indéterminée. Pour ceux qui ne savent pas ce que sont les formes indéterminées, voici en exclusivité la liste des calculs qui retournent NaN : <math>\infty - \infty</math>, <math>0 \times \infty</math>, <math>0^0</math>, <math>1^{\infty}</math>, <math>\infty^0</math>.
|-
!Overflow
|Résultat trop grand pour être stocké dans un flottant. Le plus souvent, on traite l'erreur en arrondissant le résultat en vue de la taille de la mantisse;
|-
!Underflow
|Pareil que le précédent, mais avec un résultat trop petit. Le plus souvent, on traite l'erreur en arrondissant le résultat vers 0.
|-
!Division par zéro
|Le nom parle de lui-même. La réponse la plus courante est de répondre + ou - l'infini.
|-
!'''Inexact'''
|Le résultat ne peut être représenté par un flottant et on doit l'arrondir.
|}
La gestion des arrondis pose souvent problème. Pour donner un exemple, on va prendre le nombre 0,1. En binaire, ce nombre s'écrit comme ceci : 0,1100110011001100... et ainsi de suite jusqu'à l'infini. Notre nombre utilise une infinité de décimales. Bien évidemment, on ne peut pas utiliser une infinité de bits pour stocker notre nombre et on doit impérativement l'arrondir. Comme vous le voyez avec la dernière exception, le codage des nombres flottants peut parfois poser problème : dans un ordinateur, il se peut qu'une opération sur deux nombres flottants donne un résultat qui ne peut être codé par un flottant. On est alors obligé d'arrondir ou de tronquer le résultat de façon à le faire rentrer dans un flottant. Pour éviter que des ordinateurs différents utilisent des méthodes d'arrondis différentes, on a décidé de normaliser les calculs sur les nombres flottants et les méthodes d'arrondis. Pour cela, la norme impose le support de quatre modes d'arrondis :
* Arrondir vers + l'infini ;
* vers - l'infini ;
* vers zéro ;
* vers le nombre flottant le plus proche.
===Les nombres flottants logarithmiques===
Les '''nombres flottants logarithmiques''' sont une spécialisation des nombres flottants IEEE754, ou tout du moins une spécialisation des flottants écrits en écriture scientifique. Un nombre logarithmique est donc composé d'un bit de signe et d'un exposant, sans mantisse. La mantisse est totalement implicite : tous les flottants logarithmiques ont la même mantisse, qui vaut 1.
Pour résumer, il ne reste que l'exposant, qui est tout simplement le logarithme en base 2 du nombre encodé, d'où le nom de codage flottant logarithmique donné à cette méthode. Attention toutefois : l'exposant est ici un nombre fractionnaire, codé en virgule fixe. Le choix d'un exposant fractionnaire permet de représenter pas mal de nombres de taille diverses.
{|class="wikitable"
|-
!
! Bit de signe
! Exposant
|-
! Représentation binaire
| 0
| 01110010101111
|-
! Représentation décimale
| +
| 1040,13245464
|}
L'utilité de cette représentation est de simplifier certains calculs, comme les multiplications, divisions, puissances, etc. En effet, les mathématiques nous disent que le logarithme d'un produit est égal à la somme des logarithmes : <math>\ln{(a \times b)} = \ln{a} + \ln{b}</math>. Or, il se trouve que les ordinateurs sont plus rapides pour faire des additions/soustractions que pour faire des multiplications/divisions. Donc, la représentation logarithmique permet de remplacer les multiplications/divisions par des additions/soustractions, plus simples et plus rapides pour l'ordinateur.
Évidemment, les applications des flottants logarithmiques sont rares, limitées à quelques situations bien précises (traitement d'image, calcul scientifique spécifiques).
==Les encodages binaires alternatifs==
Outre le binaire que nous venons de voir, il existe d'autres manières de coder des nombres avec des bits. Et nous allons les aborder dans cette section. Parmi celle-ci, nous parlerons du code Gray, de la représentation ''one hot'', du unaire et du ternaire. Nous en parlons car elles seront utiles dans la suite du cours, bien que de manière assez limitée. Autant nous passerons notre temps à parler du binaire normal, autant les représentations que nous allons voir sont aujourd'hui utilisées dans des cas assez spécifiques. Et elles sont plus courantes que vous ne le pensez.
===Le code Gray===
Le '''code Gray''' est un encodage binaire qui a une particularité très intéressante : deux nombres consécutifs n'ont qu'un seul bit de différence. Pour exemple, voici ce que donne le codage des 8 premiers entiers sur 3 bits :
{|class="wikitable"
! Décimal
! Binaire naturel
! Codage Gray
|-
|0||000||000
|-
|1||001||001
|-
|2||010||011
|-
|3||011||010
|-
|4||100||110
|-
|5||101||111
|-
|6||110||101
|-
|7||111||100
|}
Les utilisations du code Gray sont assez nombreuses, bien qu'on n'en croise pas tous les jours. Un exemple : le code Gray est très utile dans certains circuits appelés les compteurs, qui mémorisent un nombre et l'incrémentent (+1) ou le décrémentent (-1) suivant les besoins de l'utilisateur. Il est aussi utilisé dans des scénarios difficiles à expliquer ici (des codes correcteurs d'erreur ou des histoires de passages de domaines d'horloge). Mais le point important est que ce code sera absolument nécessaire dans quelques chapitres, quand nous parlerons des tables de Karnaugh, un concept important pour la conception de circuits électroniques. Ne passez pas à côté de cette section.
Pour construire ce code Gray, on peut procéder en suivant plusieurs méthodes, les deux plus connues étant la méthode du miroir et la méthode de l'inversion.
[[File:Binary-reflected Gray code construction.svg|vignette|Construction d'un code gray par la méthode du miroir.]]
La méthode du miroir est relativement simple. Pour connaître le code Gray des nombres codés sur n bits, il faut :
* partir du code Gray sur n-1 bits ;
* symétriser verticalement les nombres déjà obtenus (comme une réflexion dans un miroir) ;
* rajouter un 0 au début des anciens nombres, et un 1 au début des nouveaux nombres.
Il suffit de connaître le code Gray sur 1 bit pour appliquer la méthode : 0 est codé par le bit 0 et 1 par le bit 1.
Une autre méthode pour construire la suite des nombres en code Gray sur n bits est la méthode de l'inversion. Celle-ci permet de connaître le codage du nombre n à partir du codage du nombre n-1, comme la méthode du dessus. On part du nombre 0, systématiquement codé avec uniquement des zéros. Par la suite, on décide quel est le bit à inverser pour obtenir le nombre suivant, avec la règle suivante :
* si le nombre de 1 est pair, il faut inverser le dernier chiffre.
* si le nombre de 1 est impair, il faut localiser le 1 le plus à droite et inverser le chiffre situé à sa gauche.
Pour vous entraîner, essayez par vous-même avec 2, 3, voire 5.
===Les représentations ''one-hot'' et unaire===
La représentation ''one-hot'' et la représentation unaire sont deux représentations assez liées, mais légèrement différentes.
La '''représentation unaire''' est la représentation en base 1. Avec elle, le zéro est codé en mettant tous les bits du nombre à zéro, la valeur 1 est encodée avec la valeur 000...0001, le deux avec 000...0011, le trois avec 000...0111, etc. Pour résumer, le nombre N est encodé en mettant les N bits de poids faible à 1, à l'exception du zéro qui est encodé...par un zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! Unaire
|-
| 0 || 000 || 0000 0000
|-
| 1 || 001 || 0000 0001
|-
| 2 || 010 || 0000 0011
|-
| 3 || 011 || 0000 0111
|-
| 4 || 100 || 0000 1111
|-
| 5 || 101 || 0001 1111
|-
| 6 || 110 || 0011 1111
|-
| 7 || 111 || 0111 1111
|-
| 8 || 1000 || 1111 1111
|}
Pour convertir un nombre codé en unaire vers le binaire, il suffit de compter le nombre de 1. C'est ni plus ni moins ce que fait l'opération dite de ''population count'' dont nous avons parlé plus haut. La représentation unaire permet d'encoder N+1 valeurs sur N bits, car le zéro est encodé à part. Une valeur pour le zéro, plus une par bit.
Avec la '''représentation ''one-hot''''', un nombre est codé sur N bits, dont un seul bit est à 1. La position du 1 dans le nombre indique sa valeur. Précisément, la valeur encodée est égale au poids du bit à 1. En faisant cela, le zéro est codé en mettant le bit de poids faible à 1, la valeur 1 est codée avec les bits 00...00010, la valeur 2 est codée par les bits 00...000100, etc.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Elle permet d'encoder N valeurs différentes avec N bits, zéro inclus. On perd une valeur par rapport à l'unaire, car le zéro n'et pas encodé à part du reste. La traduction du binaire vers le ''one-hot'' est réalisé par un circuit nommé décodeur, que nous verrons d'ici quelques chapitres. De même, la traduction d'un nombre encodé en ''one-hot'' vers le binaire est réalisée par un circuit électronique appelé l'encodeur, qui est en quelque sorte l'inverse de l'encodeur. Les deux circuits sont très utilisés en électronique, ce sont des circuits de base, que nous n'aurons de cesse d'utiliser pour fabriquer d'autres circuits.
L'utilité de ces deux représentations n'est pas évidente. Mais sachez qu'elle le deviendra quand nous parlerons des circuits appelés les "compteurs", tout comme ce sera le cas pour le code Gray. Elles sont très utilisées dans des circuits appelés des machines à état, qui doivent incorporer des circuits compteurs efficients. Et ces représentations permettent d'avoir des circuits pour compter qui sont très simples, efficaces, rapides et économes en circuits électroniques. La représentation unaire sera aussi utile à la toute fin du cours, dans les chapitres liés à l'exécution dans le désordre. Il en sera fait référence quand nous parlerons de fenêtres d'instruction, d'émission dans l'ordre, de ''scoreboarding'', etc.
Il faut aussi noter qu'il a existé des ordinateur qui manipulaient des nombres codés en unaire. Enfin presque, la représentation utilisée était proche de l'unaire, mais ressemblait fortement. De tels ordinateurs étaient appelés des ''ordinateurs stochastiques'' et nous les aborderons à la toute fin de ce wikilivre, dans un chapitre annexe portant sur les ordinateurs ternaires (qui comptent en base 3) et unaires.
==Les encodages hybrides entre décimal et binaire==
Pour terminer, nous allons voir les représentations qui mixent binaire et décimal. Il s'agit en réalité d'encodage qui permettent de manipuler des nombres codés en décimal, mais les chiffres sont codés en utilisant des bits. L'encodage ''Binary Coded Decimal'' est le plus connu de cette catégorie, mais il y en a quelques autres, qui sont moins connus. De telles représentations étaient très utilisées au début de l'informatique, mais sont aujourd'hui tombées en désuétude. Pour désigner ces encodages, nous parlerons d''''encodages bit-décimaux''' : décimaux pour préciser qu'ils encodent des nombres codés en décimal, bit pour préciser que les chiffres sont codés avec des bits.
De tels encodages étaient utilisés sur les tous premiers ordinateurs pour faciliter le travail des programmeurs, mais aussi sur les premières calculettes. Ils sont utiles dans les applications où on doit manipuler chaque chiffre décimal séparément des autres. Un exemple classique est celui d'une horloge digitale. Ils ont aussi l'avantage de bien se marier avec les nombres à virgule. Les représentations des nombres à virgule fixe ou flottante ont le défaut que des arrondis peuvent survenir. Par exemple, la valeur 0,2 est codée comme suit en binaire normal : 0.00110011001100... et ainsi de suite jusqu’à l'infini. Avec les encodages bit-décimaux, on n'a pas ce problème, 0,2 étant codé 0000 , 0010.
Il a existé des ordinateurs qui travaillaient uniquement avec de tels encodages, appelés des '''ordinateurs décimaux'''. Ils étaient assez courants entre les années 60 et 70, même s'ils ne représentent pas la majorité des architectures de l'époque. Avec eux, la mémoire n'était pas organisée en octets, mais elle stockait des chiffres décimaux codés sur 5-6 bits. Le processeur faisait des calculs sur des chiffres bit-décimaux directement.
Leur grand avantage est leur très bonne performance dans les taches de bureautique, de comptabilité, et autres. Les processeurs de l'époque recevaient des entiers codés en bit-décimal de la part des entrées-sorties, et devaient les traiter. Les processeurs binaires devaient faire des conversions décimal-binaire pour communiquer avec les entrées-sorties, mais pas les processeurs décimaux. Le gain en performance pouvait être substantiel dans certaines applications.
Les ordinateurs décimaux se classent en deux sous-types bien précis. Les premiers gèrent des entiers qui ont un nombre de chiffres fixes. Par exemple, l'IBM 7070 gérait des entiers de 10 chiffres, plus un signe +/- pour les entiers signés. Le processeur faisait les calculs directement en bit-décimal, il gérait des entiers faisant environ 10-15 chiffres décimaux et savait faire des calculs avec de tels nombres. Le second sous-type effectue les calculs chiffre par chiffre et géraient des nombres de taille variable, sans limite de chiffres ! Nous reparlerons de ces derniers dans un chapitre ultérieur, quand nous parlerons de la différence entre byte et mot. Pour le moment, ne gardez à l'esprit que les processeurs gérant un nombre fixe de chiffres décimaux, plus simples à comprendre.
===Le ''Binary Coded Decimal''===
Le '''''Binary Coded Decimal''''', abrévié BCD, est une représentation qui mixe binaire et décimal. Avec cette représentation, les nombres sont écrits en décimal, comme nous en avons l'habitude dans la vie courante, sauf que chaque chiffre décimal est directement traduit en binaire sur 4 bits. Prenons l'exemple du nombre 624 : le 6, le 2 et le 4 sont codés en binaire séparément, ce qui donne 0110 0010 0100.
{| class="wikitable"
|+ Codage BCD
|-
! Nombre encodé (décimal)
! scope="col" colspan="4" | BCD
|-
| 0 || 0 || 0 || 0 || 0
|-
| 1 || 0 || 0 || 0 || 1
|-
| 2 || 0 || 0 || 1 || 0
|-
| 3 || 0 || 0 || 1 || 1
|-
| 4 || 0 || 1 || 0 || 0
|-
| 5 || 0 || 1 || 0 || 1
|-
| 6 || 0 || 1 || 1 || 0
|-
| 7 || 0 || 1 || 1 || 1
|-
| 8 || 1 || 0 || 0 || 0
|-
| 9 || 1 || 0 || 0 || 1
|}
On peut remarquer que 4 bits permettent de coder 16 valeurs, là où il n'y a que 10 chiffres. Dans le BCD proprement dit, les combinaisons de bits qui correspondent à 10, 11, 12, 13, 14 ou 15 ne sont tout simplement pas prises en compte. Sur quelques ordinateurs, ces combinaisons codent des chiffres décimaux en double : certains chiffres pouvaient être codés de deux manières différentes. Il est aussi possible d'utiliser ces valeurs pour coder quelque chose d'autre que des chiffres. Par exemple, il est possible de les utiliser pour coder un signe + ou -, afin de gérer les entiers relatifs. Une autre possibilité, complémentaire de la précédente, utilise ces valeurs en trop pour coder une virgule, afin de gérer les nombres non-entiers. Les possibilités sont nombreuses.
Le support du BCD implique souvent que le processeur supporte des '''opérations BCD''', à savoir des opérations capables de travailler sur des opérandes en BCD et de donner un résultat en BCD. Il faut bien faire la différence entre les opérations en binaire et les opérations en BCD. Par exemple, on n'effectue pas une addition de la même manière en binaire et en décimal/BCD, même si les grandes lignes sont presque identiques. Les différences font que les processeurs doivent avoir des opérations différentes pour les deux encodages, de la même manière que les processeurs gèrent les opérations sur les flottants et les entiers séparément.
Le support du codage BCD est abandonné de nos jours, c'est surtout quelque chose qu'on trouve sur les anciens processeurs. Les architectures 8 et 16 bits supportaient à la fois des opérations binaires et des opérations BCD. Mais il a aussi existé une méthode intermédiaire, qui utilisait des additions binaires normales sur des opérandes BCD. L'idée est que le résultat de l'addition est incorrect, car les valeurs 10 à 15 peuvent apparaître comme chiffre dans le résultat. Mais on peut le corriger pour obtenir le résultat exact en BCD. Pour résumé, les processeurs faisaient des additions en binaire, et corrigeaient le résultat avec une opération spécifique pour obtenir un résultat en BCD. Nous en reparlerons dans le chapitre sur le langage machine et l'assembleur, dans lequel nous étudierons les différentes opérations que supporte un processeur.
===Les encodages compacts du BCD===
Des variantes du BCD visent à réduire le nombre de bits utilisés pour encoder un nombre décimal. Nous allons les appeler les '''encodages BCD compacts'''. Avec certaines variantes, on peut utiliser seulement 7 bits pour coder deux chiffres décimaux, au lieu de 8 bits en BCD normal. Idem pour les nombres à 3 chiffres décimaux, qui prennent 10 bits au lieu de 12. Cette économie est réalisée par une variante du BCD assez compliquée, appelée l''''encodage Chen-Ho'''. Une alternative, appelée le ''Densely Packed Decimal'', arrive à une compression identique, mais avec quelques avantages au niveau de l'encodage. Ces encodages sont cependant assez compliqués à expliquer, surtout à ce niveau du cours, aussi je me contente de simplement mentionner leur existence.
Les encodages BCD compacts sont utilisés par les programmeurs pour stocker des données dans un fichier, en mémoire, mais guère plus. Ils ne sont pas gérés par le processeur directement, on ne peut pas faire de calculs avec, le processeur ne gére pas d'opération BCD supportant de tels encodages. C'est théoriquement possible, mais ça n'a jamais été implémenté dans un processeur, le cout en circuit n'en valait pas la chandelle. Pour faire des calculs sur des nombres en BCD compact, on doit les décompresser et les convertir en BCD normal ou en binaire, puis faire les calculs avec des opérations BCD/binaire usuelles.
===L'encodage ''Excess-3''===
La représentation '''''Excess-3''''' (XS-3) est une variante du BCD, qui a autrefois était utilisée sur d'anciens ordinateurs décimaux. Il s'agit d'une sorte d'hybride entre une représentation par excès et BCD. Chaque chiffre décimal est codé sur plusieurs bits en utilisant une représentation binaire par excès. Le biais est de 3 pour la représentation XS-3, il en existe des variantes avec un excès de 4, 5, mais elles sont moins utilisées. Avec elle, la conversion d'un chiffre décimal se fait comme suit : on prend le chiffre décimal, on ajoute 3, puis on traduit en binaire.
{| class="wikitable"
|+ Codage en ''Excess-3''
|-
! Décimal
! scope="col" colspan="4" | Binaire
|-
| -3 || 0 || 0 || 0 || 0
|-
| -2 || 0 || 0 || 0 || 1
|-
| -1 || 0 || 0 || 1 || 0
|-
| 0 || 0 || 0 || 1 || 1
|-
| 1 || 0 || 1 || 0 || 0
|-
| 2 || 0 || 1 || 0 || 1
|-
| 3 || 0 || 1 || 1 || 0
|-
| 4 || 0 || 1 || 1 || 1
|-
| 5 || 1 || 0 || 0 || 0
|-
| 6 || 1 || 0 || 0 || 1
|-
| 7 || 1 || 0 || 1 || 0
|-
| 8 || 1 || 0 || 1 || 1
|-
| 9 || 1 || 1 || 0 || 0
|-
| 10 || 1 || 1 || 0 || 1
|-
| 11 || 1 || 1 || 1 || 0
|-
| 12 || 1 || 1 || 1 || 1
|}
L'avantage de cette représentation est que l'on peut facilement calculer les soustractions en utilisant une méthode bien précise (celle du complément à 10, je ne détaille pas plus). Le défaut est que le calcul des additions est légèrement plus complexe.
L'XS-3 a été utilisé sur quelques ordinateurs décimaux assez anciens, notamment sur l'UNIVAC I et II.
===Les codes bi-quinaires===
Les '''codes bi-quinaires''' utilisent un mélange entre encodage binaire, encodage ''one-hot'' et encodage décimal. L'idée de base derrière ces codes est d'utiliser la représentation ''one-hot'' pour coder un chiffre BCD. Le problème est que cela demande d'utiliser 10 bits pour encoder un chiffre BCD, ce qui est beaucoup. L'encodage bi-quinaire optimise le tout de manière à diviser par deux le nombre de bits requis pour l'encodage.
Pour simplifier, un chiffre encodé en bi-quinaire est composé de deux parties : une partie binaire de un bit, suivie par d'une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits pour encoder les valeurs 0, 1, 2, 3 et 4. La partie binaire indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9 : 0 à 4 si la partie binaire vaut 0, 5 à 9 si elle vaut 1. La valeur du chiffre se calcule comme suit :
* si la partie binaire vaut 0, elle est égale à la partie quinaire ;
* si la partie binaire vaut 1, on ajoute 5 à la partie quinaire.
L'originalité de ce codage est qu'il permet de facilement compter sur ses doigts : on a deux mains, cinq doigts. De nombreuses langues utilisent ce système pour coder des nombres, comme le Wolof. Et ce système est utilisé dans certains pays pour compter sur ses doigts, voire dans la vie de tous les jours.
[[File:Code Biquinaer.svg|vignette|Code bi-quinaire où partie binaire et quinaire sont encodées en représentation ''one-hot''.]]
L'encodage abordé au-dessus est une version simplifiée de l'encodage bi-quinaire, il en existe d'autres variantes. Le bi-quinaire vu juste au-dessus encode la partie binaire en binaire, et la partie quinaire en ''one-hot''. Mais il est aussi possible d'inverser l'encodage, à savoir encoder la partie binaire avec une représentation ''one-hot'', alors que la partie bi-quinaire est encodée en binaire normal.
Cependant, la version la plus intéressante utilise l'encodage ''one-hot'' pour les deux ! Elle est illustrée ci-contre. Avec cette dernière, un chiffre BCD est encodé sur 7 bits, dont 2 sont obligatoirement à 1 et les 5 restants sont à 0. Cette propriété permet de détecter qu'un bit a été inversé suite à une erreur de transmission ou un problème de stockage. Si un bit est inversé, le résultat aura 1 bit à 1 et 6 à 0, ou alors 3 bits à 1 et 4 à 0, ce qui ne colle pas. Le résultat après inversion ne donnera pas un chiffre bi-quinaire valide.
Une autre variante du bi-quinaire a été utilisée sur l'IBM 1401. Avec elle, la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre encodé par la partie quinaire. L'avantage de ce système est que la gestion des retenues lors des additions était grandement simplifiée.
Le bi-quinaire et ses variantes ont été utilisés sur d'anciens ordinateurs, dont l'IBM 650, l'UNIVAC Solid State et le UNIVAC LARC. La plupart utilisaient des variantes de l'encodage bi-quinaire proprement dit. Pour rentrer vraiment dans le détail, voici les encodages utilisés sur ces machines. La lecture est facultative, il est possible de passer directement à la section suivante :
{| class="wikitable"
|-
! !! IBM 650 !! Remington Rand 409 !! UNIVAC Solid State !! UNIVAC LARC
|-
| Chiffre || 1357-9 bits || 05-01234 || p-5-421 bits || p-5-qqq bits
|-
| 0 || 10-10000 || 0000-0 || 1-0-000 || 1-0-000
|-
| 1 || 10-01000 || 1000-0 || 0-0-001 || 0-0-001
|-
| 2 || 10-00100 || 1000-1 || 0-0-010 || 1-0-011
|-
| 3 || 10-00010 || 0100-0 || 1-0-011 || 0-0-111
|-
| 4 || 10-00001 || 0100-1 || 0-0-100 || 1-0-110
|-
| 5 || 01-10000 || 0010-0 || 0-1-000 || 0-1-000
|-
| 6 || 01-01000 || 0010-1 || 1-1-001 || 1-1-001
|-
| 7 || 01-00100 || 0001-0 || 1-1-010|| 0-1-011
|-
| 8 || 01-00010 || 0001-1 || 0-1-011 || 1-1-111
|-
| 9 || 01-00001 || 0000-1 || 1-1-100|| 0-1-110
|}
==Annexe : pourquoi l'octet existe==
Nous avons vu plus haut que les nombres encodés en binaire sont découpés en octets, des paquets de 8 bits. Et les octets sont présents partout dans la conception d'un ordinateur. Par exemple, la mémoire RAM d'un ordinateur moderne est organisée en octet. Elle contient des cases mémoire, chacune contenant un octet. Les octets, les cases mémoire, sont numérotées, ce qui permet de sélectionner un octet parmi tous les autres. La capacité de la mémoire est le nombre d'octets qu'elle contient, elle s'exprime en kilo-octets (1024 octets), mébi-octets (1024 kilo-octets), gibi-octets (1024 mébi-octets). Les octets sont donc présents partout dans l'informatique moderne.
{|class="wikitable"
|+ Contenu de la RAM, simplifié
|-
! Numéro
! Octet
|-
! 0
| 0000 1111
|-
! 1
| 0101 0101
|-
! 2
| 0111 0110
|-
! 3
| 0011 1010
|-
! 4
| 1001 1001
|-
! ...
| ...
|-
! 1023
| 0110 1010
|-
! 1024
| 0001 1110
|}
Mais pourquoi est-ce que les nombres encodés en binaire sont souvent découpés en octets ? Pourquoi ne pas utiliser des groupes de 3, 5, 6, 7, 9, 10 bits ? La question est d'autant plus légitime que d'anciens ordinateurs faisaient ainsi. Leurs mémoires étaient découpé en cases mémoire, mais chacune faisait 7 ou 9 bits, rien d'impossible à cela. À vrai dire, l'usage des octets au tout début de l'informatique était assez rare. Il y a eu un basculement progressif vers l'octet pendant les années 80-90. Reste à expliquer pourquoi. Si on devait résumer, les deux raisons principales sont le support du BCD et le support des caractères pour le texte. Voyons d'abord l'encodage des caractères en binaire.
===L'encodage du texte : ASCII et autres encodages===
Pour les caractères, les encodages se sont concentrés sur l'encodage des alphabets occidentaux. Ils demandaient d'encoder au minimum toutes les lettres, en majuscules et minuscules, plus les chiffres, une dizaine de caractères pour la ponctuation. À cela, il faut ajouter des symboles utiles comme %, µ, $, £, #, {} et bien d'autres. Au minimum, cela fait au minimum 52 lettres (26 lettres doublées pour les majuscules) + 10 chiffres + une dizaine de caractères pour la ponctuation : cela fait plus de 72 valeurs à encoder. Pour encoder cela en binaire, le minimum est de 7 bits, ce qui permet d'encoder 128 caractères : les 72 caractères minimum, plus quelques autres. L'encodage ASCII utilisait d'ailleurs 7 bits pour encoder des nombres.
On pourrait croire que c'était une raison suffisante pour utiliser des groupes de 7 bits. Mais il y a quelques problèmes avec cela. Premièrement, les encodages 7 bits pour le texte marchent bien pour gérer la langue anglaise et la plupart des langues occidentales, mais d'autres langues ont besoin d'encoder des caractères locaux. Les 128 caractères de l'ASCII ne sont pas suffisant, ce qui demande de passer de 7 à 8 bits. On retombe donc sur un octet ! D'ailleurs, les encodages ASCII améliorés utilisaient les 127 premières valeurs pour l'ASCII normal, mais les 128 suivantes encodaient d'autres caractères. Les encodages en question varient d'un pays ou d'une langue à l'autre, mais ils sont terriblement importants, ce qui est un argument pour l'usage des octets.
===Le support des données encodées sur un ''nibble''===
Une autre raison pour l'usage des octets est qu'il est facile de les découper en deux ''nibbles''. Pour rappel, les ''nibbles'' sont des groupes de 4 bits. Et il se trouve que de nombreuses données sont encodées sur 4 bits, ni plus ni moins. Les ''nibbles'' sont très important pour le support du BCD et de l’hexadécimal. Et c'était un avantage important au début de l'informatique, moins maintenant. Un octet regroupe deux ''nibbles'' : un ''nibble'' de poids faible, un ''nibble'' de poids fort.
[[File:Nibbles in a byte.PNG|centre|vignette|upright=2|Nibbles dans un octet.]]
Un chiffre BCD occupe un ''nibble'', ce qui fait que la taille idéale de l'unité mémoire doit être un multiple de 4 bits : 4 bits, 8 bits, 12 bits, 16 bits, etc. Un ''nibble'' de 4 bits est insuffisant pour encoder des caractères, 8 bits fonctionne parfaitement, pas besoin d'aller plus loin. De plus, l'usage des ''nibbles'' permet de gérer la représentation hexadécimale très facilement. Un chiffre hexadécimal est encodé sur exactement un ''nibble'', un octet regroupe deux chiffres hexadécimaux, deux octets 4 chiffres hexadécimaux, etc. Enfin, 8 bits est une puissance de deux, ce qui simplifie grandement l'encodage des entiers en binaire est est une source de nombreuses simplifications. Les circuits de l'ordinateur sont plus simple à concevoir quand ils manipulent des opérandes de 2, 4, 8, 16, 32, 64, 128 bits.
===L'influence sur la taille des opérandes===
Les anciens ordinateurs étaient conçus pour le calcul scientifique, ou du moins des applications similaires. Ils n'avaient pas besoin de supporter le BCD, ni même de gérer du texte. Ils ne faisaient que des calculs et utilisaient des opérandes flottantes. La taille des opérandes était dictée par des considérations calculatoires. La norme à l'époque était que les calculs scientifiques devaient gérer des opérandes pouvant aller jusqu'à 10 chiffres décimaux. Et cela demande d'utiliser des opérandes entiers de 35 bits, avec un bit en plus pour encoder des opérandes positives ou négatives. Les 36 bits étaient parfois découpés en 6 groupes de 6 bits chacun, au lieu des octets de 8 bits.
Mais ces groupes de 6 bits étaient souvent peu utilisés. Prenons l'exemple des mémoires RAM. Plus haut, j'ai dit qu'une mémoire RAM normale est composée de plusieurs octets, tous numérotés. Intuitivement, vous vous dites que les anciens ''mainframes'' remplaçaient ces octets par des groupes de 6 bits dans la RAM. Mais en réalité, l'équivalent des RAM de l'époque utilisait des groupes de 36 bits. les groupes de 36 bits étaient aussi numérotés, les numéros étant appelés des adresses mémoire. Par contre, les adresses/numéros étaient codés sur 12 ou 18 bits, c'est à dire sur 2 ou 3 groupes de 6 bits. La raison est que ces numéros sont manipulés par le processeur, qui gère des opérandes de 36 bits. L'idée est que le numéro ne prend que la moitié ou un tiers des 36 bits gérés par le processeur.
Avec l'apparition de l'informatique commerciale, les ordinateurs ont du s'adapter aux besoins des entreprises, pour gérer la comptabilité, des applications financières, des documents texte, etc. Et cela demandait de supporter à la fois le BCD et les caractères/lettres. Il a fallu trouver une unité mémoire capable de gérer à la fois nombres entiers, chiffres BCD et caractères. L'octet était parfait pour ça. Les ''mainframes'' sont adaptés en supportant des caractères codés sur 6, 7 ou 8 bits. Mais le support demandait d'utiliser des circuits qui découpaient un groupe de 36 bits en plusieurs caractères ou en plusieurs chiffres BCD. Le tout demandait des circuits de décalage et de masquage assez complexes. Les ordinateurs 8 et 16 bits manipulaient des octets nativement : leur mémoire RAM gérait des octets, le processeur traitait des opérandes de 8, 16 ou 32 bits. Ils n'avaient pas besoin de matériel complexe pour extraire un caractère ou un chiffre BCD d'un octet/doublet/multiplet. C'était un avantage que les anciens ''mainframes'' n'avaient pas.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'encodage des données
| prevText=L'encodage des données
| next=Les codes de détection/correction d'erreur
| nextText=Les codes de détection/correction d'erreur
}}
</noinclude>
5ktn6pedo3fdlp0r7ubf46u1viaa2nd
775638
775637
2026-11-02T15:18:37Z
Mewtow
31375
/* La virgule fixe : des entiers cachés */
775638
wikitext
text/x-wiki
Dans le chapitre précédent, nous avons vu que les ordinateurs actuels utilisent un codage binaire. Ce codage binaire ne vous est peut-être pas familier. Aussi, dans ce chapitre, nous allons apprendre comment coder des nombres en binaire. Nous allons commencer par le cas le plus simple : les nombres positifs. Par la suite, nous aborderons les nombres négatifs. Et nous terminerons par les nombres à virgules, appelés aussi nombres flottants.
==Le codage des nombres entiers positifs==
Pour coder des nombres entiers positifs, il existe plusieurs méthodes : le binaire, l’hexadécimal, le code Gray, le décimal codé binaire et bien d'autres encore. La plus connue est certainement le binaire, secondée par l'hexadécimal, les autres étant plus anecdotiques. Pour comprendre ce qu'est le binaire, il nous faut faire un rappel sur les nombres entiers tel que vous les avez appris en primaire, à savoir les entiers écrits en décimal. Prenons un nombre écrit en décimal : le chiffre le plus à droite est le chiffre des unités, celui à côté est pour les dizaines, suivi du chiffre des centaines, et ainsi de suite. Dans un tel nombre :
* on utilise une dizaine de chiffres, de 0 à 9 ;
* chaque chiffre est multiplié par une puissance de 10 : 1, 10, 100, 1000, etc. ;
* la position d'un chiffre dans le nombre indique par quelle puissance de 10 il faut le multiplier : le chiffre des unités doit être multiplié par 1, celui des dizaines par 10, celui des centaines par 100, et ainsi de suite.
Exemple avec le nombre 1337 :
<math>1337 = 1 \times 1000 + 3 \times 100 + 3 \times 10 + 7 \times 1</math>
Pour résumer, un nombre en décimal s'écrit comme la somme de produits, chaque produit multipliant un chiffre par une puissance de 10. On dit alors que le nombre est en base 10.
<math>1337 = 1 \times 10^3 + 3 \times 10^2 + 3 \times 10^1 + 7 \times 10^0</math>
Ce qui peut être fait avec des puissances de 10 peut être fait avec des puissances de 2, 3, 4, 125, etc : n'importe quel nombre entier strictement positif peut servir de '''base'''. En informatique, on utilise rarement la base 10 à laquelle nous sommes tant habitués. On utilise à la place deux autres bases :
* La base 2 (système binaire) : les chiffres utilisés sont 0 et 1 ;
* La base 16 (système hexadécimal) : les chiffres utilisés sont 0, 1, 2, 3, 4, 5, 6, 7, 8 et 9 ; auxquels s'ajoutent les six premières lettres de notre alphabet : A, B, C, D, E et F.
===Le système binaire===
En binaire, on compte en base 2. Cela veut dire qu'au lieu d'utiliser des puissances de 10 comme en décimal, on utilise des puissances de deux : n'importe quel nombre entier peut être écrit sous la forme d'une somme de puissances de 2. Par exemple 6 s'écrira donc 0110 en binaire : <math>0 \times 2^3 + 1 \times 2^2 + 1 \times 2^1 + 0 \times 2^0 = 6</math>. On peut remarquer que le binaire n'autorise que deux chiffres, à savoir 0 ou 1 : ces chiffres binaires sont appelés des '''bits''' (abréviation de Binary Digit). Pour simplifier, on peut dire qu'un bit est un truc qui vaut 0 ou 1. Pour résumer, tout nombre en binaire s'écrit sous la forme d'un produit entre bits et puissances de deux de la forme :
<math>a_0 \times 2^0 + a_1 \times 2^1 + a_2 \times 2^2 + a_3 \times 2^3 + a_4 \times 2^4 +...+ a_n \times 2^n</math>
Les coefficients <math>a_0, a_1, a_2</math> sont les bits, l'exposant n qui correspond à un bit <math>a_n</math> est appelé le '''poids''' du bit.
====La terminologie du binaire====
En informatique, il est rare que l'on code une information sur un seul bit. Dans la plupart des cas, l'ordinateur manipule des nombres codés sur plusieurs bits. Les informaticiens ont donné des noms aux groupes de bits suivant leur taille. Le plus connu est certainement l''''octet''', qui désigne un groupe de 8 bits. Moins connu, on parle de '''''nibble''''' pour un groupe de 4 bits (un demi-octet), de '''doublet''' pour un groupe de 16 bits (deux octets) et de '''quadruplet''' pour un groupe de 32 bits (quatre octets).
Précisons qu'en anglais, le terme ''byte'' n'est pas synonyme d'octet. En réalité, le terme ''octet'' marche aussi bien en français qu'en anglais. Quant au terme ''byte'', il désigne un concept complètement différent, que nous aborderons plus tard (c'est la plus petite unité de mémoire que le processeur peut adresser). Il a existé dans le passé des ordinateurs où le ''byte'' faisait 4, 7, 9, 16, voire 48 bits, par exemple. Il a même existé des ordinateurs où le ''byte'' faisait exactement 1 bit ! Mais sur presque tous les ordinateurs modernes, un ''byte'' fait effectivement 8 bits, ce qui fait que le terme ''byte'' est parfois utilisé en lieu et place d'''octet''. Mais c'est un abus de langage, attention aux confusions ! Dans ce cours, nous parlerons d'octet pour désigner un groupe de 8 bits, en réservant le terme ''byte'' pour sa véritable signification.
À l'intérieur d'un nombre, le '''bit de poids faible''' est celui qui est le plus à droite du nombre, alors que le '''bit de poids fort''' est celui non nul qui est placé le plus à gauche, comme illustré dans le schéma ci-dessous.
C'est le même principe que l'écriture des nombres en base décimale : le chiffre le plus significatif à gauche, et le moins significatif à droite (l'unité ou les décimales après la virgule).
[[File:Msb.svg|centre|vignette|upright=2|Bit de poids fort.]]
[[File:Lsb.svg|centre|vignette|upright=2|Bit de poids faible.]]
Cette terminologie s'applique aussi pour les bits à l'intérieur d'un octet, d'un ''nibble'', d'un doublet ou d'un quadruplet. Pour un nombre codés sur plusieurs octets, on peut aussi parler de l'octet de poids fort et de l'octet de poids faible, du doublet de poids fort ou de poids faible, etc.
====La traduction binaire→décimal====
Pour traduire un nombre binaire en décimal, il faut juste se rappeler que la position d'un bit indique par quelle puissance il faut le multiplier. Ainsi, le chiffre le plus à droite est le chiffre des unités : il doit être multiplié par 1 (<math>2^0</math>). Le chiffre situé immédiatement à gauche du chiffre des unités doit être multiplié par 2 (<math>2^1</math>). Le chiffre encore à gauche doit être multiplié par 4 (<math>2^2</math>), et ainsi de suite. Mathématiquement, on peut dire que le énième bit en partant de la droite doit être multiplié par <math>2^{n-1}</math>. Par exemple, la valeur du nombre noté 1011 en binaire est de <math>8 \times 1 + 4 \times 0 + 2 \times 1 + 1 \times 1 = 11</math>.
[[File:Value of digits in the Binary numeral system.svg|centre|vignette|upright=2|Valeur des chffres dans le système de numération binaire.]]
====La traduction décimal→binaire====
La traduction inverse, du décimal au binaire, demande d'effectuer des divisions successives par deux. Les divisions en question sont des divisions euclidiennes, avec un reste et un quotient. En lisant les restes des divisions dans un certain sens, on obtient le nombre en binaire. Voici comment il faut procéder, pour traduire le nombre 34 :
[[File:Exemple d'illustration de la méthode de conversion décimal vers binaire.png|centre|vignette|upright=2|Exemple d'illustration de la méthode de conversion décimal vers binaire.]]
====Quelques opérations en binaire====
Maintenant que l'on sait coder des nombres en binaire normal, il est utile de savoir comment faire quelques opérations usuelles en binaire. Nous utiliserons les acquis de cette section dans la suite du chapitre, bien que de manière assez marginale.
La première opération est assez spécifique au binaire. Il s'agit d'une opération qui inverse les bits d'un nombre : les 0 deviennent des 1 et réciproquement. Par exemple, le nombre 0001 1001 devient 1110 0110. Elle porte plusieurs noms : opération NOT, opération NON, complémentation, etc. Nous parlerons de '''complémentation''' ou d'opération NOT dans ce qui suit. Beaucoup d'ordinateurs gèrent cette opération, ils savent la faire en un seul calcul. Il faut dire que c'est une opération assez utile, bien que nous ne pouvons pas encore expliquer pourquoi.
[[File:Binary Addition Demonstration.svg|vignette|Exemple d'addition en binaire.]]
La seconde opération à aborder est l'addition. Elle se fait en binaire de la même manière qu'en décimal : Pour faire une addition en binaire, on additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. Sauf que l'on additionne des bits. Heureusement, la table d'addition est très simple en binaire :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
La troisième opération est une variante de l'addition appelée l''''opération XOR''', notée <math>\oplus</math>. Il s'agit d'une addition dans laquelle on ne propage pas les retenues. L'addition des deux bits des opérandes se fait normalement, mais les retenues sont simplement oubliées, on n'en tient pas compte. Le résultat est que l'addition se résume à appliquer la table d'addition précédente :
* 0 <math>\oplus</math> 0 = 0 ;
* 0 <math>\oplus</math> 1 = 1 ;
* 1 <math>\oplus</math> 0 = 1 ;
* 1 <math>\oplus</math> 1 = 0.
Pour résumer, le résultat vaut 1 si les deux bits sont différents, 0 s'ils sont identiques. L'opération XOR sera utilisée rapidement dans le chapitre suivant, quand nous parlerons rapidement du mot de parité. Et elle sera beaucoup utilisée dans la suite du cours, nous en feront fortement usage. Pour le moment, mémorisez juste cette opération, elle n'a rien de compliqué.
Une dernière opération est l'opération de '''''population count'''''. Il s'agit ni plus ni moins que de compter le nombre de bits qui sont à 1 dans un nombre. Par exemple, pour le nombre 0110 0010 1101 1110, elle donne pour résultat 9. Elle est utilisée dans certaines applications, comme le calcul de certains codes correcteurs d'erreur, comme on le verra dans le chapitre suivant. Elle est supportée sur de nombreux ordinateurs, encore que cela dépende du processeur considéré. Il s'agit cependant d'une opération assez courante, supportée par les processeurs ARM, les processeurs x86 modernes (ceux qui gèrent le SSE), et quelques autres.
===L'hexadécimal===
L’hexadécimal est basé sur le même principe que le binaire, sauf qu'il utilise les 16 chiffres suivants :
{| class="wikitable"
|- style="color:#8080ffff; text-align:center;"
! Chiffre hexadécimal
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || A || B || C || D || E || F
|- style="text-align:center;"
! Nombre décimal correspondant
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 10 || 11 || 12 || 13 || 14 || 15
|- style="text-align:center;"
! Notation binaire
| 0000 || 0001 || 0010 || 0011 || 0100 || 0101 || 0110 || 0111 || 1000 || 1001 || 1010 || 1011 || 1100 || 1101 || 1110 || 1111
|}
Dans les textes, afin de différencier les nombres décimaux des nombres hexadécimaux, les nombres hexadécimaux sont suivis par un petit h, indiqué en indice. Si cette notation n'existait pas, des nombres comme 2546 seraient ambigus : on ne saurait pas dire sans autre indication s'ils sont écrits en décimal ou en hexadécimal. Avec la notation, on sait de suite que 2546 est en décimal et que 2546h est en hexadécimal.
Dans les codes sources des programmes, la notation diffère selon le langage de programmation.
Certains supportent le suffixe <code>h</code> pour les nombres hexadécimaux, d'autres utilisent un préfixe <code>0x</code> ou <code>0h</code>.
Pour convertir un nombre hexadécimal en décimal, il suffit de multiplier chaque chiffre par la puissance de 16 qui lui est attribuée. Là encore, la position d'un chiffre indique par quelle puissance celui-ci doit être multiplié : le chiffre le plus à droite est celui des unités, le second chiffre le plus à droite doit être multiplié par 16, le troisième chiffre en partant de la droite doit être multiplié par 256 (16 * 16) et ainsi de suite. La technique pour convertir un nombre décimal vers de l’hexadécimal est similaire à celle utilisée pour traduire un nombre du décimal vers le binaire. On retrouve une suite de divisions successives, mais cette fois-ci les divisions ne sont pas des divisions par 2 : ce sont des divisions par 16.
La conversion inverse, de l'hexadécimal vers le binaire est très simple, nettement plus simple que les autres conversions. Pour passer de l'hexadécimal au binaire, il suffit de traduire chaque chiffre en sa valeur binaire, celle indiquée dans le tableau au tout début du paragraphe nommé « Hexadécimal ». Une fois cela fait, il suffit de faire le remplacement. La traduction inverse est tout aussi simple : il suffit de grouper les bits du nombre par 4, en commençant par la droite (si un groupe est incomplet, on le remplit avec des zéros). Il suffit alors de remplacer le groupe de 4 bits par le chiffre hexadécimal qui correspond.
==Interlude propédeutique : la capacité d'un entier et les débordements d'entiers==
Dans la section précédente, nous avons vu comment coder des entiers positifs en binaire ou dans des représentations proches. La logique voudrait que l'on aborde ensuite le codage des entiers négatifs. Mais nous allons déroger à cette logique simple, pour des raisons pédagogiques. Nous allons faire un interlude qui introduira des notions utiles pour la suite du chapitre. De plus, ces concepts seront abordés de nombreuses fois dans ce wikilivre et l'introduire ici est de loin la solution idéale.
===Les ordinateurs manipulent des nombres codés sur un nombre fixe de bits===
Vous avez certainement déjà entendu parler de processeurs 32 ou 64 bits. Et si vous avez joué aux jeux vidéos durant votre jeunesse et êtes assez agé, vous avez entendu parler de consoles de jeu 8 bits, 16 bits, 32 bits, voire 64 bits (pour la Jaguar, et c'était un peu trompeur). Derrière cette appellation qu'on retrouvait autrefois comme argument commercial dans la presse se cache un concept simple. Tout ordinateur manipule des nombres entiers dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Une console 16 bits manipulait des entiers codés en binaire sur 16 bits, pas un de plus, pas un de moins. Pareil pour les anciens ordinateurs 32 bits, qui manipulaient des nombres entiers codés sur 32 bits.
Aujourd'hui, les ordinateurs modernes utilisent presque un nombre de bits qui est une puissance de 2 : 8, 16, 32, 64, 128, 256, voire 512 bits. Mais cette règle souffre évidemment d'exceptions. Aux tout débuts de l'informatique, certaines machines utilisaient 3, 7, 13, 17, 23, 36 et 48 bits ; mais elles sont aujourd'hui tombées en désuétude. De nos jours, il ne reste que les processeurs dédiés au traitement de signal audio, que l'on trouve dans les chaînes HIFI, les décodeurs TNT, les lecteurs DVD, etc. Ceux-ci utilisent des nombres entiers de 24 bits, car l'information audio est souvent codée par des nombres de 24 bits.
Anecdote amusante, il a existé des ordinateurs de 1 bit, qui sont capables de manipuler des nombres codés sur 1 bit, pas plus. Un exemple est le Motorola MC14500B, commercialisé de 1976.
====Le lien entre nombre de bits et valeurs codables====
Évidemment, on ne peut pas coder tous les entiers possibles et imaginables avec seulement 8 bits, ou 16 bits. Et il parait intuitif que l'on ait plus de valeurs codables sur 16 bits qu'avec 8 bits, par exemple. Plus le nombre de bits est important, plus on pourra coder de valeurs entières différentes. Mais combien de plus ? Par exemple, si je passe de 8 bits à 16 bits, est-ce que le nombre de valeurs que l'on peut coder double, quadruple, pentuple ? De même, combien de valeurs différentes on peut coder avec <math>n</math> bits. Par exemple, combien de nombres différents peut-on coder avec 4, 8 ou 16 bits ? La section précédente vous l'expliquer.
Avec <math>n</math> bits, on peut coder <math>2^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>2^n - 1</math>. N'oubliez pas cette formule : elle sera assez utile dans la suite de ce tutoriel. Pour exemple, on peut coder 16 valeurs avec 4 bits, qui vont de 0 à 15. De même, on peut coder 256 valeurs avec un octet, qui vont de 0 à 255. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de bits
! Nombre de valeurs codables
|-
! 4
| 16
|-
! 8
| 256
|-
! 16
| 65 536
|-
! 32
| 4 294 967 296
|-
! 64
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien de bits il faut pour coder une valeur quelconque, que nous noterons N. Pour cela, il faut utiliser la formule précédente, mais à l'envers. On cherche alors <math>n</math> tel que <math>N = 2^n</math>. L'opération qui donne <math>n</math> est appelée le logarithme, et plus précisément un logarithme en base 2, noté <math>\log_2{(X)}</math>. Le problème est que le résultat du logarithme ne tombe juste que si le nombre X est une puissance de 2. Si ce n'est pas le cas, le résultat est un nombre à virgule, ce qui n'a pas de sens pratique. Par exemple, la formule nous dit que pour coder le nombre 13, on a besoin de 3,70043971814 bits, ce qui est impossible. Pour que le résultat ait un sens, il faut arrondir à l'entier supérieur. Pour l'exemple précédent, les 3,70043971814 bits s'arrondissent en 4 bits.
====Le lien entre nombre de chiffres hexadécimaux et valeurs codables====
Maintenant, voyons combien de valeurs peut-on coder avec <math>n</math> chiffres hexadécimaux. La réponse n'est pas très différente de celle obtenue en binaire, si ce n'est qu'il faut remplacer le 2 par un 16 dans la formule précédente. Avec <math>n</math> chiffres hexadécimaux, on peut coder <math>16^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>16^n - 1</math>. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de chiffres héxadécimaux
! Nombre de valeurs codables
|-
! 1 (4 bits)
| 16
|-
! 2 (8 bits)
| 256
|-
! 4 (16 bits)
| 65 536
|-
! 8 (32 bits)
| 4 294 967 296
|-
! 16 (64 bits)
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien faut-il de chiffres hexadécimaux pour coder une valeur quelconque en hexadécimal. La formule est là encore la même qu'en binaire, sauf qu'on remplace le 2 par un 16. Pour trouver le nombre de chiffres hexadécimaux pour encoder un nombre X, il faut calculer <math>\log_{16}{(X)}</math>. Notons que le logarithme utilisé est un logarithme en base 16, et non un logarithme en base 2, comme pour le binaire. Là encore, le résultat ne tombe juste que si le nombre X est une puissance de 16 et il faut arrondir à l'entier supérieur si ce n'est pas le cas.
Une propriété mathématique des logarithmes nous dit que l'on peut passer d'un logarithme en base X et à un logarithme en base Y avec une simple division, en utilisant la formule suivante :
: <math>\log_X{(N)} = {\log_{Y}{(N)} \over \log_Y(X)}</math>
Dans le cas qui nous intéresse, on a Y = 2 et X = 16, ce qui donne :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over \log_2(16)}</math>
Or, <math>\log_2(16)</math> est tout simplement égal à 4, car il faut 4 bits pour coder la valeur 16. On a donc :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over 4}</math>
En clair, il faut quatre fois moins de chiffres hexadécimaux que de bits, ce qui est assez intuitif vu qu'il faut 4 bits pour coder un chiffre hexadécimal.
===Les débordements d'entier===
On vient de voir que tout ordinateur manipule des nombres dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Et cela limite les valeurs qu'il peut encoder, qui sont comprises dans un intervalle bien précis. Mais que ce passe-t-il si jamais le résultat d'un calcul ne rentre pas dans cet intervalle ? Par exemple, pour du binaire normal, que faire si le résultat d'un calcul atteint ou dépasse <math>2^n</math> ? Dans ce cas, le résultat ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
On peut imaginer d'autres codages pour lesquels les entiers ne commencent pas à zéro ou ne terminent pas à <math>2^n - 1</math>. On peut prendre le cas où l'ordinateur gère les nombres négatifs, par exemple. Dans le cas général, l'ordinateur peut coder les valeurs comprises dans un intervalle, qui va de la valeur la plus basse <math>N_\text{min}</math> à la valeur la plus grande <math>N_\text{max}</math>. Et encore une fois, si un résultat de calcul sort de cet intervalle, on fait face à un débordement d'entier.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math>, avec <math>N_\text{max}</math> la plus grande valeur codable par l'ordinateur.
On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur basse de débordement vaut <math>N_\text{min} - 1</math>, avec <math>N_\text{min}</math> la plus petite valeur codable par l'ordinateur.
===La gestion des débordements d'entiers===
Face à un débordement d'entier, l'ordinateur peut utiliser deux méthodes : l'arithmétique saturée ou l'arithmétique modulaire.
L''''arithmétique saturée''' consiste à arrondir le résultat pour prendre la plus grande ou la plus petite valeur. Si le résultat d'un calcul dépasse la valeur haute de débordement, le résultat est remplacé par le plus grand entier supporté par l'ordinateur. La même chose est possible quand le résultat est inférieur à la plus petite valeur possible, par exemple lors d'une soustraction : l'ordinateur arrondit au plus petit entier possible.
Pour donner un exemple, voici ce que cela donne avec des entiers codés sur 4 bits, qui codent des nombres de 0 à 15. Si je fais le calcul 8 + 9, le résultat normal vaut 17, ce qui ne rentre pas dans l'intervalle. Le résultat est alors arrondi à 15. Inversement, si je fais le calcul 8 - 9, le résultat sera de -1, ce qui ne rentre pas dans l'intervalle : le résultat est alors arrondi à 0.
[[File:Odometer rollover.jpg|vignette|Exemple de débordement d'entier sur un compteur mécanique quelconque. L'image montre clairement que le compteur revient à zéro une fois la valeur maximale dépassée.]]
L''''arithmétique modulaire''' est plus compliquée et c'est elle qui va nous intéresser dans ce qui suit. Pour simplifier, imaginons que l'on décompte à partir de zéro. Quand on arrive à la valeur haute de débordement, on recommence à compter à partir de zéro. L'arithmétique modulaire n'est pas si contre-intuitive et vous l'utilisez sans doute au quotidien. Après tout, c'est comme cela que l'on compte les heures, les minutes et les secondes. Quand on compte les minutes, on revient à 0 au bout de 60 minutes. Pareil pour les heures : on revient à zéro quand on arrive à 24 heures. Divers compteurs mécaniques fonctionnent sur le même principe et reviennent à zéro quand ils dépassent la plus grande valeur possible, l'image ci-contre en montrant un exemple.
Mathématiquement, l'arithmétique modulaire implique des divisions euclidiennes, celles qui donnent un quotient et un reste. Lors d'un débordement, le résultat s'obtient comme suit : on divise le nombre qui déborde par la valeur haute de débordement, et que l'on conserve le reste de la division. Au passage, l'opération qui consiste à faire une division et à garder le reste au lieu du quotient est appelée le ''modulo''. Prenons l'exemple où l'ordinateur peut coder tous les nombres entre 0 et 1023, soit une valeur haute de débordement de 1024. Pour coder le nombre 4563, on fait le calcul 4563 / 1024. On obtient : <math>4563 = 4 \times 1024 + 467</math>. Le reste de la division est de 467 et c'est lui qui sera utilisé pour coder la valeur de départ, 4563. Le nombre 4563 est donc codé par la valeur 467 dans un tel ordinateur en arithmétique modulaire. Au passage, la valeur haute de débordement est toujours codée par un zéro dans ce genre d'arithmétique.
Les ordinateurs utilisent le plus souvent une valeur haute de débordement de <math>2^n</math>, avec n le nombre de bits utilisé pour coder les nombres entiers positifs. En faisant cela, l'opération modulo devient très simple et revient à éliminer les bits de poids forts au-delà du énième bit. Par exemple, reprenons l'exemple d'un ordinateur qui code ses nombres sur 4 bits. Imaginons qu'il fasse le calcul <math>13 + 3</math>, soit 1101 + 0011 = 1 0000 en binaire. Le résultat entraîne un débordement d'entier et l'ordinateur ne conserve que les 4 bits de poids faible. Cela donne : 1101 + 0011 = 0000. Comme autre exemple l'addition 1111 + 0010 ne donnera pas 17 (1 0001), mais 1 (0001).
L'avantage est que les calculs sont beaucoup plus simples avec cette méthode qu'avec les autres. L'ordinateur a juste à ne pas calculer les bits de poids fort. Pas besoin de faire une division pour calculer un modulo, pas besoin de corriger le résultat pour faire de l'arithmétique saturée.
===Aparté : quelques valeurs particulières en binaire===
Plus haut, on a dit qu'avec n bits, on peut encoder toutes les valeurs allant de <math>0</math> à <math>2^n - 1</math>. Ce simple fait permet de déterminer quelle est la valeur de certains entiers à vue d’œil. Dans ce qui va suivre, nous allons poser quelques bases que nous réutiliserons dans la suite du chapitre. Il s'agit de quelques trivias qui sont cependant assez utiles.
Le premier trivia concerne la valeur maximale <math>2^n - 1</math> : elle est encodée par un nombre dont tous les bits sont à 1.
Le deuxième trivia concerne les nombres de la forme 000...000 1111 1111, à savoir des nombres dont les x bits de poids faible sont à 1 et tous les autres valent 0. Par définition, de tels nombres codent la plus grande valeur possible sur x bits, ce qui fait qu'ils valent <math>2^x - 1</math>.
Le troisième trivia concerne les nombres de la forme 11111...0000. En clair, des nombres où on a une suite consécutive de 1 dans les bits de poids fort et les x bits de poids faibles à 0. De tels nombres valent : <math>2^n - 2^x</math>. La preuve est assez simple : ils s'obtiennent en prenant la valeur maximale <math>2^n - 1</math>, et en soustrayant un nombre de la forme <math>2^x - 1</math>.
[[File:Valeurs particulières en binaire.png|centre|vignette|upright=2|Valeurs particulières en binaire]]
Si on utilise l'arithmétique modulaire, la valeur <math>2^n</math> n'est autre que la valeur de débordement haute pour les nombres stockés sur n bits, et se code comme le zéro (il faudrait 1 bit de plus pour stocker le 1 de poids fort de <math>2^n</math>). Les 1er et 3ème nombres évoqués dans les paragraphes précédents peuvent donc se passer de <math>2^n</math> dans leur expression :
* <math>- 1</math> est codé 1111111111111111...111 (n bits à 1)
* <math>- 2^x</math> est codé 11111...111000..000000 (x bits à 0 précédés de n-x bits à 1)
==Les nombres entiers négatifs==
<noinclude>[[File:Binary Representations.svg|vignette|Représentations signées, exemples sur 4 bits.]]</noinclude>
Passons maintenant aux entiers négatifs en binaire : comment représenter le signe moins ("-") avec des 0 et des 1 ? Eh bien, il existe plusieurs méthodes :
* la représentation en signe-valeur absolue ;
* la représentation en complément à un ;
* la représentation en complément à deux;
* la représentation par excès ;
* la représentation dans une base négative ;
* d'autres représentations encore moins utilisées que les autres.
===La représentation en signe-valeur absolue===
La solution la plus simple pour représenter un entier négatif consiste à coder sa valeur absolue en binaire, et rajouter un bit qui précise si c'est un entier positif ou un entier négatif. Par convention, ce bit de signe vaut 0 si le nombre est positif et 1 s'il est négatif. On parle alors de représentation en signe-valeur absolue, aussi appelée '''représentation en signe-magnitude'''.
Avec cette technique, il y autant de nombres positifs que négatifs. Mieux : pour chaque nombre représentable en représentation signe-valeur absolue, son inverse l'est aussi. Ce qui fait qu'avec cette méthode, le zéro est codé deux fois : on a un -0, et un +0. Cela pose des problèmes lorsqu'on demande à notre ordinateur d'effectuer des calculs ou des comparaisons avec zéro.
[[File:Codage sur 4 bits en signe-valeur absolue.png|centre|vignette|upright=2|Codage sur 4 bits en signe-valeur absolue]]
===La représentation en complément à un===
La représentation en '''complément à un''' peut être vue, en première approximation, comme une variante de la représentation en signe-magnitude. Et je dis bien : "en première approximation", car il y a beaucoup à dire sur la représentation en complément à un, mais nous verrons cela dans la section suivante sur le complément à deux. Conceptuellement, la représentation en signe-magnitude et celle du complément à un sont drastiquement différentes et sont basées sur des concepts mathématiques totalement différents. Mais elles ont des ressemblances de surface, qui font que faire la comparaison entre les deux est assez utile pédagogiquement parlant.
En complément à un, les nombres sont codés en utilisant un bit de signe qui indique si le nombre de positif ou négatif, couplé à une valeur absolue. Par contre, la valeur absolue est codée différemment en binaire. Pour les nombres positifs, la valeur absolue est codée en binaire normal, pas de changement comparé aux autres représentations. Mais pour les valeurs négatives, la valeur absolue est codée en binaire normal, puis tous les bits sont inversés : les 0 deviennent des 1 et réciproquement. En clair, on utilise une opération de complémentation pour les nombres négatifs.
Prenons un exemple avec un nombre codé sur 4 bits, avec un cinquième bit de signe. La valeur 5 est codée comme suit : 0 pour le bit de signe, 5 donne 0101 en binaire, le résultat est 0 0101. Pour la valeur -5, le bit de signe est 1, 5 donne 0101 en binaire, on inverse les bits ce qui donne 1010 : cela donne 11010. Notez qu'on peut passer d'un résultat à l'autre avec une opération de complémentation, à savoir en inversant les bits de l'autre et réciproquement.
Il s'agit là d'une propriété générale avec le complément à 1 : l'opposé d'un nombre, à savoir passer de sa valeur positive à sa valeur négative ou inversement, se calcule avec une opération NOT, une opération de complémentation (d'où son nom). L'avantage est que les ordinateurs gèrent naturellement d'opération de complémentation. Par contre, en signe-magnitude, inverser le bit de signe est une opération spécifique et ne sert qu'à ça. Il faut donc rajouter une opération en plus pour calculer l'opposé d'un nombre.
La représentation en complément à un garde les défauts de la représentation en signe-magnitude. Le zéro est codé deux fois, avec un zéro positif et un zéro négatif. La différence est que les valeurs négatives sont dans l'ordre inverse, il y a une symétrie un peu meilleure. Les deux zéros sont d'ailleurs totalement éloignés, ils correspondent aux valeurs extrêmes encodables : le zéro positif a tous ses bits à 0, le zéro négatif a tous ses bits à 1. Le résultat est que l'implémentation des comparaisons et de certains calculs est plus complexe qu'avec le signe-magnitude, mais de peu.
[[File:Codage sur 4 bits en complément à 1.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 1.]]
===La représentation par excès===
La représentation par excès consiste à ajouter un biais aux nombres à encoder afin de les encoder par un entier positif. Pour encoder tous les nombres compris entre -X et Y en représentation par excès, il suffit de prendre la valeur du nombre à encoder, et de lui ajouter un biais égal à X. Ainsi, la valeur -X sera encodée par zéro, et toutes les autres valeurs le seront par un entier positif, le zéro sera encodé par X, 1, par X+1, etc. Par exemple, prenons des nombres compris entre -127 et 128. On va devoir ajouter un biais égal à 127, ce qui donne :
{|class="wikitable"
|-
!Valeur avant encodage!!Valeur après encodage
|-
||-127||0
|-
||-126||1
|-
||-125||2
|-
||…||…
|-
||0||127
|-
||…||…
|-
||127||254
|-
||128||255
|}
===La représentation en complément à deux===
La représentation en '''complément à deux''' est basée sur les mêmes mathématiques que le complément à un, mais fonctionne très différemment en pratique.
Si on regarde de loin, son principe est assez différent : il n'y a pas de bit de signe ni quoi que ce soit d'autre. À la place, un nombre en complément à deux est encodé comme en binaire normal, à un point près : le bit de poids fort est soustrait, et non additionné aux autres. Il a une valeur négative : on soustrait la puissance de deux associée s'il vaut 1, on ne la tient pas en compte s'il vaut 0.
Par exemple, la valeur du nombre noté 111001 en complément à deux s'obtient comme suit :
{|class="wikitable" style="margin:auto;"
|-
! -32 !! 16 !! 8 !! 4 !! 2 !! 1
|-
| 1 || 1 || 1 || 0 || 0 || 1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(8×1)+(4×0)+(2×1)+(1×1) = −32+16+8+1 = -7.
: ''Avec le complément à deux, comme avec le complément à un, le bit de poids fort vaut 0 pour les nombres positifs et 1 pour les négatifs.''
L'avantage de cette représentation est qu'elle n'a pas de double zéro : le zéro n'est encodé que par une seule valeur. Par contre, la valeur autrefois prise par le zéro négatif est réutilisée pour encoder une valeur négative. La conséquence est que l'on a un nombre négatif en plus d'encodé, il n'y a plus le même nombre de valeurs strictement positives et de valeurs négatives encodées. Le nombre négatif en question est appelé le '''nombre le plus négatif''', ce nom trahit le fait que c'est celui qui a la plus petite valeur (la plus grande valeur absolue). Il est impossible de coder l'entier positif associé, sa valeur absolue.
[[File:Codage sur 4 bits en complément à 2.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 2.]]
====Le calcul du complément à deux : première méthode====
Les représentations en complément à un et en complément à deux sont basées sur le même principe mathématique. Leur idée est de remplacer chaque nombre négatif par un nombre positif équivalent, appelé le ''complément''. Par équivalent, on veut dire que tout calcul donne le même résultat si on remplace un nombre négatif par son complément (idem avec un nombre positif, son complément aura un signe inverse). Et pour faire cela, elles se basent sur les débordements d'entier pour fonctionner, et plus précisément sur l'arithmétique modulaire abordée plus haut.
Si on fait les calculs avec le complément, les résultats du calcul entraînent un débordement d'entier, qui sera résolu par un '''modulo''' : l'ordinateur ne conserve que les bits de poids faible du résultat, les autres bits sont oubliés. Par exemple, prenons l'addition 15 + 2, 1111 + 0010 en binaire : le résultat ne sera pas 17 (10001), vu qu'on n'a pas assez de bits pour encoder le résultat, mais 1 (0001). Et le résultat après modulo sera identique au résultat qu'on aurait obtenu avec le nombre négatif sans modulo. En clair, c'est la gestion des débordements qui permet de corriger le résultat de manière à ce que l'opération avec le complément donne le même résultat qu'avec le nombre négatif voulu. Ainsi, on peut coder un nombre négatif en utilisant son complément positif.
: Cela ressemble beaucoup à la méthode de soustraction basée sur un complément à 9 pour ceux qui connaissent, sauf que c'est une version binaire qui nous intéresse ici.
Prenons un exemple, qui permettra d'introduire la suite. Encore une fois, on utilise un codage sur 4 bits dont la valeur haute de débordement est de 16. Prenons l'addition de 13 + 3 = 16. Avec l'arithmétique modulaire, 16 est équivalent à 0, ce qui donne : 13 + 3 = 0 ! On peut aussi reformuler en disant que 13 = -3, ou encore que 3 = -13. Dit autrement, 3 est le complément de -13 pour ce codage. Et ne croyez pas que ça marche uniquement dans cet exemple : cela se généralise assez rapidement à tout nombre négatif.
Prenons un nombre N dont un veut calculer le complément à deux K. Dans le cas général, on a :
: <math>N + K = 2^n</math>, vu que <math>2^n = 0</math> en utilisant le modulo.
En réorganisant très légèrement les termes pour isoler K, on a :
: <math>K = 2^n - N</math>
La formule précédent permet de calculer la complément à deux assez simplement, en faisant le calcul à la main.
Avant de poursuivre, prenons un exemple très intéressant : le cas où N = -1. Son complément à deux vaut donc :
: <math>K = 2^n - 1</math>
Le terme <math>2^n - 1</math> devrait vous rappeler quelque chose : il s'agit d'un nombre dont tous les bits sont à 1. En clair, le complément à deux de -1 est un nombre de la forme 1111...111. Et cela vaut quel que soit le nombre de bits n. La représentation de -1 est similaire, peu importe que l'on utilise des nombres de 16 bits, 32 bits, 64 bits, etc.
Voyons maintenant le cas des puissance de deux, par exemple 2, 4, 8, 16, etc. Leur complément à deux vaut :
: <math>K = 2^n - 2^x</math>
Nous avions vu précédemment dans le chapitre que ces nombres sont de la forme 11111...0000. En clair, des nombres dont les x bits de poids faible sont à 0, et tous les autres bits sont à 1. Par exemple, le complément à deux de -2 est un nombre dont les bits sont à 1, sauf le bit de poids faible. De même, le complément à deux de -4 a tous ses bits à 1, sauf les deux bits de poids faible. Et le complément à deux de -8 a tous ses bits à 1, sauf les trois bits de poids faible. Pour résumer, tout nombre de la forme <math>- 2^x</math> a tous ses bits à 1, sauf les x bits de poids faible qui sont à 0.
{|class="wikitable"
|+ Exemple avec des nombres de 8 bits
|-
! -1
| 1111 1111
|-
! -2
| 1111 1110
|-
! -4
| 1111 1100
|-
! -8
| 1111 1000
|-
! -16
| 1111 0000
|-
! -32
| 1110 0000
|-
! -64
| 1100 0000
|-
! -128
| 1000 0000
|-
! 0 / - 256
| 0000 0000
|}
====Le calcul du complément à deux : seconde méthode====
Il existe une seconde méthode pour calculer le complément à deux d'un nombre, la voici. Pour les nombres positifs, encodez-les comme en binaire normale. Pour les nombres négatifs, faites pareil, puis inversez tous les bits, avant d'ajouter un. La procédure est identique à celle du complément à un, sauf que l'on incrémente le résultat final. Et ce n'est pas une coïncidence, comme nous allons le voir immédiatement.
Pour comprendre pourquoi la méthode marche, repartons de la formule précédente <math>K = 2^n - N</math>. Elle peut se reformuler comme suit :
: <math>K = (2^n - 1 + 1) - N</math>
: <math>K = (2^n - 1) - N + 1</math>
La valeur <math>(2^n - 1)</math> est par définition un nombre dont tous les bits sont à 1. À cette valeur, on soustrait un nombre <math>|N|</math> dont certains bits sont à 1 et d'autres à 0. En clair, pour chaque colonne, on a deux possibilités : soit on doit faire la soustraction <math>1-1</math>, soit la soustraction <math>1-0</math>. Or, les règles de l'arithmétique binaire disent que <math>1-1=0</math> et <math>1-0=1</math>. En regardant attentivement, on se rend compte que le bit du résultat est l'inverse du bit de départ. De plus, les deux cas ne donnent pas de retenue : le calcul pour chaque bit n'influence pas les bits voisins.
Le terme <math>(2^n - 1) - N</math> est donc le complément à un du nombre N, un nombre égal à sa représentation en binaire dont tous les bits sont inversés. Notons <math>\overline{|N|}</math> le nombre formé en inversant tous les bits de N. On a alors :
: <math>K = \overline{N} + 1</math>
En clair, le complément à deux s'obtient en prenant le complément à un et en ajoutant 1. Dit autrement, il faut prendre le nombre N, en inverser tous les bits et ajouter 1.
Une autre manière équivalente consiste à faire le calcul suivant :
: <math>K = \overline{N - 1}</math>
On prend le nombre dont on veut le complément, on soustrait 1 et on inverse les bits.
: Notons que tout ce qui a été dit plus haut marche aussi pour le complément à un, avec cependant une petite différence : la valeur haute de débordement n'est pas la même, ce qui change les calculs. Pour des nombres codés sur <math>n</math> bits, la valeur haute de débordement est égale à <math>2^n</math> en complément à deux, alors qu'elle est de <math>2^n - 1</math> en complément à un. De ce fait, la gestion des débordements est plus simple en complément à deux.
====L'extension de signe====
Dans les ordinateurs, tous les nombres sont codés sur un nombre fixé et constant de bits. Ainsi, les circuits d'un ordinateur ne peuvent manipuler que des nombres de 4, 8, 12, 16, 32, 48, 64 bits, suivant la machine. Si l'on veut utiliser un entier codé sur 16 bits et que l'ordinateur ne peut manipuler que des nombres de 32 bits, il faut bien trouver un moyen de convertir le nombre de 16 bits en un nombre de 32 bits, sans changer sa valeur et en conservant son signe. Cette conversion d'un entier en un entier plus grand, qui conserve valeur et signe s'appelle l''''extension de signe'''.
L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
L'explication plus simple tient dans la manière de coder le bit de poids fort. Prenons l'exemple de la conversion d'un entier de 5 bits en un entier de 8bits. Les 4 bits de poids faible ont un poids positif (on les additionne), alors que le bit de poids fort a un poids négatif. Le nombre encodé vaut : <math>\text{Bit de poids fort} \times -16 + \text{Valeur encodée sur les 4 bits de poids faible}</math>. La valeur encodée sur 4 bits reste la même après extension de signe, car les poids des bits de poids faible ne changent pas. Par contre, le bit de poids fort change. Sur 8 bits, la valeur -16 est encodée par un nombre de la forme 1111 0000. En remplaçant le bit de poids fort par sa valeur calculée sur plus de bits, on remarque que les bits de poids fort ont été remplacés par des 1.
===La représentation négabinaire===
Enfin, il existe une dernière méthode, assez simple à comprendre, appelée '''représentation négabinaire'''. Dans cette méthode, les nombres sont codés non en base 2, mais en base -2. Oui, vous avez bien lu : la base est un nombre négatif. Dans les faits, la base -2 est similaire à la base 2 : il y a toujours deux chiffres (0 et 1), et la position dans un chiffre indique toujours par quelle puissance de 2 il faut multiplier, sauf qu'il faudra ajouter un signe moins une fois sur 2. Concrètement, les puissances de -2 sont les suivantes : 1, -2, 4, -8, 16, -32, 64, etc. En effet, un nombre négatif multiplié par un nombre négatif donne un nombre positif, ce qui fait qu'une puissance sur deux est négative, alors que les autres sont positives. Ainsi, on peut représenter des nombres négatifs, mais aussi des nombres positifs dans une puissance négative.
Par exemple, la valeur du nombre noté 11011 en base -2 s'obtient comme suit :
{|class="wikitable"
|-
!…
!-32
!16
!-8
!4
!-2
!1
|-
|…
|1
|1
|1
|0
|1
|1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(−8×1)+(4×0)+(−2×1)+(1×1)=−32+16−8−2+1=−25.
==Les nombres à virgule fixe==
On sait donc comment sont stockés nos nombres entiers dans un ordinateur. Néanmoins, les nombres entiers ne sont pas les seuls nombres que l'on utilise au quotidien : il nous arrive d'en utiliser à virgule. Notre ordinateur n'est pas en reste : il est lui aussi capable de manipuler de tels nombres. Dans les grandes lignes, il peut utiliser deux méthodes pour coder des nombres à virgule en binaire : La virgule fixe et la virgule flottante. Parlons d'abord de la virgule fixe, plus simple à comprendre.
===La virgule fixe : des entiers cachés===
La méthode de la virgule fixe consiste à émuler les nombres à virgule à partir de nombres entiers. Un nombre à virgule fixe est codé par un nombre entier proportionnel au nombre à virgule fixe. Pour obtenir la valeur de notre nombre à virgule fixe, il suffit de diviser l'entier servant à le représenter par le facteur de proportionnalité. Par exemple, pour coder 1,23 en virgule fixe, on peut choisir comme « facteur de conversion » 1000, ce qui donne l'entier 1230.
Généralement, les informaticiens utilisent une puissance de deux comme facteur de conversion, pour simplifier les calculs. En faisant cela, on peut écrire les nombres en binaire et les traduire en décimal facilement. Pour l'exemple, cela permet d'écrire des nombres à virgule en binaire comme ceci : 1011101,1011001. Et ces nombres peuvent se traduire en décimal avec la même méthode que des nombres entier, modulo une petite différence. Comme pour les chiffres situés à gauche de la virgule, chaque bit situé à droite de la virgule doit être multiplié par la puissance de deux adéquate. La différence, c'est que les chiffres situés à droite de la virgule sont multipliés par une puissance négative de deux, c'est à dire par <math>1 \over 2</math>, <math>1 \over 4</math>, <math>1 \over 8</math>, <math>1 \over 16</math>, <math>1 \over 32</math>, ...
[[File:Fixed-point binary representation.svg|centre|vignette|upright=2.5|Nombres à virgule fixe.]]
Cette méthode est assez peu utilisée de nos jours, quoiqu'elle puisse avoir quelques rares applications relativement connue. Un bon exemple est celui des banques : les sommes d'argent déposées sur les comptes ou transférées sont codés en virgule fixe. Les sommes manipulées par les ordinateurs ne sont pas exprimées en euros, mais en centimes d'euros. Et c'est une forme de codage en virgule fixe dont le facteur de conversion est égal à 100. La raison de ce choix est que les autres méthodes de codage des nombres à virgule peuvent donner des résultats imprécis : il se peut que les résultats doivent être tronqués ou arrondis, suivant les opérandes. Cela n'arrive jamais en virgule fixe, du moins quand on se limite aux additions et soustractions.
===Les nombres à virgule non-représentables en binaire===
Quel que soit la façon de représenter les nombres à virgules, il existe des nombres qui ne peuvent être représentés de manière exacte, à savoir avec un nombre fini de décimales après la virgule. En soi, ce n'est pas spécifique au binaire, on a la même chose en décimal. Par exemple, la fraction 1/3 en décimal s'écrit 0.3333333..., avec une infinité de 3. La même chose existe en binaire, mais pour des nombres différents.
Déjà, évacuons le cas des nombres irrationnels, à savoir les nombres qui ne peuvent pas s'écrire sous la forme d'une fraction, comme <math>pi</math> ou <math>e</math>. Ils ont une infinité de décimales que ce soit en binaire, en décimal, en hexadécimal, ou autre. Ils ne sont pas représentables avec un nombre fini de décimales quelle que soit la base utilisée. Concentrons-nous sur des nombres qui ne sont pas dans ce cas, et qui ont un nombre fini ou infini de décimales.
Le passage de la base 10 à la base 2 change le nombre de décimales, et peut faire passer d'un nombre fini de décimales à un nombre infini. Par exemple, <math>\frac{1}{5} = 0.2</math> est représenté en binaire avec une séquence infinie de bits : <math>0.001100110011...</math>. Les nombres étant en base binaire et représenté avec un nombre limité de bits, il existe certains nombres décimaux triviaux qui ne sont pas représentables avec un nombre fini de décimales.
Et la réciproque n'est pas vraie : tout nombre binaire avec un nombre fini de décimales en binaire est représentable avec un nombre fini de décimales en base 10. Ceci est lié à la décomposition en facteur premier des bases utilisées :
* <math>2 = 2^1</math>
* <math>10 = 2^1 \times 5^1</math>
Le nombre 10 possède tous les facteurs premiers de 2, mais 2 n'a pas de 5 dans sa décomposition.
==Les nombres flottants==
Les nombres à virgule fixe ont aujourd'hui été remplacés par les '''nombres à virgule flottante''', où le nombre de chiffres après la virgule est variable. Le codage d'un nombre flottant est basée sur son ''écriture scientifique''. Pour rappel, en décimal, l’écriture scientifique d'un nombre consiste à écrire celui-ci comme un produit entre un nombre <math>a</math> et une puissance de 10. Ce qui donne :
: <math>a \times 10^e</math>, avec <math>a \in [1, 10 [</math>
Le nombre <math>a</math> est appelé le ''significande'' et il est compris entre 1 (inclus) et 10 (exclu). Cette contrainte garantit que l'écriture scientifique d'un nombre est unique, qu'il n'y a qu'une seule façon d'écrire un nombre en notation scientifique. Pour cela, on impose le nombre de chiffre à gauche de la virgule et le plus simple est que celui-ci soit égal à 1. Mais il faut aussi que celui-ci ne soit pas nul. En effet, si on autorise de mettre un 0 à gauche de la virgule, il y a plusieurs manières équivalentes d'écrire un nombre. Ces deux contraintes font que le significande doit être égal ou plus grand que 1, mais strictement inférieur à 10. Par contre, on peut mettre autant de décimales que l'on veut.
En binaire, c'est la même chose, mais avec une puissance de deux. Cela implique de modifier la puissance utilisée : au lieu d'utiliser une puissance de 10, on utilise une puissance de 2.
: <math>a \times 2^e</math>, avec <math>a \in [1, 2 [</math>
Le significande est aussi altéré, au même titre que la puissance, même si les contraintes sont similaires à celles en base 10. En effet, le nombre <math>a</math> ne possède toujours qu'un seul chiffre à gauche de la virgule, comme en base 10. Vu que seuls deux chiffres sont possibles (0 et 1) en binaire, on s'attend à ce que le chiffre situé à gauche de la virgule soit un zéro ou un 1. Mais rappelons que le chiffre à gauche doit être non-nul, pour les mêmes raisons qu'en décimal. En clair, le significande a forcément un bit à 1 à gauche de la virgule. Pour récapituler, l'écriture scientifique binaire d'un nombre consiste à écrire celui-ci sous la forme :
: <math>1,m \times 2^e</math>, avec <math>m \in [0, 1 [</math>
La partie fractionnaire du nombre <math>a</math>, qu'on appelle la '''mantisse'''.
[[File:Mantissa.svg|centre|vignette|Écriture scientifique (anglais).]]
===Traduire un nombre en écriture scientifique binaire===
Pour déterminer l'écriture scientifique en binaire d'un nombre quelconque, la procédure dépend de la valeur du nombre en question. Tout dépend s'il est dans l'intervalle <math>[1, 2 [</math>, au-delà de 2 ou en-dessous de 1.
* Pour un nombre entre 1 (inclus) et 2 (exclu), il suffit de le traduire en binaire. Son exposant est 0.
* Pour un nombre au-delà de 2, il faut le diviser par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant est alors le nombre de fois qu'il a fallu diviser par 2.
* Pour un nombre plus petit que 1, il faut le multiplier par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant se calcule en prenant le nombre de fois qu'il a fallu multiplier par 2, et en prenant l'opposé (en mettant un signe - devant le résultat).
===Le codage des nombres flottants et la norme IEEE 754===
Pour coder cette écriture scientifique avec des nombres, l'idée la plus simple est d'utiliser trois nombres, pour coder respectivement la mantisse, l'exposant et un bit de signe. Coder la mantisse implique que le bit à gauche de la virgule vaut toujours 1, mais nous verrons qu'il y a quelques rares exceptions à cette règle.
Quelques nombres flottants spécialisés, les dénormaux, ne sont pas codés en respectant les règles pour le significande et ont un 0 à gauche de la virgule. Un bon exemple est tout simplement la valeur zéro, que l'on peut coder en virgule flottante, mais seulement en passant outre les règles sur le significande.
Toujours est-il que le bit à gauche de la virgule n'est pas codé, que ce soit pour les flottants normaux ou les fameux dénormaux qui font exception. On verra que ce bit peut se déduire en fonction de l'exposant utilisé pour encoder le nombre à virgule, ce qui lui vaut le nom de '''bit implicite'''. L'exposant peut être aussi bien positif que négatif (pour permettre de coder des nombres très petits), et est encodé en représentation par excès sur n bits avec un biais égal à <math>2^{n-1} - 1</math>.
[[File:IEEE754 Format General.png|centre|vignette|upright=2|IEEE754 Format Général]]
Le standard pour le codage des nombres à virgule flottante est la '''norme IEEE 754'''. Cette norme va (entre autres) définir quatre types de flottants différents, qui pourront stocker plus ou moins de valeurs différentes.
{|class="wikitable"
|-
!Classe de nombre flottant
!Nombre de bits utilisés pour coder un flottant
!Nombre de bits de l'exposant
!Nombre de bits pour la mantisse
!Décalage
|-
!Simple précision
| 32
| 8
| 23
| 127
|-
!Double précision
| 64
| 11
| 52
| 1023
|-
!Double précision étendue
| 80 ou plus
| 15 ou plus
| 64 ou plus
| 16383 ou plus
|}
Voici à peu-près comment les nombres flottants sont répartis sur une ligne numérique. Vous remarquerez que les flottants sont de plus en plus éloignés au fur et à mesure qu'on s'éloigne de zéro. La raison est l'encodage avec un exposant et une mantisse. L'écart entre deux flottants reste stable pour un même exposant, mais il y a une cassure quand on passe d'un exposant au suivant.
[[File:FloatingPointPrecisionAugmented.png|centre|vignette|upright=3|Floating Point Precision.]]
IEEE754 impose aussi le support de certains nombres flottants spéciaux qui servent notamment à stocker des valeurs comme l'infini. Commençons notre revue des flottants spéciaux par les '''dénormaux''', aussi appelés flottants dénormalisés. Ces flottants ont une particularité : leur bit implicite vaut 0. Ces dénormaux sont des nombres flottants où l'exposant est le plus petit possible. Le '''zéro''' est un dénormal particulier dont la mantisse est nulle. Au fait, remarquez que le zéro est codé deux fois à cause du bit de signe : on se retrouve avec un -0 et un +0.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur minimale (0 en binaire) || Mantisse différente de zéro (dénormal strict) ou égale à zéro (zéro)
|}
Fait étrange, la norme IEEE754 permet de représenter '''l'infini''', aussi bien en positif qu'en négatif. Celui-ci est codé en mettant l'exposant à sa valeur maximale et la mantisse à zéro. Et le pire, c'est qu'on peut effectuer des calculs sur ces flottants infinis. Mais cela a peu d'utilité.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse égale à zéro
|}
Mais malheureusement, l'invention des flottants infinis n'a pas réglé tous les problèmes. Par exemple, quel est le résultat de <math>\infty - \infty</math> ? Ou encore <math>0 - \infty</math> ? Autant prévenir tout de suite : mathématiquement, on ne peut pas savoir quel est le résultat de ces opérations. Pour pouvoir résoudre ces calculs, il a fallu inventer un nombre flottant qui signifie « je ne sais pas quel est le résultat de ton calcul pourri ». Ce nombre, c'est '''NaN'''. NaN est l'abréviation de ''Not A Number'', ce qui signifie : n'est pas un nombre. Ce NaN a un exposant dont la valeur est maximale, mais une mantisse différente de zéro. Pour être plus précis, il existe différents types de NaN, qui diffèrent par la valeur de leur mantisse, ainsi que par les effets qu'ils peuvent avoir. Malgré son nom explicite, on peut faire des opérations avec NaN, mais cela ne sert pas vraiment à grand chose : une opération arithmétique appliquée avec un NaN aura un résultat toujours égal à NaN.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse différente de zéro
|}
===Les arrondis et exceptions===
La norme impose aussi une gestion des arrondis ou erreurs, qui arrivent lors de calculs particuliers. En voici la liste :
{|class="wikitable"
|-
!Nom de l’exception
!Description
|-
!Invalid operation
|Opération qui produit un NAN. Elle est levée dans le cas de calculs ayant un résultat qui est un nombre complexe, ou quand le calcul est une forme indéterminée. Pour ceux qui ne savent pas ce que sont les formes indéterminées, voici en exclusivité la liste des calculs qui retournent NaN : <math>\infty - \infty</math>, <math>0 \times \infty</math>, <math>0^0</math>, <math>1^{\infty}</math>, <math>\infty^0</math>.
|-
!Overflow
|Résultat trop grand pour être stocké dans un flottant. Le plus souvent, on traite l'erreur en arrondissant le résultat en vue de la taille de la mantisse;
|-
!Underflow
|Pareil que le précédent, mais avec un résultat trop petit. Le plus souvent, on traite l'erreur en arrondissant le résultat vers 0.
|-
!Division par zéro
|Le nom parle de lui-même. La réponse la plus courante est de répondre + ou - l'infini.
|-
!'''Inexact'''
|Le résultat ne peut être représenté par un flottant et on doit l'arrondir.
|}
La gestion des arrondis pose souvent problème. Pour donner un exemple, on va prendre le nombre 0,1. En binaire, ce nombre s'écrit comme ceci : 0,1100110011001100... et ainsi de suite jusqu'à l'infini. Notre nombre utilise une infinité de décimales. Bien évidemment, on ne peut pas utiliser une infinité de bits pour stocker notre nombre et on doit impérativement l'arrondir. Comme vous le voyez avec la dernière exception, le codage des nombres flottants peut parfois poser problème : dans un ordinateur, il se peut qu'une opération sur deux nombres flottants donne un résultat qui ne peut être codé par un flottant. On est alors obligé d'arrondir ou de tronquer le résultat de façon à le faire rentrer dans un flottant. Pour éviter que des ordinateurs différents utilisent des méthodes d'arrondis différentes, on a décidé de normaliser les calculs sur les nombres flottants et les méthodes d'arrondis. Pour cela, la norme impose le support de quatre modes d'arrondis :
* Arrondir vers + l'infini ;
* vers - l'infini ;
* vers zéro ;
* vers le nombre flottant le plus proche.
===Les nombres flottants logarithmiques===
Les '''nombres flottants logarithmiques''' sont une spécialisation des nombres flottants IEEE754, ou tout du moins une spécialisation des flottants écrits en écriture scientifique. Un nombre logarithmique est donc composé d'un bit de signe et d'un exposant, sans mantisse. La mantisse est totalement implicite : tous les flottants logarithmiques ont la même mantisse, qui vaut 1.
Pour résumer, il ne reste que l'exposant, qui est tout simplement le logarithme en base 2 du nombre encodé, d'où le nom de codage flottant logarithmique donné à cette méthode. Attention toutefois : l'exposant est ici un nombre fractionnaire, codé en virgule fixe. Le choix d'un exposant fractionnaire permet de représenter pas mal de nombres de taille diverses.
{|class="wikitable"
|-
!
! Bit de signe
! Exposant
|-
! Représentation binaire
| 0
| 01110010101111
|-
! Représentation décimale
| +
| 1040,13245464
|}
L'utilité de cette représentation est de simplifier certains calculs, comme les multiplications, divisions, puissances, etc. En effet, les mathématiques nous disent que le logarithme d'un produit est égal à la somme des logarithmes : <math>\ln{(a \times b)} = \ln{a} + \ln{b}</math>. Or, il se trouve que les ordinateurs sont plus rapides pour faire des additions/soustractions que pour faire des multiplications/divisions. Donc, la représentation logarithmique permet de remplacer les multiplications/divisions par des additions/soustractions, plus simples et plus rapides pour l'ordinateur.
Évidemment, les applications des flottants logarithmiques sont rares, limitées à quelques situations bien précises (traitement d'image, calcul scientifique spécifiques).
==Les encodages binaires alternatifs==
Outre le binaire que nous venons de voir, il existe d'autres manières de coder des nombres avec des bits. Et nous allons les aborder dans cette section. Parmi celle-ci, nous parlerons du code Gray, de la représentation ''one hot'', du unaire et du ternaire. Nous en parlons car elles seront utiles dans la suite du cours, bien que de manière assez limitée. Autant nous passerons notre temps à parler du binaire normal, autant les représentations que nous allons voir sont aujourd'hui utilisées dans des cas assez spécifiques. Et elles sont plus courantes que vous ne le pensez.
===Le code Gray===
Le '''code Gray''' est un encodage binaire qui a une particularité très intéressante : deux nombres consécutifs n'ont qu'un seul bit de différence. Pour exemple, voici ce que donne le codage des 8 premiers entiers sur 3 bits :
{|class="wikitable"
! Décimal
! Binaire naturel
! Codage Gray
|-
|0||000||000
|-
|1||001||001
|-
|2||010||011
|-
|3||011||010
|-
|4||100||110
|-
|5||101||111
|-
|6||110||101
|-
|7||111||100
|}
Les utilisations du code Gray sont assez nombreuses, bien qu'on n'en croise pas tous les jours. Un exemple : le code Gray est très utile dans certains circuits appelés les compteurs, qui mémorisent un nombre et l'incrémentent (+1) ou le décrémentent (-1) suivant les besoins de l'utilisateur. Il est aussi utilisé dans des scénarios difficiles à expliquer ici (des codes correcteurs d'erreur ou des histoires de passages de domaines d'horloge). Mais le point important est que ce code sera absolument nécessaire dans quelques chapitres, quand nous parlerons des tables de Karnaugh, un concept important pour la conception de circuits électroniques. Ne passez pas à côté de cette section.
Pour construire ce code Gray, on peut procéder en suivant plusieurs méthodes, les deux plus connues étant la méthode du miroir et la méthode de l'inversion.
[[File:Binary-reflected Gray code construction.svg|vignette|Construction d'un code gray par la méthode du miroir.]]
La méthode du miroir est relativement simple. Pour connaître le code Gray des nombres codés sur n bits, il faut :
* partir du code Gray sur n-1 bits ;
* symétriser verticalement les nombres déjà obtenus (comme une réflexion dans un miroir) ;
* rajouter un 0 au début des anciens nombres, et un 1 au début des nouveaux nombres.
Il suffit de connaître le code Gray sur 1 bit pour appliquer la méthode : 0 est codé par le bit 0 et 1 par le bit 1.
Une autre méthode pour construire la suite des nombres en code Gray sur n bits est la méthode de l'inversion. Celle-ci permet de connaître le codage du nombre n à partir du codage du nombre n-1, comme la méthode du dessus. On part du nombre 0, systématiquement codé avec uniquement des zéros. Par la suite, on décide quel est le bit à inverser pour obtenir le nombre suivant, avec la règle suivante :
* si le nombre de 1 est pair, il faut inverser le dernier chiffre.
* si le nombre de 1 est impair, il faut localiser le 1 le plus à droite et inverser le chiffre situé à sa gauche.
Pour vous entraîner, essayez par vous-même avec 2, 3, voire 5.
===Les représentations ''one-hot'' et unaire===
La représentation ''one-hot'' et la représentation unaire sont deux représentations assez liées, mais légèrement différentes.
La '''représentation unaire''' est la représentation en base 1. Avec elle, le zéro est codé en mettant tous les bits du nombre à zéro, la valeur 1 est encodée avec la valeur 000...0001, le deux avec 000...0011, le trois avec 000...0111, etc. Pour résumer, le nombre N est encodé en mettant les N bits de poids faible à 1, à l'exception du zéro qui est encodé...par un zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! Unaire
|-
| 0 || 000 || 0000 0000
|-
| 1 || 001 || 0000 0001
|-
| 2 || 010 || 0000 0011
|-
| 3 || 011 || 0000 0111
|-
| 4 || 100 || 0000 1111
|-
| 5 || 101 || 0001 1111
|-
| 6 || 110 || 0011 1111
|-
| 7 || 111 || 0111 1111
|-
| 8 || 1000 || 1111 1111
|}
Pour convertir un nombre codé en unaire vers le binaire, il suffit de compter le nombre de 1. C'est ni plus ni moins ce que fait l'opération dite de ''population count'' dont nous avons parlé plus haut. La représentation unaire permet d'encoder N+1 valeurs sur N bits, car le zéro est encodé à part. Une valeur pour le zéro, plus une par bit.
Avec la '''représentation ''one-hot''''', un nombre est codé sur N bits, dont un seul bit est à 1. La position du 1 dans le nombre indique sa valeur. Précisément, la valeur encodée est égale au poids du bit à 1. En faisant cela, le zéro est codé en mettant le bit de poids faible à 1, la valeur 1 est codée avec les bits 00...00010, la valeur 2 est codée par les bits 00...000100, etc.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Elle permet d'encoder N valeurs différentes avec N bits, zéro inclus. On perd une valeur par rapport à l'unaire, car le zéro n'et pas encodé à part du reste. La traduction du binaire vers le ''one-hot'' est réalisé par un circuit nommé décodeur, que nous verrons d'ici quelques chapitres. De même, la traduction d'un nombre encodé en ''one-hot'' vers le binaire est réalisée par un circuit électronique appelé l'encodeur, qui est en quelque sorte l'inverse de l'encodeur. Les deux circuits sont très utilisés en électronique, ce sont des circuits de base, que nous n'aurons de cesse d'utiliser pour fabriquer d'autres circuits.
L'utilité de ces deux représentations n'est pas évidente. Mais sachez qu'elle le deviendra quand nous parlerons des circuits appelés les "compteurs", tout comme ce sera le cas pour le code Gray. Elles sont très utilisées dans des circuits appelés des machines à état, qui doivent incorporer des circuits compteurs efficients. Et ces représentations permettent d'avoir des circuits pour compter qui sont très simples, efficaces, rapides et économes en circuits électroniques. La représentation unaire sera aussi utile à la toute fin du cours, dans les chapitres liés à l'exécution dans le désordre. Il en sera fait référence quand nous parlerons de fenêtres d'instruction, d'émission dans l'ordre, de ''scoreboarding'', etc.
Il faut aussi noter qu'il a existé des ordinateur qui manipulaient des nombres codés en unaire. Enfin presque, la représentation utilisée était proche de l'unaire, mais ressemblait fortement. De tels ordinateurs étaient appelés des ''ordinateurs stochastiques'' et nous les aborderons à la toute fin de ce wikilivre, dans un chapitre annexe portant sur les ordinateurs ternaires (qui comptent en base 3) et unaires.
==Les encodages hybrides entre décimal et binaire==
Pour terminer, nous allons voir les représentations qui mixent binaire et décimal. Il s'agit en réalité d'encodage qui permettent de manipuler des nombres codés en décimal, mais les chiffres sont codés en utilisant des bits. L'encodage ''Binary Coded Decimal'' est le plus connu de cette catégorie, mais il y en a quelques autres, qui sont moins connus. De telles représentations étaient très utilisées au début de l'informatique, mais sont aujourd'hui tombées en désuétude. Pour désigner ces encodages, nous parlerons d''''encodages bit-décimaux''' : décimaux pour préciser qu'ils encodent des nombres codés en décimal, bit pour préciser que les chiffres sont codés avec des bits.
De tels encodages étaient utilisés sur les tous premiers ordinateurs pour faciliter le travail des programmeurs, mais aussi sur les premières calculettes. Ils sont utiles dans les applications où on doit manipuler chaque chiffre décimal séparément des autres. Un exemple classique est celui d'une horloge digitale. Ils ont aussi l'avantage de bien se marier avec les nombres à virgule. Les représentations des nombres à virgule fixe ou flottante ont le défaut que des arrondis peuvent survenir. Par exemple, la valeur 0,2 est codée comme suit en binaire normal : 0.00110011001100... et ainsi de suite jusqu’à l'infini. Avec les encodages bit-décimaux, on n'a pas ce problème, 0,2 étant codé 0000 , 0010.
Il a existé des ordinateurs qui travaillaient uniquement avec de tels encodages, appelés des '''ordinateurs décimaux'''. Ils étaient assez courants entre les années 60 et 70, même s'ils ne représentent pas la majorité des architectures de l'époque. Avec eux, la mémoire n'était pas organisée en octets, mais elle stockait des chiffres décimaux codés sur 5-6 bits. Le processeur faisait des calculs sur des chiffres bit-décimaux directement.
Leur grand avantage est leur très bonne performance dans les taches de bureautique, de comptabilité, et autres. Les processeurs de l'époque recevaient des entiers codés en bit-décimal de la part des entrées-sorties, et devaient les traiter. Les processeurs binaires devaient faire des conversions décimal-binaire pour communiquer avec les entrées-sorties, mais pas les processeurs décimaux. Le gain en performance pouvait être substantiel dans certaines applications.
Les ordinateurs décimaux se classent en deux sous-types bien précis. Les premiers gèrent des entiers qui ont un nombre de chiffres fixes. Par exemple, l'IBM 7070 gérait des entiers de 10 chiffres, plus un signe +/- pour les entiers signés. Le processeur faisait les calculs directement en bit-décimal, il gérait des entiers faisant environ 10-15 chiffres décimaux et savait faire des calculs avec de tels nombres. Le second sous-type effectue les calculs chiffre par chiffre et géraient des nombres de taille variable, sans limite de chiffres ! Nous reparlerons de ces derniers dans un chapitre ultérieur, quand nous parlerons de la différence entre byte et mot. Pour le moment, ne gardez à l'esprit que les processeurs gérant un nombre fixe de chiffres décimaux, plus simples à comprendre.
===Le ''Binary Coded Decimal''===
Le '''''Binary Coded Decimal''''', abrévié BCD, est une représentation qui mixe binaire et décimal. Avec cette représentation, les nombres sont écrits en décimal, comme nous en avons l'habitude dans la vie courante, sauf que chaque chiffre décimal est directement traduit en binaire sur 4 bits. Prenons l'exemple du nombre 624 : le 6, le 2 et le 4 sont codés en binaire séparément, ce qui donne 0110 0010 0100.
{| class="wikitable"
|+ Codage BCD
|-
! Nombre encodé (décimal)
! scope="col" colspan="4" | BCD
|-
| 0 || 0 || 0 || 0 || 0
|-
| 1 || 0 || 0 || 0 || 1
|-
| 2 || 0 || 0 || 1 || 0
|-
| 3 || 0 || 0 || 1 || 1
|-
| 4 || 0 || 1 || 0 || 0
|-
| 5 || 0 || 1 || 0 || 1
|-
| 6 || 0 || 1 || 1 || 0
|-
| 7 || 0 || 1 || 1 || 1
|-
| 8 || 1 || 0 || 0 || 0
|-
| 9 || 1 || 0 || 0 || 1
|}
On peut remarquer que 4 bits permettent de coder 16 valeurs, là où il n'y a que 10 chiffres. Dans le BCD proprement dit, les combinaisons de bits qui correspondent à 10, 11, 12, 13, 14 ou 15 ne sont tout simplement pas prises en compte. Sur quelques ordinateurs, ces combinaisons codent des chiffres décimaux en double : certains chiffres pouvaient être codés de deux manières différentes. Il est aussi possible d'utiliser ces valeurs pour coder quelque chose d'autre que des chiffres. Par exemple, il est possible de les utiliser pour coder un signe + ou -, afin de gérer les entiers relatifs. Une autre possibilité, complémentaire de la précédente, utilise ces valeurs en trop pour coder une virgule, afin de gérer les nombres non-entiers. Les possibilités sont nombreuses.
Le support du BCD implique souvent que le processeur supporte des '''opérations BCD''', à savoir des opérations capables de travailler sur des opérandes en BCD et de donner un résultat en BCD. Il faut bien faire la différence entre les opérations en binaire et les opérations en BCD. Par exemple, on n'effectue pas une addition de la même manière en binaire et en décimal/BCD, même si les grandes lignes sont presque identiques. Les différences font que les processeurs doivent avoir des opérations différentes pour les deux encodages, de la même manière que les processeurs gèrent les opérations sur les flottants et les entiers séparément.
Le support du codage BCD est abandonné de nos jours, c'est surtout quelque chose qu'on trouve sur les anciens processeurs. Les architectures 8 et 16 bits supportaient à la fois des opérations binaires et des opérations BCD. Mais il a aussi existé une méthode intermédiaire, qui utilisait des additions binaires normales sur des opérandes BCD. L'idée est que le résultat de l'addition est incorrect, car les valeurs 10 à 15 peuvent apparaître comme chiffre dans le résultat. Mais on peut le corriger pour obtenir le résultat exact en BCD. Pour résumé, les processeurs faisaient des additions en binaire, et corrigeaient le résultat avec une opération spécifique pour obtenir un résultat en BCD. Nous en reparlerons dans le chapitre sur le langage machine et l'assembleur, dans lequel nous étudierons les différentes opérations que supporte un processeur.
===Les encodages compacts du BCD===
Des variantes du BCD visent à réduire le nombre de bits utilisés pour encoder un nombre décimal. Nous allons les appeler les '''encodages BCD compacts'''. Avec certaines variantes, on peut utiliser seulement 7 bits pour coder deux chiffres décimaux, au lieu de 8 bits en BCD normal. Idem pour les nombres à 3 chiffres décimaux, qui prennent 10 bits au lieu de 12. Cette économie est réalisée par une variante du BCD assez compliquée, appelée l''''encodage Chen-Ho'''. Une alternative, appelée le ''Densely Packed Decimal'', arrive à une compression identique, mais avec quelques avantages au niveau de l'encodage. Ces encodages sont cependant assez compliqués à expliquer, surtout à ce niveau du cours, aussi je me contente de simplement mentionner leur existence.
Les encodages BCD compacts sont utilisés par les programmeurs pour stocker des données dans un fichier, en mémoire, mais guère plus. Ils ne sont pas gérés par le processeur directement, on ne peut pas faire de calculs avec, le processeur ne gére pas d'opération BCD supportant de tels encodages. C'est théoriquement possible, mais ça n'a jamais été implémenté dans un processeur, le cout en circuit n'en valait pas la chandelle. Pour faire des calculs sur des nombres en BCD compact, on doit les décompresser et les convertir en BCD normal ou en binaire, puis faire les calculs avec des opérations BCD/binaire usuelles.
===L'encodage ''Excess-3''===
La représentation '''''Excess-3''''' (XS-3) est une variante du BCD, qui a autrefois était utilisée sur d'anciens ordinateurs décimaux. Il s'agit d'une sorte d'hybride entre une représentation par excès et BCD. Chaque chiffre décimal est codé sur plusieurs bits en utilisant une représentation binaire par excès. Le biais est de 3 pour la représentation XS-3, il en existe des variantes avec un excès de 4, 5, mais elles sont moins utilisées. Avec elle, la conversion d'un chiffre décimal se fait comme suit : on prend le chiffre décimal, on ajoute 3, puis on traduit en binaire.
{| class="wikitable"
|+ Codage en ''Excess-3''
|-
! Décimal
! scope="col" colspan="4" | Binaire
|-
| -3 || 0 || 0 || 0 || 0
|-
| -2 || 0 || 0 || 0 || 1
|-
| -1 || 0 || 0 || 1 || 0
|-
| 0 || 0 || 0 || 1 || 1
|-
| 1 || 0 || 1 || 0 || 0
|-
| 2 || 0 || 1 || 0 || 1
|-
| 3 || 0 || 1 || 1 || 0
|-
| 4 || 0 || 1 || 1 || 1
|-
| 5 || 1 || 0 || 0 || 0
|-
| 6 || 1 || 0 || 0 || 1
|-
| 7 || 1 || 0 || 1 || 0
|-
| 8 || 1 || 0 || 1 || 1
|-
| 9 || 1 || 1 || 0 || 0
|-
| 10 || 1 || 1 || 0 || 1
|-
| 11 || 1 || 1 || 1 || 0
|-
| 12 || 1 || 1 || 1 || 1
|}
L'avantage de cette représentation est que l'on peut facilement calculer les soustractions en utilisant une méthode bien précise (celle du complément à 10, je ne détaille pas plus). Le défaut est que le calcul des additions est légèrement plus complexe.
L'XS-3 a été utilisé sur quelques ordinateurs décimaux assez anciens, notamment sur l'UNIVAC I et II.
===Les codes bi-quinaires===
Les '''codes bi-quinaires''' utilisent un mélange entre encodage binaire, encodage ''one-hot'' et encodage décimal. L'idée de base derrière ces codes est d'utiliser la représentation ''one-hot'' pour coder un chiffre BCD. Le problème est que cela demande d'utiliser 10 bits pour encoder un chiffre BCD, ce qui est beaucoup. L'encodage bi-quinaire optimise le tout de manière à diviser par deux le nombre de bits requis pour l'encodage.
Pour simplifier, un chiffre encodé en bi-quinaire est composé de deux parties : une partie binaire de un bit, suivie par d'une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits pour encoder les valeurs 0, 1, 2, 3 et 4. La partie binaire indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9 : 0 à 4 si la partie binaire vaut 0, 5 à 9 si elle vaut 1. La valeur du chiffre se calcule comme suit :
* si la partie binaire vaut 0, elle est égale à la partie quinaire ;
* si la partie binaire vaut 1, on ajoute 5 à la partie quinaire.
L'originalité de ce codage est qu'il permet de facilement compter sur ses doigts : on a deux mains, cinq doigts. De nombreuses langues utilisent ce système pour coder des nombres, comme le Wolof. Et ce système est utilisé dans certains pays pour compter sur ses doigts, voire dans la vie de tous les jours.
[[File:Code Biquinaer.svg|vignette|Code bi-quinaire où partie binaire et quinaire sont encodées en représentation ''one-hot''.]]
L'encodage abordé au-dessus est une version simplifiée de l'encodage bi-quinaire, il en existe d'autres variantes. Le bi-quinaire vu juste au-dessus encode la partie binaire en binaire, et la partie quinaire en ''one-hot''. Mais il est aussi possible d'inverser l'encodage, à savoir encoder la partie binaire avec une représentation ''one-hot'', alors que la partie bi-quinaire est encodée en binaire normal.
Cependant, la version la plus intéressante utilise l'encodage ''one-hot'' pour les deux ! Elle est illustrée ci-contre. Avec cette dernière, un chiffre BCD est encodé sur 7 bits, dont 2 sont obligatoirement à 1 et les 5 restants sont à 0. Cette propriété permet de détecter qu'un bit a été inversé suite à une erreur de transmission ou un problème de stockage. Si un bit est inversé, le résultat aura 1 bit à 1 et 6 à 0, ou alors 3 bits à 1 et 4 à 0, ce qui ne colle pas. Le résultat après inversion ne donnera pas un chiffre bi-quinaire valide.
Une autre variante du bi-quinaire a été utilisée sur l'IBM 1401. Avec elle, la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre encodé par la partie quinaire. L'avantage de ce système est que la gestion des retenues lors des additions était grandement simplifiée.
Le bi-quinaire et ses variantes ont été utilisés sur d'anciens ordinateurs, dont l'IBM 650, l'UNIVAC Solid State et le UNIVAC LARC. La plupart utilisaient des variantes de l'encodage bi-quinaire proprement dit. Pour rentrer vraiment dans le détail, voici les encodages utilisés sur ces machines. La lecture est facultative, il est possible de passer directement à la section suivante :
{| class="wikitable"
|-
! !! IBM 650 !! Remington Rand 409 !! UNIVAC Solid State !! UNIVAC LARC
|-
| Chiffre || 1357-9 bits || 05-01234 || p-5-421 bits || p-5-qqq bits
|-
| 0 || 10-10000 || 0000-0 || 1-0-000 || 1-0-000
|-
| 1 || 10-01000 || 1000-0 || 0-0-001 || 0-0-001
|-
| 2 || 10-00100 || 1000-1 || 0-0-010 || 1-0-011
|-
| 3 || 10-00010 || 0100-0 || 1-0-011 || 0-0-111
|-
| 4 || 10-00001 || 0100-1 || 0-0-100 || 1-0-110
|-
| 5 || 01-10000 || 0010-0 || 0-1-000 || 0-1-000
|-
| 6 || 01-01000 || 0010-1 || 1-1-001 || 1-1-001
|-
| 7 || 01-00100 || 0001-0 || 1-1-010|| 0-1-011
|-
| 8 || 01-00010 || 0001-1 || 0-1-011 || 1-1-111
|-
| 9 || 01-00001 || 0000-1 || 1-1-100|| 0-1-110
|}
==Annexe : pourquoi l'octet existe==
Nous avons vu plus haut que les nombres encodés en binaire sont découpés en octets, des paquets de 8 bits. Et les octets sont présents partout dans la conception d'un ordinateur. Par exemple, la mémoire RAM d'un ordinateur moderne est organisée en octet. Elle contient des cases mémoire, chacune contenant un octet. Les octets, les cases mémoire, sont numérotées, ce qui permet de sélectionner un octet parmi tous les autres. La capacité de la mémoire est le nombre d'octets qu'elle contient, elle s'exprime en kilo-octets (1024 octets), mébi-octets (1024 kilo-octets), gibi-octets (1024 mébi-octets). Les octets sont donc présents partout dans l'informatique moderne.
{|class="wikitable"
|+ Contenu de la RAM, simplifié
|-
! Numéro
! Octet
|-
! 0
| 0000 1111
|-
! 1
| 0101 0101
|-
! 2
| 0111 0110
|-
! 3
| 0011 1010
|-
! 4
| 1001 1001
|-
! ...
| ...
|-
! 1023
| 0110 1010
|-
! 1024
| 0001 1110
|}
Mais pourquoi est-ce que les nombres encodés en binaire sont souvent découpés en octets ? Pourquoi ne pas utiliser des groupes de 3, 5, 6, 7, 9, 10 bits ? La question est d'autant plus légitime que d'anciens ordinateurs faisaient ainsi. Leurs mémoires étaient découpé en cases mémoire, mais chacune faisait 7 ou 9 bits, rien d'impossible à cela. À vrai dire, l'usage des octets au tout début de l'informatique était assez rare. Il y a eu un basculement progressif vers l'octet pendant les années 80-90. Reste à expliquer pourquoi. Si on devait résumer, les deux raisons principales sont le support du BCD et le support des caractères pour le texte. Voyons d'abord l'encodage des caractères en binaire.
===L'encodage du texte : ASCII et autres encodages===
Pour les caractères, les encodages se sont concentrés sur l'encodage des alphabets occidentaux. Ils demandaient d'encoder au minimum toutes les lettres, en majuscules et minuscules, plus les chiffres, une dizaine de caractères pour la ponctuation. À cela, il faut ajouter des symboles utiles comme %, µ, $, £, #, {} et bien d'autres. Au minimum, cela fait au minimum 52 lettres (26 lettres doublées pour les majuscules) + 10 chiffres + une dizaine de caractères pour la ponctuation : cela fait plus de 72 valeurs à encoder. Pour encoder cela en binaire, le minimum est de 7 bits, ce qui permet d'encoder 128 caractères : les 72 caractères minimum, plus quelques autres. L'encodage ASCII utilisait d'ailleurs 7 bits pour encoder des nombres.
On pourrait croire que c'était une raison suffisante pour utiliser des groupes de 7 bits. Mais il y a quelques problèmes avec cela. Premièrement, les encodages 7 bits pour le texte marchent bien pour gérer la langue anglaise et la plupart des langues occidentales, mais d'autres langues ont besoin d'encoder des caractères locaux. Les 128 caractères de l'ASCII ne sont pas suffisant, ce qui demande de passer de 7 à 8 bits. On retombe donc sur un octet ! D'ailleurs, les encodages ASCII améliorés utilisaient les 127 premières valeurs pour l'ASCII normal, mais les 128 suivantes encodaient d'autres caractères. Les encodages en question varient d'un pays ou d'une langue à l'autre, mais ils sont terriblement importants, ce qui est un argument pour l'usage des octets.
===Le support des données encodées sur un ''nibble''===
Une autre raison pour l'usage des octets est qu'il est facile de les découper en deux ''nibbles''. Pour rappel, les ''nibbles'' sont des groupes de 4 bits. Et il se trouve que de nombreuses données sont encodées sur 4 bits, ni plus ni moins. Les ''nibbles'' sont très important pour le support du BCD et de l’hexadécimal. Et c'était un avantage important au début de l'informatique, moins maintenant. Un octet regroupe deux ''nibbles'' : un ''nibble'' de poids faible, un ''nibble'' de poids fort.
[[File:Nibbles in a byte.PNG|centre|vignette|upright=2|Nibbles dans un octet.]]
Un chiffre BCD occupe un ''nibble'', ce qui fait que la taille idéale de l'unité mémoire doit être un multiple de 4 bits : 4 bits, 8 bits, 12 bits, 16 bits, etc. Un ''nibble'' de 4 bits est insuffisant pour encoder des caractères, 8 bits fonctionne parfaitement, pas besoin d'aller plus loin. De plus, l'usage des ''nibbles'' permet de gérer la représentation hexadécimale très facilement. Un chiffre hexadécimal est encodé sur exactement un ''nibble'', un octet regroupe deux chiffres hexadécimaux, deux octets 4 chiffres hexadécimaux, etc. Enfin, 8 bits est une puissance de deux, ce qui simplifie grandement l'encodage des entiers en binaire est est une source de nombreuses simplifications. Les circuits de l'ordinateur sont plus simple à concevoir quand ils manipulent des opérandes de 2, 4, 8, 16, 32, 64, 128 bits.
===L'influence sur la taille des opérandes===
Les anciens ordinateurs étaient conçus pour le calcul scientifique, ou du moins des applications similaires. Ils n'avaient pas besoin de supporter le BCD, ni même de gérer du texte. Ils ne faisaient que des calculs et utilisaient des opérandes flottantes. La taille des opérandes était dictée par des considérations calculatoires. La norme à l'époque était que les calculs scientifiques devaient gérer des opérandes pouvant aller jusqu'à 10 chiffres décimaux. Et cela demande d'utiliser des opérandes entiers de 35 bits, avec un bit en plus pour encoder des opérandes positives ou négatives. Les 36 bits étaient parfois découpés en 6 groupes de 6 bits chacun, au lieu des octets de 8 bits.
Mais ces groupes de 6 bits étaient souvent peu utilisés. Prenons l'exemple des mémoires RAM. Plus haut, j'ai dit qu'une mémoire RAM normale est composée de plusieurs octets, tous numérotés. Intuitivement, vous vous dites que les anciens ''mainframes'' remplaçaient ces octets par des groupes de 6 bits dans la RAM. Mais en réalité, l'équivalent des RAM de l'époque utilisait des groupes de 36 bits. les groupes de 36 bits étaient aussi numérotés, les numéros étant appelés des adresses mémoire. Par contre, les adresses/numéros étaient codés sur 12 ou 18 bits, c'est à dire sur 2 ou 3 groupes de 6 bits. La raison est que ces numéros sont manipulés par le processeur, qui gère des opérandes de 36 bits. L'idée est que le numéro ne prend que la moitié ou un tiers des 36 bits gérés par le processeur.
Avec l'apparition de l'informatique commerciale, les ordinateurs ont du s'adapter aux besoins des entreprises, pour gérer la comptabilité, des applications financières, des documents texte, etc. Et cela demandait de supporter à la fois le BCD et les caractères/lettres. Il a fallu trouver une unité mémoire capable de gérer à la fois nombres entiers, chiffres BCD et caractères. L'octet était parfait pour ça. Les ''mainframes'' sont adaptés en supportant des caractères codés sur 6, 7 ou 8 bits. Mais le support demandait d'utiliser des circuits qui découpaient un groupe de 36 bits en plusieurs caractères ou en plusieurs chiffres BCD. Le tout demandait des circuits de décalage et de masquage assez complexes. Les ordinateurs 8 et 16 bits manipulaient des octets nativement : leur mémoire RAM gérait des octets, le processeur traitait des opérandes de 8, 16 ou 32 bits. Ils n'avaient pas besoin de matériel complexe pour extraire un caractère ou un chiffre BCD d'un octet/doublet/multiplet. C'était un avantage que les anciens ''mainframes'' n'avaient pas.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'encodage des données
| prevText=L'encodage des données
| next=Les codes de détection/correction d'erreur
| nextText=Les codes de détection/correction d'erreur
}}
</noinclude>
b4q8oqk40qi1riznj004sq8l7ren92a
775639
775638
2026-11-02T15:20:25Z
Mewtow
31375
/* Traduire un nombre en écriture scientifique binaire */
775639
wikitext
text/x-wiki
Dans le chapitre précédent, nous avons vu que les ordinateurs actuels utilisent un codage binaire. Ce codage binaire ne vous est peut-être pas familier. Aussi, dans ce chapitre, nous allons apprendre comment coder des nombres en binaire. Nous allons commencer par le cas le plus simple : les nombres positifs. Par la suite, nous aborderons les nombres négatifs. Et nous terminerons par les nombres à virgules, appelés aussi nombres flottants.
==Le codage des nombres entiers positifs==
Pour coder des nombres entiers positifs, il existe plusieurs méthodes : le binaire, l’hexadécimal, le code Gray, le décimal codé binaire et bien d'autres encore. La plus connue est certainement le binaire, secondée par l'hexadécimal, les autres étant plus anecdotiques. Pour comprendre ce qu'est le binaire, il nous faut faire un rappel sur les nombres entiers tel que vous les avez appris en primaire, à savoir les entiers écrits en décimal. Prenons un nombre écrit en décimal : le chiffre le plus à droite est le chiffre des unités, celui à côté est pour les dizaines, suivi du chiffre des centaines, et ainsi de suite. Dans un tel nombre :
* on utilise une dizaine de chiffres, de 0 à 9 ;
* chaque chiffre est multiplié par une puissance de 10 : 1, 10, 100, 1000, etc. ;
* la position d'un chiffre dans le nombre indique par quelle puissance de 10 il faut le multiplier : le chiffre des unités doit être multiplié par 1, celui des dizaines par 10, celui des centaines par 100, et ainsi de suite.
Exemple avec le nombre 1337 :
<math>1337 = 1 \times 1000 + 3 \times 100 + 3 \times 10 + 7 \times 1</math>
Pour résumer, un nombre en décimal s'écrit comme la somme de produits, chaque produit multipliant un chiffre par une puissance de 10. On dit alors que le nombre est en base 10.
<math>1337 = 1 \times 10^3 + 3 \times 10^2 + 3 \times 10^1 + 7 \times 10^0</math>
Ce qui peut être fait avec des puissances de 10 peut être fait avec des puissances de 2, 3, 4, 125, etc : n'importe quel nombre entier strictement positif peut servir de '''base'''. En informatique, on utilise rarement la base 10 à laquelle nous sommes tant habitués. On utilise à la place deux autres bases :
* La base 2 (système binaire) : les chiffres utilisés sont 0 et 1 ;
* La base 16 (système hexadécimal) : les chiffres utilisés sont 0, 1, 2, 3, 4, 5, 6, 7, 8 et 9 ; auxquels s'ajoutent les six premières lettres de notre alphabet : A, B, C, D, E et F.
===Le système binaire===
En binaire, on compte en base 2. Cela veut dire qu'au lieu d'utiliser des puissances de 10 comme en décimal, on utilise des puissances de deux : n'importe quel nombre entier peut être écrit sous la forme d'une somme de puissances de 2. Par exemple 6 s'écrira donc 0110 en binaire : <math>0 \times 2^3 + 1 \times 2^2 + 1 \times 2^1 + 0 \times 2^0 = 6</math>. On peut remarquer que le binaire n'autorise que deux chiffres, à savoir 0 ou 1 : ces chiffres binaires sont appelés des '''bits''' (abréviation de Binary Digit). Pour simplifier, on peut dire qu'un bit est un truc qui vaut 0 ou 1. Pour résumer, tout nombre en binaire s'écrit sous la forme d'un produit entre bits et puissances de deux de la forme :
<math>a_0 \times 2^0 + a_1 \times 2^1 + a_2 \times 2^2 + a_3 \times 2^3 + a_4 \times 2^4 +...+ a_n \times 2^n</math>
Les coefficients <math>a_0, a_1, a_2</math> sont les bits, l'exposant n qui correspond à un bit <math>a_n</math> est appelé le '''poids''' du bit.
====La terminologie du binaire====
En informatique, il est rare que l'on code une information sur un seul bit. Dans la plupart des cas, l'ordinateur manipule des nombres codés sur plusieurs bits. Les informaticiens ont donné des noms aux groupes de bits suivant leur taille. Le plus connu est certainement l''''octet''', qui désigne un groupe de 8 bits. Moins connu, on parle de '''''nibble''''' pour un groupe de 4 bits (un demi-octet), de '''doublet''' pour un groupe de 16 bits (deux octets) et de '''quadruplet''' pour un groupe de 32 bits (quatre octets).
Précisons qu'en anglais, le terme ''byte'' n'est pas synonyme d'octet. En réalité, le terme ''octet'' marche aussi bien en français qu'en anglais. Quant au terme ''byte'', il désigne un concept complètement différent, que nous aborderons plus tard (c'est la plus petite unité de mémoire que le processeur peut adresser). Il a existé dans le passé des ordinateurs où le ''byte'' faisait 4, 7, 9, 16, voire 48 bits, par exemple. Il a même existé des ordinateurs où le ''byte'' faisait exactement 1 bit ! Mais sur presque tous les ordinateurs modernes, un ''byte'' fait effectivement 8 bits, ce qui fait que le terme ''byte'' est parfois utilisé en lieu et place d'''octet''. Mais c'est un abus de langage, attention aux confusions ! Dans ce cours, nous parlerons d'octet pour désigner un groupe de 8 bits, en réservant le terme ''byte'' pour sa véritable signification.
À l'intérieur d'un nombre, le '''bit de poids faible''' est celui qui est le plus à droite du nombre, alors que le '''bit de poids fort''' est celui non nul qui est placé le plus à gauche, comme illustré dans le schéma ci-dessous.
C'est le même principe que l'écriture des nombres en base décimale : le chiffre le plus significatif à gauche, et le moins significatif à droite (l'unité ou les décimales après la virgule).
[[File:Msb.svg|centre|vignette|upright=2|Bit de poids fort.]]
[[File:Lsb.svg|centre|vignette|upright=2|Bit de poids faible.]]
Cette terminologie s'applique aussi pour les bits à l'intérieur d'un octet, d'un ''nibble'', d'un doublet ou d'un quadruplet. Pour un nombre codés sur plusieurs octets, on peut aussi parler de l'octet de poids fort et de l'octet de poids faible, du doublet de poids fort ou de poids faible, etc.
====La traduction binaire→décimal====
Pour traduire un nombre binaire en décimal, il faut juste se rappeler que la position d'un bit indique par quelle puissance il faut le multiplier. Ainsi, le chiffre le plus à droite est le chiffre des unités : il doit être multiplié par 1 (<math>2^0</math>). Le chiffre situé immédiatement à gauche du chiffre des unités doit être multiplié par 2 (<math>2^1</math>). Le chiffre encore à gauche doit être multiplié par 4 (<math>2^2</math>), et ainsi de suite. Mathématiquement, on peut dire que le énième bit en partant de la droite doit être multiplié par <math>2^{n-1}</math>. Par exemple, la valeur du nombre noté 1011 en binaire est de <math>8 \times 1 + 4 \times 0 + 2 \times 1 + 1 \times 1 = 11</math>.
[[File:Value of digits in the Binary numeral system.svg|centre|vignette|upright=2|Valeur des chffres dans le système de numération binaire.]]
====La traduction décimal→binaire====
La traduction inverse, du décimal au binaire, demande d'effectuer des divisions successives par deux. Les divisions en question sont des divisions euclidiennes, avec un reste et un quotient. En lisant les restes des divisions dans un certain sens, on obtient le nombre en binaire. Voici comment il faut procéder, pour traduire le nombre 34 :
[[File:Exemple d'illustration de la méthode de conversion décimal vers binaire.png|centre|vignette|upright=2|Exemple d'illustration de la méthode de conversion décimal vers binaire.]]
====Quelques opérations en binaire====
Maintenant que l'on sait coder des nombres en binaire normal, il est utile de savoir comment faire quelques opérations usuelles en binaire. Nous utiliserons les acquis de cette section dans la suite du chapitre, bien que de manière assez marginale.
La première opération est assez spécifique au binaire. Il s'agit d'une opération qui inverse les bits d'un nombre : les 0 deviennent des 1 et réciproquement. Par exemple, le nombre 0001 1001 devient 1110 0110. Elle porte plusieurs noms : opération NOT, opération NON, complémentation, etc. Nous parlerons de '''complémentation''' ou d'opération NOT dans ce qui suit. Beaucoup d'ordinateurs gèrent cette opération, ils savent la faire en un seul calcul. Il faut dire que c'est une opération assez utile, bien que nous ne pouvons pas encore expliquer pourquoi.
[[File:Binary Addition Demonstration.svg|vignette|Exemple d'addition en binaire.]]
La seconde opération à aborder est l'addition. Elle se fait en binaire de la même manière qu'en décimal : Pour faire une addition en binaire, on additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. Sauf que l'on additionne des bits. Heureusement, la table d'addition est très simple en binaire :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
La troisième opération est une variante de l'addition appelée l''''opération XOR''', notée <math>\oplus</math>. Il s'agit d'une addition dans laquelle on ne propage pas les retenues. L'addition des deux bits des opérandes se fait normalement, mais les retenues sont simplement oubliées, on n'en tient pas compte. Le résultat est que l'addition se résume à appliquer la table d'addition précédente :
* 0 <math>\oplus</math> 0 = 0 ;
* 0 <math>\oplus</math> 1 = 1 ;
* 1 <math>\oplus</math> 0 = 1 ;
* 1 <math>\oplus</math> 1 = 0.
Pour résumer, le résultat vaut 1 si les deux bits sont différents, 0 s'ils sont identiques. L'opération XOR sera utilisée rapidement dans le chapitre suivant, quand nous parlerons rapidement du mot de parité. Et elle sera beaucoup utilisée dans la suite du cours, nous en feront fortement usage. Pour le moment, mémorisez juste cette opération, elle n'a rien de compliqué.
Une dernière opération est l'opération de '''''population count'''''. Il s'agit ni plus ni moins que de compter le nombre de bits qui sont à 1 dans un nombre. Par exemple, pour le nombre 0110 0010 1101 1110, elle donne pour résultat 9. Elle est utilisée dans certaines applications, comme le calcul de certains codes correcteurs d'erreur, comme on le verra dans le chapitre suivant. Elle est supportée sur de nombreux ordinateurs, encore que cela dépende du processeur considéré. Il s'agit cependant d'une opération assez courante, supportée par les processeurs ARM, les processeurs x86 modernes (ceux qui gèrent le SSE), et quelques autres.
===L'hexadécimal===
L’hexadécimal est basé sur le même principe que le binaire, sauf qu'il utilise les 16 chiffres suivants :
{| class="wikitable"
|- style="color:#8080ffff; text-align:center;"
! Chiffre hexadécimal
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || A || B || C || D || E || F
|- style="text-align:center;"
! Nombre décimal correspondant
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 10 || 11 || 12 || 13 || 14 || 15
|- style="text-align:center;"
! Notation binaire
| 0000 || 0001 || 0010 || 0011 || 0100 || 0101 || 0110 || 0111 || 1000 || 1001 || 1010 || 1011 || 1100 || 1101 || 1110 || 1111
|}
Dans les textes, afin de différencier les nombres décimaux des nombres hexadécimaux, les nombres hexadécimaux sont suivis par un petit h, indiqué en indice. Si cette notation n'existait pas, des nombres comme 2546 seraient ambigus : on ne saurait pas dire sans autre indication s'ils sont écrits en décimal ou en hexadécimal. Avec la notation, on sait de suite que 2546 est en décimal et que 2546h est en hexadécimal.
Dans les codes sources des programmes, la notation diffère selon le langage de programmation.
Certains supportent le suffixe <code>h</code> pour les nombres hexadécimaux, d'autres utilisent un préfixe <code>0x</code> ou <code>0h</code>.
Pour convertir un nombre hexadécimal en décimal, il suffit de multiplier chaque chiffre par la puissance de 16 qui lui est attribuée. Là encore, la position d'un chiffre indique par quelle puissance celui-ci doit être multiplié : le chiffre le plus à droite est celui des unités, le second chiffre le plus à droite doit être multiplié par 16, le troisième chiffre en partant de la droite doit être multiplié par 256 (16 * 16) et ainsi de suite. La technique pour convertir un nombre décimal vers de l’hexadécimal est similaire à celle utilisée pour traduire un nombre du décimal vers le binaire. On retrouve une suite de divisions successives, mais cette fois-ci les divisions ne sont pas des divisions par 2 : ce sont des divisions par 16.
La conversion inverse, de l'hexadécimal vers le binaire est très simple, nettement plus simple que les autres conversions. Pour passer de l'hexadécimal au binaire, il suffit de traduire chaque chiffre en sa valeur binaire, celle indiquée dans le tableau au tout début du paragraphe nommé « Hexadécimal ». Une fois cela fait, il suffit de faire le remplacement. La traduction inverse est tout aussi simple : il suffit de grouper les bits du nombre par 4, en commençant par la droite (si un groupe est incomplet, on le remplit avec des zéros). Il suffit alors de remplacer le groupe de 4 bits par le chiffre hexadécimal qui correspond.
==Interlude propédeutique : la capacité d'un entier et les débordements d'entiers==
Dans la section précédente, nous avons vu comment coder des entiers positifs en binaire ou dans des représentations proches. La logique voudrait que l'on aborde ensuite le codage des entiers négatifs. Mais nous allons déroger à cette logique simple, pour des raisons pédagogiques. Nous allons faire un interlude qui introduira des notions utiles pour la suite du chapitre. De plus, ces concepts seront abordés de nombreuses fois dans ce wikilivre et l'introduire ici est de loin la solution idéale.
===Les ordinateurs manipulent des nombres codés sur un nombre fixe de bits===
Vous avez certainement déjà entendu parler de processeurs 32 ou 64 bits. Et si vous avez joué aux jeux vidéos durant votre jeunesse et êtes assez agé, vous avez entendu parler de consoles de jeu 8 bits, 16 bits, 32 bits, voire 64 bits (pour la Jaguar, et c'était un peu trompeur). Derrière cette appellation qu'on retrouvait autrefois comme argument commercial dans la presse se cache un concept simple. Tout ordinateur manipule des nombres entiers dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Une console 16 bits manipulait des entiers codés en binaire sur 16 bits, pas un de plus, pas un de moins. Pareil pour les anciens ordinateurs 32 bits, qui manipulaient des nombres entiers codés sur 32 bits.
Aujourd'hui, les ordinateurs modernes utilisent presque un nombre de bits qui est une puissance de 2 : 8, 16, 32, 64, 128, 256, voire 512 bits. Mais cette règle souffre évidemment d'exceptions. Aux tout débuts de l'informatique, certaines machines utilisaient 3, 7, 13, 17, 23, 36 et 48 bits ; mais elles sont aujourd'hui tombées en désuétude. De nos jours, il ne reste que les processeurs dédiés au traitement de signal audio, que l'on trouve dans les chaînes HIFI, les décodeurs TNT, les lecteurs DVD, etc. Ceux-ci utilisent des nombres entiers de 24 bits, car l'information audio est souvent codée par des nombres de 24 bits.
Anecdote amusante, il a existé des ordinateurs de 1 bit, qui sont capables de manipuler des nombres codés sur 1 bit, pas plus. Un exemple est le Motorola MC14500B, commercialisé de 1976.
====Le lien entre nombre de bits et valeurs codables====
Évidemment, on ne peut pas coder tous les entiers possibles et imaginables avec seulement 8 bits, ou 16 bits. Et il parait intuitif que l'on ait plus de valeurs codables sur 16 bits qu'avec 8 bits, par exemple. Plus le nombre de bits est important, plus on pourra coder de valeurs entières différentes. Mais combien de plus ? Par exemple, si je passe de 8 bits à 16 bits, est-ce que le nombre de valeurs que l'on peut coder double, quadruple, pentuple ? De même, combien de valeurs différentes on peut coder avec <math>n</math> bits. Par exemple, combien de nombres différents peut-on coder avec 4, 8 ou 16 bits ? La section précédente vous l'expliquer.
Avec <math>n</math> bits, on peut coder <math>2^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>2^n - 1</math>. N'oubliez pas cette formule : elle sera assez utile dans la suite de ce tutoriel. Pour exemple, on peut coder 16 valeurs avec 4 bits, qui vont de 0 à 15. De même, on peut coder 256 valeurs avec un octet, qui vont de 0 à 255. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de bits
! Nombre de valeurs codables
|-
! 4
| 16
|-
! 8
| 256
|-
! 16
| 65 536
|-
! 32
| 4 294 967 296
|-
! 64
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien de bits il faut pour coder une valeur quelconque, que nous noterons N. Pour cela, il faut utiliser la formule précédente, mais à l'envers. On cherche alors <math>n</math> tel que <math>N = 2^n</math>. L'opération qui donne <math>n</math> est appelée le logarithme, et plus précisément un logarithme en base 2, noté <math>\log_2{(X)}</math>. Le problème est que le résultat du logarithme ne tombe juste que si le nombre X est une puissance de 2. Si ce n'est pas le cas, le résultat est un nombre à virgule, ce qui n'a pas de sens pratique. Par exemple, la formule nous dit que pour coder le nombre 13, on a besoin de 3,70043971814 bits, ce qui est impossible. Pour que le résultat ait un sens, il faut arrondir à l'entier supérieur. Pour l'exemple précédent, les 3,70043971814 bits s'arrondissent en 4 bits.
====Le lien entre nombre de chiffres hexadécimaux et valeurs codables====
Maintenant, voyons combien de valeurs peut-on coder avec <math>n</math> chiffres hexadécimaux. La réponse n'est pas très différente de celle obtenue en binaire, si ce n'est qu'il faut remplacer le 2 par un 16 dans la formule précédente. Avec <math>n</math> chiffres hexadécimaux, on peut coder <math>16^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>16^n - 1</math>. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de chiffres héxadécimaux
! Nombre de valeurs codables
|-
! 1 (4 bits)
| 16
|-
! 2 (8 bits)
| 256
|-
! 4 (16 bits)
| 65 536
|-
! 8 (32 bits)
| 4 294 967 296
|-
! 16 (64 bits)
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien faut-il de chiffres hexadécimaux pour coder une valeur quelconque en hexadécimal. La formule est là encore la même qu'en binaire, sauf qu'on remplace le 2 par un 16. Pour trouver le nombre de chiffres hexadécimaux pour encoder un nombre X, il faut calculer <math>\log_{16}{(X)}</math>. Notons que le logarithme utilisé est un logarithme en base 16, et non un logarithme en base 2, comme pour le binaire. Là encore, le résultat ne tombe juste que si le nombre X est une puissance de 16 et il faut arrondir à l'entier supérieur si ce n'est pas le cas.
Une propriété mathématique des logarithmes nous dit que l'on peut passer d'un logarithme en base X et à un logarithme en base Y avec une simple division, en utilisant la formule suivante :
: <math>\log_X{(N)} = {\log_{Y}{(N)} \over \log_Y(X)}</math>
Dans le cas qui nous intéresse, on a Y = 2 et X = 16, ce qui donne :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over \log_2(16)}</math>
Or, <math>\log_2(16)</math> est tout simplement égal à 4, car il faut 4 bits pour coder la valeur 16. On a donc :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over 4}</math>
En clair, il faut quatre fois moins de chiffres hexadécimaux que de bits, ce qui est assez intuitif vu qu'il faut 4 bits pour coder un chiffre hexadécimal.
===Les débordements d'entier===
On vient de voir que tout ordinateur manipule des nombres dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Et cela limite les valeurs qu'il peut encoder, qui sont comprises dans un intervalle bien précis. Mais que ce passe-t-il si jamais le résultat d'un calcul ne rentre pas dans cet intervalle ? Par exemple, pour du binaire normal, que faire si le résultat d'un calcul atteint ou dépasse <math>2^n</math> ? Dans ce cas, le résultat ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
On peut imaginer d'autres codages pour lesquels les entiers ne commencent pas à zéro ou ne terminent pas à <math>2^n - 1</math>. On peut prendre le cas où l'ordinateur gère les nombres négatifs, par exemple. Dans le cas général, l'ordinateur peut coder les valeurs comprises dans un intervalle, qui va de la valeur la plus basse <math>N_\text{min}</math> à la valeur la plus grande <math>N_\text{max}</math>. Et encore une fois, si un résultat de calcul sort de cet intervalle, on fait face à un débordement d'entier.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math>, avec <math>N_\text{max}</math> la plus grande valeur codable par l'ordinateur.
On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur basse de débordement vaut <math>N_\text{min} - 1</math>, avec <math>N_\text{min}</math> la plus petite valeur codable par l'ordinateur.
===La gestion des débordements d'entiers===
Face à un débordement d'entier, l'ordinateur peut utiliser deux méthodes : l'arithmétique saturée ou l'arithmétique modulaire.
L''''arithmétique saturée''' consiste à arrondir le résultat pour prendre la plus grande ou la plus petite valeur. Si le résultat d'un calcul dépasse la valeur haute de débordement, le résultat est remplacé par le plus grand entier supporté par l'ordinateur. La même chose est possible quand le résultat est inférieur à la plus petite valeur possible, par exemple lors d'une soustraction : l'ordinateur arrondit au plus petit entier possible.
Pour donner un exemple, voici ce que cela donne avec des entiers codés sur 4 bits, qui codent des nombres de 0 à 15. Si je fais le calcul 8 + 9, le résultat normal vaut 17, ce qui ne rentre pas dans l'intervalle. Le résultat est alors arrondi à 15. Inversement, si je fais le calcul 8 - 9, le résultat sera de -1, ce qui ne rentre pas dans l'intervalle : le résultat est alors arrondi à 0.
[[File:Odometer rollover.jpg|vignette|Exemple de débordement d'entier sur un compteur mécanique quelconque. L'image montre clairement que le compteur revient à zéro une fois la valeur maximale dépassée.]]
L''''arithmétique modulaire''' est plus compliquée et c'est elle qui va nous intéresser dans ce qui suit. Pour simplifier, imaginons que l'on décompte à partir de zéro. Quand on arrive à la valeur haute de débordement, on recommence à compter à partir de zéro. L'arithmétique modulaire n'est pas si contre-intuitive et vous l'utilisez sans doute au quotidien. Après tout, c'est comme cela que l'on compte les heures, les minutes et les secondes. Quand on compte les minutes, on revient à 0 au bout de 60 minutes. Pareil pour les heures : on revient à zéro quand on arrive à 24 heures. Divers compteurs mécaniques fonctionnent sur le même principe et reviennent à zéro quand ils dépassent la plus grande valeur possible, l'image ci-contre en montrant un exemple.
Mathématiquement, l'arithmétique modulaire implique des divisions euclidiennes, celles qui donnent un quotient et un reste. Lors d'un débordement, le résultat s'obtient comme suit : on divise le nombre qui déborde par la valeur haute de débordement, et que l'on conserve le reste de la division. Au passage, l'opération qui consiste à faire une division et à garder le reste au lieu du quotient est appelée le ''modulo''. Prenons l'exemple où l'ordinateur peut coder tous les nombres entre 0 et 1023, soit une valeur haute de débordement de 1024. Pour coder le nombre 4563, on fait le calcul 4563 / 1024. On obtient : <math>4563 = 4 \times 1024 + 467</math>. Le reste de la division est de 467 et c'est lui qui sera utilisé pour coder la valeur de départ, 4563. Le nombre 4563 est donc codé par la valeur 467 dans un tel ordinateur en arithmétique modulaire. Au passage, la valeur haute de débordement est toujours codée par un zéro dans ce genre d'arithmétique.
Les ordinateurs utilisent le plus souvent une valeur haute de débordement de <math>2^n</math>, avec n le nombre de bits utilisé pour coder les nombres entiers positifs. En faisant cela, l'opération modulo devient très simple et revient à éliminer les bits de poids forts au-delà du énième bit. Par exemple, reprenons l'exemple d'un ordinateur qui code ses nombres sur 4 bits. Imaginons qu'il fasse le calcul <math>13 + 3</math>, soit 1101 + 0011 = 1 0000 en binaire. Le résultat entraîne un débordement d'entier et l'ordinateur ne conserve que les 4 bits de poids faible. Cela donne : 1101 + 0011 = 0000. Comme autre exemple l'addition 1111 + 0010 ne donnera pas 17 (1 0001), mais 1 (0001).
L'avantage est que les calculs sont beaucoup plus simples avec cette méthode qu'avec les autres. L'ordinateur a juste à ne pas calculer les bits de poids fort. Pas besoin de faire une division pour calculer un modulo, pas besoin de corriger le résultat pour faire de l'arithmétique saturée.
===Aparté : quelques valeurs particulières en binaire===
Plus haut, on a dit qu'avec n bits, on peut encoder toutes les valeurs allant de <math>0</math> à <math>2^n - 1</math>. Ce simple fait permet de déterminer quelle est la valeur de certains entiers à vue d’œil. Dans ce qui va suivre, nous allons poser quelques bases que nous réutiliserons dans la suite du chapitre. Il s'agit de quelques trivias qui sont cependant assez utiles.
Le premier trivia concerne la valeur maximale <math>2^n - 1</math> : elle est encodée par un nombre dont tous les bits sont à 1.
Le deuxième trivia concerne les nombres de la forme 000...000 1111 1111, à savoir des nombres dont les x bits de poids faible sont à 1 et tous les autres valent 0. Par définition, de tels nombres codent la plus grande valeur possible sur x bits, ce qui fait qu'ils valent <math>2^x - 1</math>.
Le troisième trivia concerne les nombres de la forme 11111...0000. En clair, des nombres où on a une suite consécutive de 1 dans les bits de poids fort et les x bits de poids faibles à 0. De tels nombres valent : <math>2^n - 2^x</math>. La preuve est assez simple : ils s'obtiennent en prenant la valeur maximale <math>2^n - 1</math>, et en soustrayant un nombre de la forme <math>2^x - 1</math>.
[[File:Valeurs particulières en binaire.png|centre|vignette|upright=2|Valeurs particulières en binaire]]
Si on utilise l'arithmétique modulaire, la valeur <math>2^n</math> n'est autre que la valeur de débordement haute pour les nombres stockés sur n bits, et se code comme le zéro (il faudrait 1 bit de plus pour stocker le 1 de poids fort de <math>2^n</math>). Les 1er et 3ème nombres évoqués dans les paragraphes précédents peuvent donc se passer de <math>2^n</math> dans leur expression :
* <math>- 1</math> est codé 1111111111111111...111 (n bits à 1)
* <math>- 2^x</math> est codé 11111...111000..000000 (x bits à 0 précédés de n-x bits à 1)
==Les nombres entiers négatifs==
<noinclude>[[File:Binary Representations.svg|vignette|Représentations signées, exemples sur 4 bits.]]</noinclude>
Passons maintenant aux entiers négatifs en binaire : comment représenter le signe moins ("-") avec des 0 et des 1 ? Eh bien, il existe plusieurs méthodes :
* la représentation en signe-valeur absolue ;
* la représentation en complément à un ;
* la représentation en complément à deux;
* la représentation par excès ;
* la représentation dans une base négative ;
* d'autres représentations encore moins utilisées que les autres.
===La représentation en signe-valeur absolue===
La solution la plus simple pour représenter un entier négatif consiste à coder sa valeur absolue en binaire, et rajouter un bit qui précise si c'est un entier positif ou un entier négatif. Par convention, ce bit de signe vaut 0 si le nombre est positif et 1 s'il est négatif. On parle alors de représentation en signe-valeur absolue, aussi appelée '''représentation en signe-magnitude'''.
Avec cette technique, il y autant de nombres positifs que négatifs. Mieux : pour chaque nombre représentable en représentation signe-valeur absolue, son inverse l'est aussi. Ce qui fait qu'avec cette méthode, le zéro est codé deux fois : on a un -0, et un +0. Cela pose des problèmes lorsqu'on demande à notre ordinateur d'effectuer des calculs ou des comparaisons avec zéro.
[[File:Codage sur 4 bits en signe-valeur absolue.png|centre|vignette|upright=2|Codage sur 4 bits en signe-valeur absolue]]
===La représentation en complément à un===
La représentation en '''complément à un''' peut être vue, en première approximation, comme une variante de la représentation en signe-magnitude. Et je dis bien : "en première approximation", car il y a beaucoup à dire sur la représentation en complément à un, mais nous verrons cela dans la section suivante sur le complément à deux. Conceptuellement, la représentation en signe-magnitude et celle du complément à un sont drastiquement différentes et sont basées sur des concepts mathématiques totalement différents. Mais elles ont des ressemblances de surface, qui font que faire la comparaison entre les deux est assez utile pédagogiquement parlant.
En complément à un, les nombres sont codés en utilisant un bit de signe qui indique si le nombre de positif ou négatif, couplé à une valeur absolue. Par contre, la valeur absolue est codée différemment en binaire. Pour les nombres positifs, la valeur absolue est codée en binaire normal, pas de changement comparé aux autres représentations. Mais pour les valeurs négatives, la valeur absolue est codée en binaire normal, puis tous les bits sont inversés : les 0 deviennent des 1 et réciproquement. En clair, on utilise une opération de complémentation pour les nombres négatifs.
Prenons un exemple avec un nombre codé sur 4 bits, avec un cinquième bit de signe. La valeur 5 est codée comme suit : 0 pour le bit de signe, 5 donne 0101 en binaire, le résultat est 0 0101. Pour la valeur -5, le bit de signe est 1, 5 donne 0101 en binaire, on inverse les bits ce qui donne 1010 : cela donne 11010. Notez qu'on peut passer d'un résultat à l'autre avec une opération de complémentation, à savoir en inversant les bits de l'autre et réciproquement.
Il s'agit là d'une propriété générale avec le complément à 1 : l'opposé d'un nombre, à savoir passer de sa valeur positive à sa valeur négative ou inversement, se calcule avec une opération NOT, une opération de complémentation (d'où son nom). L'avantage est que les ordinateurs gèrent naturellement d'opération de complémentation. Par contre, en signe-magnitude, inverser le bit de signe est une opération spécifique et ne sert qu'à ça. Il faut donc rajouter une opération en plus pour calculer l'opposé d'un nombre.
La représentation en complément à un garde les défauts de la représentation en signe-magnitude. Le zéro est codé deux fois, avec un zéro positif et un zéro négatif. La différence est que les valeurs négatives sont dans l'ordre inverse, il y a une symétrie un peu meilleure. Les deux zéros sont d'ailleurs totalement éloignés, ils correspondent aux valeurs extrêmes encodables : le zéro positif a tous ses bits à 0, le zéro négatif a tous ses bits à 1. Le résultat est que l'implémentation des comparaisons et de certains calculs est plus complexe qu'avec le signe-magnitude, mais de peu.
[[File:Codage sur 4 bits en complément à 1.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 1.]]
===La représentation par excès===
La représentation par excès consiste à ajouter un biais aux nombres à encoder afin de les encoder par un entier positif. Pour encoder tous les nombres compris entre -X et Y en représentation par excès, il suffit de prendre la valeur du nombre à encoder, et de lui ajouter un biais égal à X. Ainsi, la valeur -X sera encodée par zéro, et toutes les autres valeurs le seront par un entier positif, le zéro sera encodé par X, 1, par X+1, etc. Par exemple, prenons des nombres compris entre -127 et 128. On va devoir ajouter un biais égal à 127, ce qui donne :
{|class="wikitable"
|-
!Valeur avant encodage!!Valeur après encodage
|-
||-127||0
|-
||-126||1
|-
||-125||2
|-
||…||…
|-
||0||127
|-
||…||…
|-
||127||254
|-
||128||255
|}
===La représentation en complément à deux===
La représentation en '''complément à deux''' est basée sur les mêmes mathématiques que le complément à un, mais fonctionne très différemment en pratique.
Si on regarde de loin, son principe est assez différent : il n'y a pas de bit de signe ni quoi que ce soit d'autre. À la place, un nombre en complément à deux est encodé comme en binaire normal, à un point près : le bit de poids fort est soustrait, et non additionné aux autres. Il a une valeur négative : on soustrait la puissance de deux associée s'il vaut 1, on ne la tient pas en compte s'il vaut 0.
Par exemple, la valeur du nombre noté 111001 en complément à deux s'obtient comme suit :
{|class="wikitable" style="margin:auto;"
|-
! -32 !! 16 !! 8 !! 4 !! 2 !! 1
|-
| 1 || 1 || 1 || 0 || 0 || 1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(8×1)+(4×0)+(2×1)+(1×1) = −32+16+8+1 = -7.
: ''Avec le complément à deux, comme avec le complément à un, le bit de poids fort vaut 0 pour les nombres positifs et 1 pour les négatifs.''
L'avantage de cette représentation est qu'elle n'a pas de double zéro : le zéro n'est encodé que par une seule valeur. Par contre, la valeur autrefois prise par le zéro négatif est réutilisée pour encoder une valeur négative. La conséquence est que l'on a un nombre négatif en plus d'encodé, il n'y a plus le même nombre de valeurs strictement positives et de valeurs négatives encodées. Le nombre négatif en question est appelé le '''nombre le plus négatif''', ce nom trahit le fait que c'est celui qui a la plus petite valeur (la plus grande valeur absolue). Il est impossible de coder l'entier positif associé, sa valeur absolue.
[[File:Codage sur 4 bits en complément à 2.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 2.]]
====Le calcul du complément à deux : première méthode====
Les représentations en complément à un et en complément à deux sont basées sur le même principe mathématique. Leur idée est de remplacer chaque nombre négatif par un nombre positif équivalent, appelé le ''complément''. Par équivalent, on veut dire que tout calcul donne le même résultat si on remplace un nombre négatif par son complément (idem avec un nombre positif, son complément aura un signe inverse). Et pour faire cela, elles se basent sur les débordements d'entier pour fonctionner, et plus précisément sur l'arithmétique modulaire abordée plus haut.
Si on fait les calculs avec le complément, les résultats du calcul entraînent un débordement d'entier, qui sera résolu par un '''modulo''' : l'ordinateur ne conserve que les bits de poids faible du résultat, les autres bits sont oubliés. Par exemple, prenons l'addition 15 + 2, 1111 + 0010 en binaire : le résultat ne sera pas 17 (10001), vu qu'on n'a pas assez de bits pour encoder le résultat, mais 1 (0001). Et le résultat après modulo sera identique au résultat qu'on aurait obtenu avec le nombre négatif sans modulo. En clair, c'est la gestion des débordements qui permet de corriger le résultat de manière à ce que l'opération avec le complément donne le même résultat qu'avec le nombre négatif voulu. Ainsi, on peut coder un nombre négatif en utilisant son complément positif.
: Cela ressemble beaucoup à la méthode de soustraction basée sur un complément à 9 pour ceux qui connaissent, sauf que c'est une version binaire qui nous intéresse ici.
Prenons un exemple, qui permettra d'introduire la suite. Encore une fois, on utilise un codage sur 4 bits dont la valeur haute de débordement est de 16. Prenons l'addition de 13 + 3 = 16. Avec l'arithmétique modulaire, 16 est équivalent à 0, ce qui donne : 13 + 3 = 0 ! On peut aussi reformuler en disant que 13 = -3, ou encore que 3 = -13. Dit autrement, 3 est le complément de -13 pour ce codage. Et ne croyez pas que ça marche uniquement dans cet exemple : cela se généralise assez rapidement à tout nombre négatif.
Prenons un nombre N dont un veut calculer le complément à deux K. Dans le cas général, on a :
: <math>N + K = 2^n</math>, vu que <math>2^n = 0</math> en utilisant le modulo.
En réorganisant très légèrement les termes pour isoler K, on a :
: <math>K = 2^n - N</math>
La formule précédent permet de calculer la complément à deux assez simplement, en faisant le calcul à la main.
Avant de poursuivre, prenons un exemple très intéressant : le cas où N = -1. Son complément à deux vaut donc :
: <math>K = 2^n - 1</math>
Le terme <math>2^n - 1</math> devrait vous rappeler quelque chose : il s'agit d'un nombre dont tous les bits sont à 1. En clair, le complément à deux de -1 est un nombre de la forme 1111...111. Et cela vaut quel que soit le nombre de bits n. La représentation de -1 est similaire, peu importe que l'on utilise des nombres de 16 bits, 32 bits, 64 bits, etc.
Voyons maintenant le cas des puissance de deux, par exemple 2, 4, 8, 16, etc. Leur complément à deux vaut :
: <math>K = 2^n - 2^x</math>
Nous avions vu précédemment dans le chapitre que ces nombres sont de la forme 11111...0000. En clair, des nombres dont les x bits de poids faible sont à 0, et tous les autres bits sont à 1. Par exemple, le complément à deux de -2 est un nombre dont les bits sont à 1, sauf le bit de poids faible. De même, le complément à deux de -4 a tous ses bits à 1, sauf les deux bits de poids faible. Et le complément à deux de -8 a tous ses bits à 1, sauf les trois bits de poids faible. Pour résumer, tout nombre de la forme <math>- 2^x</math> a tous ses bits à 1, sauf les x bits de poids faible qui sont à 0.
{|class="wikitable"
|+ Exemple avec des nombres de 8 bits
|-
! -1
| 1111 1111
|-
! -2
| 1111 1110
|-
! -4
| 1111 1100
|-
! -8
| 1111 1000
|-
! -16
| 1111 0000
|-
! -32
| 1110 0000
|-
! -64
| 1100 0000
|-
! -128
| 1000 0000
|-
! 0 / - 256
| 0000 0000
|}
====Le calcul du complément à deux : seconde méthode====
Il existe une seconde méthode pour calculer le complément à deux d'un nombre, la voici. Pour les nombres positifs, encodez-les comme en binaire normale. Pour les nombres négatifs, faites pareil, puis inversez tous les bits, avant d'ajouter un. La procédure est identique à celle du complément à un, sauf que l'on incrémente le résultat final. Et ce n'est pas une coïncidence, comme nous allons le voir immédiatement.
Pour comprendre pourquoi la méthode marche, repartons de la formule précédente <math>K = 2^n - N</math>. Elle peut se reformuler comme suit :
: <math>K = (2^n - 1 + 1) - N</math>
: <math>K = (2^n - 1) - N + 1</math>
La valeur <math>(2^n - 1)</math> est par définition un nombre dont tous les bits sont à 1. À cette valeur, on soustrait un nombre <math>|N|</math> dont certains bits sont à 1 et d'autres à 0. En clair, pour chaque colonne, on a deux possibilités : soit on doit faire la soustraction <math>1-1</math>, soit la soustraction <math>1-0</math>. Or, les règles de l'arithmétique binaire disent que <math>1-1=0</math> et <math>1-0=1</math>. En regardant attentivement, on se rend compte que le bit du résultat est l'inverse du bit de départ. De plus, les deux cas ne donnent pas de retenue : le calcul pour chaque bit n'influence pas les bits voisins.
Le terme <math>(2^n - 1) - N</math> est donc le complément à un du nombre N, un nombre égal à sa représentation en binaire dont tous les bits sont inversés. Notons <math>\overline{|N|}</math> le nombre formé en inversant tous les bits de N. On a alors :
: <math>K = \overline{N} + 1</math>
En clair, le complément à deux s'obtient en prenant le complément à un et en ajoutant 1. Dit autrement, il faut prendre le nombre N, en inverser tous les bits et ajouter 1.
Une autre manière équivalente consiste à faire le calcul suivant :
: <math>K = \overline{N - 1}</math>
On prend le nombre dont on veut le complément, on soustrait 1 et on inverse les bits.
: Notons que tout ce qui a été dit plus haut marche aussi pour le complément à un, avec cependant une petite différence : la valeur haute de débordement n'est pas la même, ce qui change les calculs. Pour des nombres codés sur <math>n</math> bits, la valeur haute de débordement est égale à <math>2^n</math> en complément à deux, alors qu'elle est de <math>2^n - 1</math> en complément à un. De ce fait, la gestion des débordements est plus simple en complément à deux.
====L'extension de signe====
Dans les ordinateurs, tous les nombres sont codés sur un nombre fixé et constant de bits. Ainsi, les circuits d'un ordinateur ne peuvent manipuler que des nombres de 4, 8, 12, 16, 32, 48, 64 bits, suivant la machine. Si l'on veut utiliser un entier codé sur 16 bits et que l'ordinateur ne peut manipuler que des nombres de 32 bits, il faut bien trouver un moyen de convertir le nombre de 16 bits en un nombre de 32 bits, sans changer sa valeur et en conservant son signe. Cette conversion d'un entier en un entier plus grand, qui conserve valeur et signe s'appelle l''''extension de signe'''.
L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
L'explication plus simple tient dans la manière de coder le bit de poids fort. Prenons l'exemple de la conversion d'un entier de 5 bits en un entier de 8bits. Les 4 bits de poids faible ont un poids positif (on les additionne), alors que le bit de poids fort a un poids négatif. Le nombre encodé vaut : <math>\text{Bit de poids fort} \times -16 + \text{Valeur encodée sur les 4 bits de poids faible}</math>. La valeur encodée sur 4 bits reste la même après extension de signe, car les poids des bits de poids faible ne changent pas. Par contre, le bit de poids fort change. Sur 8 bits, la valeur -16 est encodée par un nombre de la forme 1111 0000. En remplaçant le bit de poids fort par sa valeur calculée sur plus de bits, on remarque que les bits de poids fort ont été remplacés par des 1.
===La représentation négabinaire===
Enfin, il existe une dernière méthode, assez simple à comprendre, appelée '''représentation négabinaire'''. Dans cette méthode, les nombres sont codés non en base 2, mais en base -2. Oui, vous avez bien lu : la base est un nombre négatif. Dans les faits, la base -2 est similaire à la base 2 : il y a toujours deux chiffres (0 et 1), et la position dans un chiffre indique toujours par quelle puissance de 2 il faut multiplier, sauf qu'il faudra ajouter un signe moins une fois sur 2. Concrètement, les puissances de -2 sont les suivantes : 1, -2, 4, -8, 16, -32, 64, etc. En effet, un nombre négatif multiplié par un nombre négatif donne un nombre positif, ce qui fait qu'une puissance sur deux est négative, alors que les autres sont positives. Ainsi, on peut représenter des nombres négatifs, mais aussi des nombres positifs dans une puissance négative.
Par exemple, la valeur du nombre noté 11011 en base -2 s'obtient comme suit :
{|class="wikitable"
|-
!…
!-32
!16
!-8
!4
!-2
!1
|-
|…
|1
|1
|1
|0
|1
|1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(−8×1)+(4×0)+(−2×1)+(1×1)=−32+16−8−2+1=−25.
==Les nombres à virgule fixe==
On sait donc comment sont stockés nos nombres entiers dans un ordinateur. Néanmoins, les nombres entiers ne sont pas les seuls nombres que l'on utilise au quotidien : il nous arrive d'en utiliser à virgule. Notre ordinateur n'est pas en reste : il est lui aussi capable de manipuler de tels nombres. Dans les grandes lignes, il peut utiliser deux méthodes pour coder des nombres à virgule en binaire : La virgule fixe et la virgule flottante. Parlons d'abord de la virgule fixe, plus simple à comprendre.
===La virgule fixe : des entiers cachés===
La méthode de la virgule fixe consiste à émuler les nombres à virgule à partir de nombres entiers. Un nombre à virgule fixe est codé par un nombre entier proportionnel au nombre à virgule fixe. Pour obtenir la valeur de notre nombre à virgule fixe, il suffit de diviser l'entier servant à le représenter par le facteur de proportionnalité. Par exemple, pour coder 1,23 en virgule fixe, on peut choisir comme « facteur de conversion » 1000, ce qui donne l'entier 1230.
Généralement, les informaticiens utilisent une puissance de deux comme facteur de conversion, pour simplifier les calculs. En faisant cela, on peut écrire les nombres en binaire et les traduire en décimal facilement. Pour l'exemple, cela permet d'écrire des nombres à virgule en binaire comme ceci : 1011101,1011001. Et ces nombres peuvent se traduire en décimal avec la même méthode que des nombres entier, modulo une petite différence. Comme pour les chiffres situés à gauche de la virgule, chaque bit situé à droite de la virgule doit être multiplié par la puissance de deux adéquate. La différence, c'est que les chiffres situés à droite de la virgule sont multipliés par une puissance négative de deux, c'est à dire par <math>1 \over 2</math>, <math>1 \over 4</math>, <math>1 \over 8</math>, <math>1 \over 16</math>, <math>1 \over 32</math>, ...
[[File:Fixed-point binary representation.svg|centre|vignette|upright=2.5|Nombres à virgule fixe.]]
Cette méthode est assez peu utilisée de nos jours, quoiqu'elle puisse avoir quelques rares applications relativement connue. Un bon exemple est celui des banques : les sommes d'argent déposées sur les comptes ou transférées sont codés en virgule fixe. Les sommes manipulées par les ordinateurs ne sont pas exprimées en euros, mais en centimes d'euros. Et c'est une forme de codage en virgule fixe dont le facteur de conversion est égal à 100. La raison de ce choix est que les autres méthodes de codage des nombres à virgule peuvent donner des résultats imprécis : il se peut que les résultats doivent être tronqués ou arrondis, suivant les opérandes. Cela n'arrive jamais en virgule fixe, du moins quand on se limite aux additions et soustractions.
===Les nombres à virgule non-représentables en binaire===
Quel que soit la façon de représenter les nombres à virgules, il existe des nombres qui ne peuvent être représentés de manière exacte, à savoir avec un nombre fini de décimales après la virgule. En soi, ce n'est pas spécifique au binaire, on a la même chose en décimal. Par exemple, la fraction 1/3 en décimal s'écrit 0.3333333..., avec une infinité de 3. La même chose existe en binaire, mais pour des nombres différents.
Déjà, évacuons le cas des nombres irrationnels, à savoir les nombres qui ne peuvent pas s'écrire sous la forme d'une fraction, comme <math>pi</math> ou <math>e</math>. Ils ont une infinité de décimales que ce soit en binaire, en décimal, en hexadécimal, ou autre. Ils ne sont pas représentables avec un nombre fini de décimales quelle que soit la base utilisée. Concentrons-nous sur des nombres qui ne sont pas dans ce cas, et qui ont un nombre fini ou infini de décimales.
Le passage de la base 10 à la base 2 change le nombre de décimales, et peut faire passer d'un nombre fini de décimales à un nombre infini. Par exemple, <math>\frac{1}{5} = 0.2</math> est représenté en binaire avec une séquence infinie de bits : <math>0.001100110011...</math>. Les nombres étant en base binaire et représenté avec un nombre limité de bits, il existe certains nombres décimaux triviaux qui ne sont pas représentables avec un nombre fini de décimales.
Et la réciproque n'est pas vraie : tout nombre binaire avec un nombre fini de décimales en binaire est représentable avec un nombre fini de décimales en base 10. Ceci est lié à la décomposition en facteur premier des bases utilisées :
* <math>2 = 2^1</math>
* <math>10 = 2^1 \times 5^1</math>
Le nombre 10 possède tous les facteurs premiers de 2, mais 2 n'a pas de 5 dans sa décomposition.
==Les nombres flottants==
Les nombres à virgule fixe ont aujourd'hui été remplacés par les '''nombres à virgule flottante''', où le nombre de chiffres après la virgule est variable. Le codage d'un nombre flottant est basée sur son ''écriture scientifique''. Pour rappel, en décimal, l’écriture scientifique d'un nombre consiste à écrire celui-ci comme un produit entre un nombre <math>a</math> et une puissance de 10. Ce qui donne :
: <math>a \times 10^e</math>, avec <math>a \in [1, 10 [</math>
Le nombre <math>a</math> est appelé le ''significande'' et il est compris entre 1 (inclus) et 10 (exclu). Cette contrainte garantit que l'écriture scientifique d'un nombre est unique, qu'il n'y a qu'une seule façon d'écrire un nombre en notation scientifique. Pour cela, on impose le nombre de chiffre à gauche de la virgule et le plus simple est que celui-ci soit égal à 1. Mais il faut aussi que celui-ci ne soit pas nul. En effet, si on autorise de mettre un 0 à gauche de la virgule, il y a plusieurs manières équivalentes d'écrire un nombre. Ces deux contraintes font que le significande doit être égal ou plus grand que 1, mais strictement inférieur à 10. Par contre, on peut mettre autant de décimales que l'on veut.
En binaire, c'est la même chose, mais avec une puissance de deux. Cela implique de modifier la puissance utilisée : au lieu d'utiliser une puissance de 10, on utilise une puissance de 2.
: <math>a \times 2^e</math>, avec <math>a \in [1, 2 [</math>
Le significande est aussi altéré, au même titre que la puissance, même si les contraintes sont similaires à celles en base 10. En effet, le nombre <math>a</math> ne possède toujours qu'un seul chiffre à gauche de la virgule, comme en base 10. Vu que seuls deux chiffres sont possibles (0 et 1) en binaire, on s'attend à ce que le chiffre situé à gauche de la virgule soit un zéro ou un 1. Mais rappelons que le chiffre à gauche doit être non-nul, pour les mêmes raisons qu'en décimal. En clair, le significande a forcément un bit à 1 à gauche de la virgule. Pour récapituler, l'écriture scientifique binaire d'un nombre consiste à écrire celui-ci sous la forme :
: <math>1,m \times 2^e</math>, avec <math>m \in [0, 1 [</math>
La partie fractionnaire du nombre <math>a</math>, qu'on appelle la '''mantisse'''.
[[File:Mantissa.svg|centre|vignette|Écriture scientifique (anglais).]]
Pour déterminer l'écriture scientifique en binaire d'un nombre quelconque, la procédure dépend de la valeur du nombre en question. Tout dépend s'il est dans l'intervalle <math>[1, 2 [</math>, au-delà de 2 ou en-dessous de 1.
* Pour un nombre entre 1 (inclus) et 2 (exclu), il suffit de le traduire en binaire. Son exposant est 0.
* Pour un nombre au-delà de 2, il faut le diviser par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant est alors le nombre de fois qu'il a fallu diviser par 2.
* Pour un nombre plus petit que 1, il faut le multiplier par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant se calcule en prenant le nombre de fois qu'il a fallu multiplier par 2, et en prenant l'opposé (en mettant un signe - devant le résultat).
===Le codage des nombres flottants et la norme IEEE 754===
Pour coder cette écriture scientifique avec des nombres, l'idée la plus simple est d'utiliser trois nombres, pour coder respectivement la mantisse, l'exposant et un bit de signe. Coder la mantisse implique que le bit à gauche de la virgule vaut toujours 1, mais nous verrons qu'il y a quelques rares exceptions à cette règle.
Quelques nombres flottants spécialisés, les dénormaux, ne sont pas codés en respectant les règles pour le significande et ont un 0 à gauche de la virgule. Un bon exemple est tout simplement la valeur zéro, que l'on peut coder en virgule flottante, mais seulement en passant outre les règles sur le significande.
Toujours est-il que le bit à gauche de la virgule n'est pas codé, que ce soit pour les flottants normaux ou les fameux dénormaux qui font exception. On verra que ce bit peut se déduire en fonction de l'exposant utilisé pour encoder le nombre à virgule, ce qui lui vaut le nom de '''bit implicite'''. L'exposant peut être aussi bien positif que négatif (pour permettre de coder des nombres très petits), et est encodé en représentation par excès sur n bits avec un biais égal à <math>2^{n-1} - 1</math>.
[[File:IEEE754 Format General.png|centre|vignette|upright=2|IEEE754 Format Général]]
Le standard pour le codage des nombres à virgule flottante est la '''norme IEEE 754'''. Cette norme va (entre autres) définir quatre types de flottants différents, qui pourront stocker plus ou moins de valeurs différentes.
{|class="wikitable"
|-
!Classe de nombre flottant
!Nombre de bits utilisés pour coder un flottant
!Nombre de bits de l'exposant
!Nombre de bits pour la mantisse
!Décalage
|-
!Simple précision
| 32
| 8
| 23
| 127
|-
!Double précision
| 64
| 11
| 52
| 1023
|-
!Double précision étendue
| 80 ou plus
| 15 ou plus
| 64 ou plus
| 16383 ou plus
|}
Voici à peu-près comment les nombres flottants sont répartis sur une ligne numérique. Vous remarquerez que les flottants sont de plus en plus éloignés au fur et à mesure qu'on s'éloigne de zéro. La raison est l'encodage avec un exposant et une mantisse. L'écart entre deux flottants reste stable pour un même exposant, mais il y a une cassure quand on passe d'un exposant au suivant.
[[File:FloatingPointPrecisionAugmented.png|centre|vignette|upright=3|Floating Point Precision.]]
IEEE754 impose aussi le support de certains nombres flottants spéciaux qui servent notamment à stocker des valeurs comme l'infini. Commençons notre revue des flottants spéciaux par les '''dénormaux''', aussi appelés flottants dénormalisés. Ces flottants ont une particularité : leur bit implicite vaut 0. Ces dénormaux sont des nombres flottants où l'exposant est le plus petit possible. Le '''zéro''' est un dénormal particulier dont la mantisse est nulle. Au fait, remarquez que le zéro est codé deux fois à cause du bit de signe : on se retrouve avec un -0 et un +0.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur minimale (0 en binaire) || Mantisse différente de zéro (dénormal strict) ou égale à zéro (zéro)
|}
Fait étrange, la norme IEEE754 permet de représenter '''l'infini''', aussi bien en positif qu'en négatif. Celui-ci est codé en mettant l'exposant à sa valeur maximale et la mantisse à zéro. Et le pire, c'est qu'on peut effectuer des calculs sur ces flottants infinis. Mais cela a peu d'utilité.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse égale à zéro
|}
Mais malheureusement, l'invention des flottants infinis n'a pas réglé tous les problèmes. Par exemple, quel est le résultat de <math>\infty - \infty</math> ? Ou encore <math>0 - \infty</math> ? Autant prévenir tout de suite : mathématiquement, on ne peut pas savoir quel est le résultat de ces opérations. Pour pouvoir résoudre ces calculs, il a fallu inventer un nombre flottant qui signifie « je ne sais pas quel est le résultat de ton calcul pourri ». Ce nombre, c'est '''NaN'''. NaN est l'abréviation de ''Not A Number'', ce qui signifie : n'est pas un nombre. Ce NaN a un exposant dont la valeur est maximale, mais une mantisse différente de zéro. Pour être plus précis, il existe différents types de NaN, qui diffèrent par la valeur de leur mantisse, ainsi que par les effets qu'ils peuvent avoir. Malgré son nom explicite, on peut faire des opérations avec NaN, mais cela ne sert pas vraiment à grand chose : une opération arithmétique appliquée avec un NaN aura un résultat toujours égal à NaN.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse différente de zéro
|}
===Les arrondis et exceptions===
La norme impose aussi une gestion des arrondis ou erreurs, qui arrivent lors de calculs particuliers. En voici la liste :
{|class="wikitable"
|-
!Nom de l’exception
!Description
|-
!Invalid operation
|Opération qui produit un NAN. Elle est levée dans le cas de calculs ayant un résultat qui est un nombre complexe, ou quand le calcul est une forme indéterminée. Pour ceux qui ne savent pas ce que sont les formes indéterminées, voici en exclusivité la liste des calculs qui retournent NaN : <math>\infty - \infty</math>, <math>0 \times \infty</math>, <math>0^0</math>, <math>1^{\infty}</math>, <math>\infty^0</math>.
|-
!Overflow
|Résultat trop grand pour être stocké dans un flottant. Le plus souvent, on traite l'erreur en arrondissant le résultat en vue de la taille de la mantisse;
|-
!Underflow
|Pareil que le précédent, mais avec un résultat trop petit. Le plus souvent, on traite l'erreur en arrondissant le résultat vers 0.
|-
!Division par zéro
|Le nom parle de lui-même. La réponse la plus courante est de répondre + ou - l'infini.
|-
!'''Inexact'''
|Le résultat ne peut être représenté par un flottant et on doit l'arrondir.
|}
La gestion des arrondis pose souvent problème. Pour donner un exemple, on va prendre le nombre 0,1. En binaire, ce nombre s'écrit comme ceci : 0,1100110011001100... et ainsi de suite jusqu'à l'infini. Notre nombre utilise une infinité de décimales. Bien évidemment, on ne peut pas utiliser une infinité de bits pour stocker notre nombre et on doit impérativement l'arrondir. Comme vous le voyez avec la dernière exception, le codage des nombres flottants peut parfois poser problème : dans un ordinateur, il se peut qu'une opération sur deux nombres flottants donne un résultat qui ne peut être codé par un flottant. On est alors obligé d'arrondir ou de tronquer le résultat de façon à le faire rentrer dans un flottant. Pour éviter que des ordinateurs différents utilisent des méthodes d'arrondis différentes, on a décidé de normaliser les calculs sur les nombres flottants et les méthodes d'arrondis. Pour cela, la norme impose le support de quatre modes d'arrondis :
* Arrondir vers + l'infini ;
* vers - l'infini ;
* vers zéro ;
* vers le nombre flottant le plus proche.
===Les nombres flottants logarithmiques===
Les '''nombres flottants logarithmiques''' sont une spécialisation des nombres flottants IEEE754, ou tout du moins une spécialisation des flottants écrits en écriture scientifique. Un nombre logarithmique est donc composé d'un bit de signe et d'un exposant, sans mantisse. La mantisse est totalement implicite : tous les flottants logarithmiques ont la même mantisse, qui vaut 1.
Pour résumer, il ne reste que l'exposant, qui est tout simplement le logarithme en base 2 du nombre encodé, d'où le nom de codage flottant logarithmique donné à cette méthode. Attention toutefois : l'exposant est ici un nombre fractionnaire, codé en virgule fixe. Le choix d'un exposant fractionnaire permet de représenter pas mal de nombres de taille diverses.
{|class="wikitable"
|-
!
! Bit de signe
! Exposant
|-
! Représentation binaire
| 0
| 01110010101111
|-
! Représentation décimale
| +
| 1040,13245464
|}
L'utilité de cette représentation est de simplifier certains calculs, comme les multiplications, divisions, puissances, etc. En effet, les mathématiques nous disent que le logarithme d'un produit est égal à la somme des logarithmes : <math>\ln{(a \times b)} = \ln{a} + \ln{b}</math>. Or, il se trouve que les ordinateurs sont plus rapides pour faire des additions/soustractions que pour faire des multiplications/divisions. Donc, la représentation logarithmique permet de remplacer les multiplications/divisions par des additions/soustractions, plus simples et plus rapides pour l'ordinateur.
Évidemment, les applications des flottants logarithmiques sont rares, limitées à quelques situations bien précises (traitement d'image, calcul scientifique spécifiques).
==Les encodages binaires alternatifs==
Outre le binaire que nous venons de voir, il existe d'autres manières de coder des nombres avec des bits. Et nous allons les aborder dans cette section. Parmi celle-ci, nous parlerons du code Gray, de la représentation ''one hot'', du unaire et du ternaire. Nous en parlons car elles seront utiles dans la suite du cours, bien que de manière assez limitée. Autant nous passerons notre temps à parler du binaire normal, autant les représentations que nous allons voir sont aujourd'hui utilisées dans des cas assez spécifiques. Et elles sont plus courantes que vous ne le pensez.
===Le code Gray===
Le '''code Gray''' est un encodage binaire qui a une particularité très intéressante : deux nombres consécutifs n'ont qu'un seul bit de différence. Pour exemple, voici ce que donne le codage des 8 premiers entiers sur 3 bits :
{|class="wikitable"
! Décimal
! Binaire naturel
! Codage Gray
|-
|0||000||000
|-
|1||001||001
|-
|2||010||011
|-
|3||011||010
|-
|4||100||110
|-
|5||101||111
|-
|6||110||101
|-
|7||111||100
|}
Les utilisations du code Gray sont assez nombreuses, bien qu'on n'en croise pas tous les jours. Un exemple : le code Gray est très utile dans certains circuits appelés les compteurs, qui mémorisent un nombre et l'incrémentent (+1) ou le décrémentent (-1) suivant les besoins de l'utilisateur. Il est aussi utilisé dans des scénarios difficiles à expliquer ici (des codes correcteurs d'erreur ou des histoires de passages de domaines d'horloge). Mais le point important est que ce code sera absolument nécessaire dans quelques chapitres, quand nous parlerons des tables de Karnaugh, un concept important pour la conception de circuits électroniques. Ne passez pas à côté de cette section.
Pour construire ce code Gray, on peut procéder en suivant plusieurs méthodes, les deux plus connues étant la méthode du miroir et la méthode de l'inversion.
[[File:Binary-reflected Gray code construction.svg|vignette|Construction d'un code gray par la méthode du miroir.]]
La méthode du miroir est relativement simple. Pour connaître le code Gray des nombres codés sur n bits, il faut :
* partir du code Gray sur n-1 bits ;
* symétriser verticalement les nombres déjà obtenus (comme une réflexion dans un miroir) ;
* rajouter un 0 au début des anciens nombres, et un 1 au début des nouveaux nombres.
Il suffit de connaître le code Gray sur 1 bit pour appliquer la méthode : 0 est codé par le bit 0 et 1 par le bit 1.
Une autre méthode pour construire la suite des nombres en code Gray sur n bits est la méthode de l'inversion. Celle-ci permet de connaître le codage du nombre n à partir du codage du nombre n-1, comme la méthode du dessus. On part du nombre 0, systématiquement codé avec uniquement des zéros. Par la suite, on décide quel est le bit à inverser pour obtenir le nombre suivant, avec la règle suivante :
* si le nombre de 1 est pair, il faut inverser le dernier chiffre.
* si le nombre de 1 est impair, il faut localiser le 1 le plus à droite et inverser le chiffre situé à sa gauche.
Pour vous entraîner, essayez par vous-même avec 2, 3, voire 5.
===Les représentations ''one-hot'' et unaire===
La représentation ''one-hot'' et la représentation unaire sont deux représentations assez liées, mais légèrement différentes.
La '''représentation unaire''' est la représentation en base 1. Avec elle, le zéro est codé en mettant tous les bits du nombre à zéro, la valeur 1 est encodée avec la valeur 000...0001, le deux avec 000...0011, le trois avec 000...0111, etc. Pour résumer, le nombre N est encodé en mettant les N bits de poids faible à 1, à l'exception du zéro qui est encodé...par un zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! Unaire
|-
| 0 || 000 || 0000 0000
|-
| 1 || 001 || 0000 0001
|-
| 2 || 010 || 0000 0011
|-
| 3 || 011 || 0000 0111
|-
| 4 || 100 || 0000 1111
|-
| 5 || 101 || 0001 1111
|-
| 6 || 110 || 0011 1111
|-
| 7 || 111 || 0111 1111
|-
| 8 || 1000 || 1111 1111
|}
Pour convertir un nombre codé en unaire vers le binaire, il suffit de compter le nombre de 1. C'est ni plus ni moins ce que fait l'opération dite de ''population count'' dont nous avons parlé plus haut. La représentation unaire permet d'encoder N+1 valeurs sur N bits, car le zéro est encodé à part. Une valeur pour le zéro, plus une par bit.
Avec la '''représentation ''one-hot''''', un nombre est codé sur N bits, dont un seul bit est à 1. La position du 1 dans le nombre indique sa valeur. Précisément, la valeur encodée est égale au poids du bit à 1. En faisant cela, le zéro est codé en mettant le bit de poids faible à 1, la valeur 1 est codée avec les bits 00...00010, la valeur 2 est codée par les bits 00...000100, etc.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Elle permet d'encoder N valeurs différentes avec N bits, zéro inclus. On perd une valeur par rapport à l'unaire, car le zéro n'et pas encodé à part du reste. La traduction du binaire vers le ''one-hot'' est réalisé par un circuit nommé décodeur, que nous verrons d'ici quelques chapitres. De même, la traduction d'un nombre encodé en ''one-hot'' vers le binaire est réalisée par un circuit électronique appelé l'encodeur, qui est en quelque sorte l'inverse de l'encodeur. Les deux circuits sont très utilisés en électronique, ce sont des circuits de base, que nous n'aurons de cesse d'utiliser pour fabriquer d'autres circuits.
L'utilité de ces deux représentations n'est pas évidente. Mais sachez qu'elle le deviendra quand nous parlerons des circuits appelés les "compteurs", tout comme ce sera le cas pour le code Gray. Elles sont très utilisées dans des circuits appelés des machines à état, qui doivent incorporer des circuits compteurs efficients. Et ces représentations permettent d'avoir des circuits pour compter qui sont très simples, efficaces, rapides et économes en circuits électroniques. La représentation unaire sera aussi utile à la toute fin du cours, dans les chapitres liés à l'exécution dans le désordre. Il en sera fait référence quand nous parlerons de fenêtres d'instruction, d'émission dans l'ordre, de ''scoreboarding'', etc.
Il faut aussi noter qu'il a existé des ordinateur qui manipulaient des nombres codés en unaire. Enfin presque, la représentation utilisée était proche de l'unaire, mais ressemblait fortement. De tels ordinateurs étaient appelés des ''ordinateurs stochastiques'' et nous les aborderons à la toute fin de ce wikilivre, dans un chapitre annexe portant sur les ordinateurs ternaires (qui comptent en base 3) et unaires.
==Les encodages hybrides entre décimal et binaire==
Pour terminer, nous allons voir les représentations qui mixent binaire et décimal. Il s'agit en réalité d'encodage qui permettent de manipuler des nombres codés en décimal, mais les chiffres sont codés en utilisant des bits. L'encodage ''Binary Coded Decimal'' est le plus connu de cette catégorie, mais il y en a quelques autres, qui sont moins connus. De telles représentations étaient très utilisées au début de l'informatique, mais sont aujourd'hui tombées en désuétude. Pour désigner ces encodages, nous parlerons d''''encodages bit-décimaux''' : décimaux pour préciser qu'ils encodent des nombres codés en décimal, bit pour préciser que les chiffres sont codés avec des bits.
De tels encodages étaient utilisés sur les tous premiers ordinateurs pour faciliter le travail des programmeurs, mais aussi sur les premières calculettes. Ils sont utiles dans les applications où on doit manipuler chaque chiffre décimal séparément des autres. Un exemple classique est celui d'une horloge digitale. Ils ont aussi l'avantage de bien se marier avec les nombres à virgule. Les représentations des nombres à virgule fixe ou flottante ont le défaut que des arrondis peuvent survenir. Par exemple, la valeur 0,2 est codée comme suit en binaire normal : 0.00110011001100... et ainsi de suite jusqu’à l'infini. Avec les encodages bit-décimaux, on n'a pas ce problème, 0,2 étant codé 0000 , 0010.
Il a existé des ordinateurs qui travaillaient uniquement avec de tels encodages, appelés des '''ordinateurs décimaux'''. Ils étaient assez courants entre les années 60 et 70, même s'ils ne représentent pas la majorité des architectures de l'époque. Avec eux, la mémoire n'était pas organisée en octets, mais elle stockait des chiffres décimaux codés sur 5-6 bits. Le processeur faisait des calculs sur des chiffres bit-décimaux directement.
Leur grand avantage est leur très bonne performance dans les taches de bureautique, de comptabilité, et autres. Les processeurs de l'époque recevaient des entiers codés en bit-décimal de la part des entrées-sorties, et devaient les traiter. Les processeurs binaires devaient faire des conversions décimal-binaire pour communiquer avec les entrées-sorties, mais pas les processeurs décimaux. Le gain en performance pouvait être substantiel dans certaines applications.
Les ordinateurs décimaux se classent en deux sous-types bien précis. Les premiers gèrent des entiers qui ont un nombre de chiffres fixes. Par exemple, l'IBM 7070 gérait des entiers de 10 chiffres, plus un signe +/- pour les entiers signés. Le processeur faisait les calculs directement en bit-décimal, il gérait des entiers faisant environ 10-15 chiffres décimaux et savait faire des calculs avec de tels nombres. Le second sous-type effectue les calculs chiffre par chiffre et géraient des nombres de taille variable, sans limite de chiffres ! Nous reparlerons de ces derniers dans un chapitre ultérieur, quand nous parlerons de la différence entre byte et mot. Pour le moment, ne gardez à l'esprit que les processeurs gérant un nombre fixe de chiffres décimaux, plus simples à comprendre.
===Le ''Binary Coded Decimal''===
Le '''''Binary Coded Decimal''''', abrévié BCD, est une représentation qui mixe binaire et décimal. Avec cette représentation, les nombres sont écrits en décimal, comme nous en avons l'habitude dans la vie courante, sauf que chaque chiffre décimal est directement traduit en binaire sur 4 bits. Prenons l'exemple du nombre 624 : le 6, le 2 et le 4 sont codés en binaire séparément, ce qui donne 0110 0010 0100.
{| class="wikitable"
|+ Codage BCD
|-
! Nombre encodé (décimal)
! scope="col" colspan="4" | BCD
|-
| 0 || 0 || 0 || 0 || 0
|-
| 1 || 0 || 0 || 0 || 1
|-
| 2 || 0 || 0 || 1 || 0
|-
| 3 || 0 || 0 || 1 || 1
|-
| 4 || 0 || 1 || 0 || 0
|-
| 5 || 0 || 1 || 0 || 1
|-
| 6 || 0 || 1 || 1 || 0
|-
| 7 || 0 || 1 || 1 || 1
|-
| 8 || 1 || 0 || 0 || 0
|-
| 9 || 1 || 0 || 0 || 1
|}
On peut remarquer que 4 bits permettent de coder 16 valeurs, là où il n'y a que 10 chiffres. Dans le BCD proprement dit, les combinaisons de bits qui correspondent à 10, 11, 12, 13, 14 ou 15 ne sont tout simplement pas prises en compte. Sur quelques ordinateurs, ces combinaisons codent des chiffres décimaux en double : certains chiffres pouvaient être codés de deux manières différentes. Il est aussi possible d'utiliser ces valeurs pour coder quelque chose d'autre que des chiffres. Par exemple, il est possible de les utiliser pour coder un signe + ou -, afin de gérer les entiers relatifs. Une autre possibilité, complémentaire de la précédente, utilise ces valeurs en trop pour coder une virgule, afin de gérer les nombres non-entiers. Les possibilités sont nombreuses.
Le support du BCD implique souvent que le processeur supporte des '''opérations BCD''', à savoir des opérations capables de travailler sur des opérandes en BCD et de donner un résultat en BCD. Il faut bien faire la différence entre les opérations en binaire et les opérations en BCD. Par exemple, on n'effectue pas une addition de la même manière en binaire et en décimal/BCD, même si les grandes lignes sont presque identiques. Les différences font que les processeurs doivent avoir des opérations différentes pour les deux encodages, de la même manière que les processeurs gèrent les opérations sur les flottants et les entiers séparément.
Le support du codage BCD est abandonné de nos jours, c'est surtout quelque chose qu'on trouve sur les anciens processeurs. Les architectures 8 et 16 bits supportaient à la fois des opérations binaires et des opérations BCD. Mais il a aussi existé une méthode intermédiaire, qui utilisait des additions binaires normales sur des opérandes BCD. L'idée est que le résultat de l'addition est incorrect, car les valeurs 10 à 15 peuvent apparaître comme chiffre dans le résultat. Mais on peut le corriger pour obtenir le résultat exact en BCD. Pour résumé, les processeurs faisaient des additions en binaire, et corrigeaient le résultat avec une opération spécifique pour obtenir un résultat en BCD. Nous en reparlerons dans le chapitre sur le langage machine et l'assembleur, dans lequel nous étudierons les différentes opérations que supporte un processeur.
===Les encodages compacts du BCD===
Des variantes du BCD visent à réduire le nombre de bits utilisés pour encoder un nombre décimal. Nous allons les appeler les '''encodages BCD compacts'''. Avec certaines variantes, on peut utiliser seulement 7 bits pour coder deux chiffres décimaux, au lieu de 8 bits en BCD normal. Idem pour les nombres à 3 chiffres décimaux, qui prennent 10 bits au lieu de 12. Cette économie est réalisée par une variante du BCD assez compliquée, appelée l''''encodage Chen-Ho'''. Une alternative, appelée le ''Densely Packed Decimal'', arrive à une compression identique, mais avec quelques avantages au niveau de l'encodage. Ces encodages sont cependant assez compliqués à expliquer, surtout à ce niveau du cours, aussi je me contente de simplement mentionner leur existence.
Les encodages BCD compacts sont utilisés par les programmeurs pour stocker des données dans un fichier, en mémoire, mais guère plus. Ils ne sont pas gérés par le processeur directement, on ne peut pas faire de calculs avec, le processeur ne gére pas d'opération BCD supportant de tels encodages. C'est théoriquement possible, mais ça n'a jamais été implémenté dans un processeur, le cout en circuit n'en valait pas la chandelle. Pour faire des calculs sur des nombres en BCD compact, on doit les décompresser et les convertir en BCD normal ou en binaire, puis faire les calculs avec des opérations BCD/binaire usuelles.
===L'encodage ''Excess-3''===
La représentation '''''Excess-3''''' (XS-3) est une variante du BCD, qui a autrefois était utilisée sur d'anciens ordinateurs décimaux. Il s'agit d'une sorte d'hybride entre une représentation par excès et BCD. Chaque chiffre décimal est codé sur plusieurs bits en utilisant une représentation binaire par excès. Le biais est de 3 pour la représentation XS-3, il en existe des variantes avec un excès de 4, 5, mais elles sont moins utilisées. Avec elle, la conversion d'un chiffre décimal se fait comme suit : on prend le chiffre décimal, on ajoute 3, puis on traduit en binaire.
{| class="wikitable"
|+ Codage en ''Excess-3''
|-
! Décimal
! scope="col" colspan="4" | Binaire
|-
| -3 || 0 || 0 || 0 || 0
|-
| -2 || 0 || 0 || 0 || 1
|-
| -1 || 0 || 0 || 1 || 0
|-
| 0 || 0 || 0 || 1 || 1
|-
| 1 || 0 || 1 || 0 || 0
|-
| 2 || 0 || 1 || 0 || 1
|-
| 3 || 0 || 1 || 1 || 0
|-
| 4 || 0 || 1 || 1 || 1
|-
| 5 || 1 || 0 || 0 || 0
|-
| 6 || 1 || 0 || 0 || 1
|-
| 7 || 1 || 0 || 1 || 0
|-
| 8 || 1 || 0 || 1 || 1
|-
| 9 || 1 || 1 || 0 || 0
|-
| 10 || 1 || 1 || 0 || 1
|-
| 11 || 1 || 1 || 1 || 0
|-
| 12 || 1 || 1 || 1 || 1
|}
L'avantage de cette représentation est que l'on peut facilement calculer les soustractions en utilisant une méthode bien précise (celle du complément à 10, je ne détaille pas plus). Le défaut est que le calcul des additions est légèrement plus complexe.
L'XS-3 a été utilisé sur quelques ordinateurs décimaux assez anciens, notamment sur l'UNIVAC I et II.
===Les codes bi-quinaires===
Les '''codes bi-quinaires''' utilisent un mélange entre encodage binaire, encodage ''one-hot'' et encodage décimal. L'idée de base derrière ces codes est d'utiliser la représentation ''one-hot'' pour coder un chiffre BCD. Le problème est que cela demande d'utiliser 10 bits pour encoder un chiffre BCD, ce qui est beaucoup. L'encodage bi-quinaire optimise le tout de manière à diviser par deux le nombre de bits requis pour l'encodage.
Pour simplifier, un chiffre encodé en bi-quinaire est composé de deux parties : une partie binaire de un bit, suivie par d'une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits pour encoder les valeurs 0, 1, 2, 3 et 4. La partie binaire indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9 : 0 à 4 si la partie binaire vaut 0, 5 à 9 si elle vaut 1. La valeur du chiffre se calcule comme suit :
* si la partie binaire vaut 0, elle est égale à la partie quinaire ;
* si la partie binaire vaut 1, on ajoute 5 à la partie quinaire.
L'originalité de ce codage est qu'il permet de facilement compter sur ses doigts : on a deux mains, cinq doigts. De nombreuses langues utilisent ce système pour coder des nombres, comme le Wolof. Et ce système est utilisé dans certains pays pour compter sur ses doigts, voire dans la vie de tous les jours.
[[File:Code Biquinaer.svg|vignette|Code bi-quinaire où partie binaire et quinaire sont encodées en représentation ''one-hot''.]]
L'encodage abordé au-dessus est une version simplifiée de l'encodage bi-quinaire, il en existe d'autres variantes. Le bi-quinaire vu juste au-dessus encode la partie binaire en binaire, et la partie quinaire en ''one-hot''. Mais il est aussi possible d'inverser l'encodage, à savoir encoder la partie binaire avec une représentation ''one-hot'', alors que la partie bi-quinaire est encodée en binaire normal.
Cependant, la version la plus intéressante utilise l'encodage ''one-hot'' pour les deux ! Elle est illustrée ci-contre. Avec cette dernière, un chiffre BCD est encodé sur 7 bits, dont 2 sont obligatoirement à 1 et les 5 restants sont à 0. Cette propriété permet de détecter qu'un bit a été inversé suite à une erreur de transmission ou un problème de stockage. Si un bit est inversé, le résultat aura 1 bit à 1 et 6 à 0, ou alors 3 bits à 1 et 4 à 0, ce qui ne colle pas. Le résultat après inversion ne donnera pas un chiffre bi-quinaire valide.
Une autre variante du bi-quinaire a été utilisée sur l'IBM 1401. Avec elle, la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre encodé par la partie quinaire. L'avantage de ce système est que la gestion des retenues lors des additions était grandement simplifiée.
Le bi-quinaire et ses variantes ont été utilisés sur d'anciens ordinateurs, dont l'IBM 650, l'UNIVAC Solid State et le UNIVAC LARC. La plupart utilisaient des variantes de l'encodage bi-quinaire proprement dit. Pour rentrer vraiment dans le détail, voici les encodages utilisés sur ces machines. La lecture est facultative, il est possible de passer directement à la section suivante :
{| class="wikitable"
|-
! !! IBM 650 !! Remington Rand 409 !! UNIVAC Solid State !! UNIVAC LARC
|-
| Chiffre || 1357-9 bits || 05-01234 || p-5-421 bits || p-5-qqq bits
|-
| 0 || 10-10000 || 0000-0 || 1-0-000 || 1-0-000
|-
| 1 || 10-01000 || 1000-0 || 0-0-001 || 0-0-001
|-
| 2 || 10-00100 || 1000-1 || 0-0-010 || 1-0-011
|-
| 3 || 10-00010 || 0100-0 || 1-0-011 || 0-0-111
|-
| 4 || 10-00001 || 0100-1 || 0-0-100 || 1-0-110
|-
| 5 || 01-10000 || 0010-0 || 0-1-000 || 0-1-000
|-
| 6 || 01-01000 || 0010-1 || 1-1-001 || 1-1-001
|-
| 7 || 01-00100 || 0001-0 || 1-1-010|| 0-1-011
|-
| 8 || 01-00010 || 0001-1 || 0-1-011 || 1-1-111
|-
| 9 || 01-00001 || 0000-1 || 1-1-100|| 0-1-110
|}
==Annexe : pourquoi l'octet existe==
Nous avons vu plus haut que les nombres encodés en binaire sont découpés en octets, des paquets de 8 bits. Et les octets sont présents partout dans la conception d'un ordinateur. Par exemple, la mémoire RAM d'un ordinateur moderne est organisée en octet. Elle contient des cases mémoire, chacune contenant un octet. Les octets, les cases mémoire, sont numérotées, ce qui permet de sélectionner un octet parmi tous les autres. La capacité de la mémoire est le nombre d'octets qu'elle contient, elle s'exprime en kilo-octets (1024 octets), mébi-octets (1024 kilo-octets), gibi-octets (1024 mébi-octets). Les octets sont donc présents partout dans l'informatique moderne.
{|class="wikitable"
|+ Contenu de la RAM, simplifié
|-
! Numéro
! Octet
|-
! 0
| 0000 1111
|-
! 1
| 0101 0101
|-
! 2
| 0111 0110
|-
! 3
| 0011 1010
|-
! 4
| 1001 1001
|-
! ...
| ...
|-
! 1023
| 0110 1010
|-
! 1024
| 0001 1110
|}
Mais pourquoi est-ce que les nombres encodés en binaire sont souvent découpés en octets ? Pourquoi ne pas utiliser des groupes de 3, 5, 6, 7, 9, 10 bits ? La question est d'autant plus légitime que d'anciens ordinateurs faisaient ainsi. Leurs mémoires étaient découpé en cases mémoire, mais chacune faisait 7 ou 9 bits, rien d'impossible à cela. À vrai dire, l'usage des octets au tout début de l'informatique était assez rare. Il y a eu un basculement progressif vers l'octet pendant les années 80-90. Reste à expliquer pourquoi. Si on devait résumer, les deux raisons principales sont le support du BCD et le support des caractères pour le texte. Voyons d'abord l'encodage des caractères en binaire.
===L'encodage du texte : ASCII et autres encodages===
Pour les caractères, les encodages se sont concentrés sur l'encodage des alphabets occidentaux. Ils demandaient d'encoder au minimum toutes les lettres, en majuscules et minuscules, plus les chiffres, une dizaine de caractères pour la ponctuation. À cela, il faut ajouter des symboles utiles comme %, µ, $, £, #, {} et bien d'autres. Au minimum, cela fait au minimum 52 lettres (26 lettres doublées pour les majuscules) + 10 chiffres + une dizaine de caractères pour la ponctuation : cela fait plus de 72 valeurs à encoder. Pour encoder cela en binaire, le minimum est de 7 bits, ce qui permet d'encoder 128 caractères : les 72 caractères minimum, plus quelques autres. L'encodage ASCII utilisait d'ailleurs 7 bits pour encoder des nombres.
On pourrait croire que c'était une raison suffisante pour utiliser des groupes de 7 bits. Mais il y a quelques problèmes avec cela. Premièrement, les encodages 7 bits pour le texte marchent bien pour gérer la langue anglaise et la plupart des langues occidentales, mais d'autres langues ont besoin d'encoder des caractères locaux. Les 128 caractères de l'ASCII ne sont pas suffisant, ce qui demande de passer de 7 à 8 bits. On retombe donc sur un octet ! D'ailleurs, les encodages ASCII améliorés utilisaient les 127 premières valeurs pour l'ASCII normal, mais les 128 suivantes encodaient d'autres caractères. Les encodages en question varient d'un pays ou d'une langue à l'autre, mais ils sont terriblement importants, ce qui est un argument pour l'usage des octets.
===Le support des données encodées sur un ''nibble''===
Une autre raison pour l'usage des octets est qu'il est facile de les découper en deux ''nibbles''. Pour rappel, les ''nibbles'' sont des groupes de 4 bits. Et il se trouve que de nombreuses données sont encodées sur 4 bits, ni plus ni moins. Les ''nibbles'' sont très important pour le support du BCD et de l’hexadécimal. Et c'était un avantage important au début de l'informatique, moins maintenant. Un octet regroupe deux ''nibbles'' : un ''nibble'' de poids faible, un ''nibble'' de poids fort.
[[File:Nibbles in a byte.PNG|centre|vignette|upright=2|Nibbles dans un octet.]]
Un chiffre BCD occupe un ''nibble'', ce qui fait que la taille idéale de l'unité mémoire doit être un multiple de 4 bits : 4 bits, 8 bits, 12 bits, 16 bits, etc. Un ''nibble'' de 4 bits est insuffisant pour encoder des caractères, 8 bits fonctionne parfaitement, pas besoin d'aller plus loin. De plus, l'usage des ''nibbles'' permet de gérer la représentation hexadécimale très facilement. Un chiffre hexadécimal est encodé sur exactement un ''nibble'', un octet regroupe deux chiffres hexadécimaux, deux octets 4 chiffres hexadécimaux, etc. Enfin, 8 bits est une puissance de deux, ce qui simplifie grandement l'encodage des entiers en binaire est est une source de nombreuses simplifications. Les circuits de l'ordinateur sont plus simple à concevoir quand ils manipulent des opérandes de 2, 4, 8, 16, 32, 64, 128 bits.
===L'influence sur la taille des opérandes===
Les anciens ordinateurs étaient conçus pour le calcul scientifique, ou du moins des applications similaires. Ils n'avaient pas besoin de supporter le BCD, ni même de gérer du texte. Ils ne faisaient que des calculs et utilisaient des opérandes flottantes. La taille des opérandes était dictée par des considérations calculatoires. La norme à l'époque était que les calculs scientifiques devaient gérer des opérandes pouvant aller jusqu'à 10 chiffres décimaux. Et cela demande d'utiliser des opérandes entiers de 35 bits, avec un bit en plus pour encoder des opérandes positives ou négatives. Les 36 bits étaient parfois découpés en 6 groupes de 6 bits chacun, au lieu des octets de 8 bits.
Mais ces groupes de 6 bits étaient souvent peu utilisés. Prenons l'exemple des mémoires RAM. Plus haut, j'ai dit qu'une mémoire RAM normale est composée de plusieurs octets, tous numérotés. Intuitivement, vous vous dites que les anciens ''mainframes'' remplaçaient ces octets par des groupes de 6 bits dans la RAM. Mais en réalité, l'équivalent des RAM de l'époque utilisait des groupes de 36 bits. les groupes de 36 bits étaient aussi numérotés, les numéros étant appelés des adresses mémoire. Par contre, les adresses/numéros étaient codés sur 12 ou 18 bits, c'est à dire sur 2 ou 3 groupes de 6 bits. La raison est que ces numéros sont manipulés par le processeur, qui gère des opérandes de 36 bits. L'idée est que le numéro ne prend que la moitié ou un tiers des 36 bits gérés par le processeur.
Avec l'apparition de l'informatique commerciale, les ordinateurs ont du s'adapter aux besoins des entreprises, pour gérer la comptabilité, des applications financières, des documents texte, etc. Et cela demandait de supporter à la fois le BCD et les caractères/lettres. Il a fallu trouver une unité mémoire capable de gérer à la fois nombres entiers, chiffres BCD et caractères. L'octet était parfait pour ça. Les ''mainframes'' sont adaptés en supportant des caractères codés sur 6, 7 ou 8 bits. Mais le support demandait d'utiliser des circuits qui découpaient un groupe de 36 bits en plusieurs caractères ou en plusieurs chiffres BCD. Le tout demandait des circuits de décalage et de masquage assez complexes. Les ordinateurs 8 et 16 bits manipulaient des octets nativement : leur mémoire RAM gérait des octets, le processeur traitait des opérandes de 8, 16 ou 32 bits. Ils n'avaient pas besoin de matériel complexe pour extraire un caractère ou un chiffre BCD d'un octet/doublet/multiplet. C'était un avantage que les anciens ''mainframes'' n'avaient pas.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'encodage des données
| prevText=L'encodage des données
| next=Les codes de détection/correction d'erreur
| nextText=Les codes de détection/correction d'erreur
}}
</noinclude>
k0e2un2tmdmk6v72kfimvd5d2nhod0t
775640
775639
2026-11-02T15:24:29Z
Mewtow
31375
/* Annexe : pourquoi l'octet existe */
775640
wikitext
text/x-wiki
Dans le chapitre précédent, nous avons vu que les ordinateurs actuels utilisent un codage binaire. Ce codage binaire ne vous est peut-être pas familier. Aussi, dans ce chapitre, nous allons apprendre comment coder des nombres en binaire. Nous allons commencer par le cas le plus simple : les nombres positifs. Par la suite, nous aborderons les nombres négatifs. Et nous terminerons par les nombres à virgules, appelés aussi nombres flottants.
==Le codage des nombres entiers positifs==
Pour coder des nombres entiers positifs, il existe plusieurs méthodes : le binaire, l’hexadécimal, le code Gray, le décimal codé binaire et bien d'autres encore. La plus connue est certainement le binaire, secondée par l'hexadécimal, les autres étant plus anecdotiques. Pour comprendre ce qu'est le binaire, il nous faut faire un rappel sur les nombres entiers tel que vous les avez appris en primaire, à savoir les entiers écrits en décimal. Prenons un nombre écrit en décimal : le chiffre le plus à droite est le chiffre des unités, celui à côté est pour les dizaines, suivi du chiffre des centaines, et ainsi de suite. Dans un tel nombre :
* on utilise une dizaine de chiffres, de 0 à 9 ;
* chaque chiffre est multiplié par une puissance de 10 : 1, 10, 100, 1000, etc. ;
* la position d'un chiffre dans le nombre indique par quelle puissance de 10 il faut le multiplier : le chiffre des unités doit être multiplié par 1, celui des dizaines par 10, celui des centaines par 100, et ainsi de suite.
Exemple avec le nombre 1337 :
<math>1337 = 1 \times 1000 + 3 \times 100 + 3 \times 10 + 7 \times 1</math>
Pour résumer, un nombre en décimal s'écrit comme la somme de produits, chaque produit multipliant un chiffre par une puissance de 10. On dit alors que le nombre est en base 10.
<math>1337 = 1 \times 10^3 + 3 \times 10^2 + 3 \times 10^1 + 7 \times 10^0</math>
Ce qui peut être fait avec des puissances de 10 peut être fait avec des puissances de 2, 3, 4, 125, etc : n'importe quel nombre entier strictement positif peut servir de '''base'''. En informatique, on utilise rarement la base 10 à laquelle nous sommes tant habitués. On utilise à la place deux autres bases :
* La base 2 (système binaire) : les chiffres utilisés sont 0 et 1 ;
* La base 16 (système hexadécimal) : les chiffres utilisés sont 0, 1, 2, 3, 4, 5, 6, 7, 8 et 9 ; auxquels s'ajoutent les six premières lettres de notre alphabet : A, B, C, D, E et F.
===Le système binaire===
En binaire, on compte en base 2. Cela veut dire qu'au lieu d'utiliser des puissances de 10 comme en décimal, on utilise des puissances de deux : n'importe quel nombre entier peut être écrit sous la forme d'une somme de puissances de 2. Par exemple 6 s'écrira donc 0110 en binaire : <math>0 \times 2^3 + 1 \times 2^2 + 1 \times 2^1 + 0 \times 2^0 = 6</math>. On peut remarquer que le binaire n'autorise que deux chiffres, à savoir 0 ou 1 : ces chiffres binaires sont appelés des '''bits''' (abréviation de Binary Digit). Pour simplifier, on peut dire qu'un bit est un truc qui vaut 0 ou 1. Pour résumer, tout nombre en binaire s'écrit sous la forme d'un produit entre bits et puissances de deux de la forme :
<math>a_0 \times 2^0 + a_1 \times 2^1 + a_2 \times 2^2 + a_3 \times 2^3 + a_4 \times 2^4 +...+ a_n \times 2^n</math>
Les coefficients <math>a_0, a_1, a_2</math> sont les bits, l'exposant n qui correspond à un bit <math>a_n</math> est appelé le '''poids''' du bit.
====La terminologie du binaire====
En informatique, il est rare que l'on code une information sur un seul bit. Dans la plupart des cas, l'ordinateur manipule des nombres codés sur plusieurs bits. Les informaticiens ont donné des noms aux groupes de bits suivant leur taille. Le plus connu est certainement l''''octet''', qui désigne un groupe de 8 bits. Moins connu, on parle de '''''nibble''''' pour un groupe de 4 bits (un demi-octet), de '''doublet''' pour un groupe de 16 bits (deux octets) et de '''quadruplet''' pour un groupe de 32 bits (quatre octets).
Précisons qu'en anglais, le terme ''byte'' n'est pas synonyme d'octet. En réalité, le terme ''octet'' marche aussi bien en français qu'en anglais. Quant au terme ''byte'', il désigne un concept complètement différent, que nous aborderons plus tard (c'est la plus petite unité de mémoire que le processeur peut adresser). Il a existé dans le passé des ordinateurs où le ''byte'' faisait 4, 7, 9, 16, voire 48 bits, par exemple. Il a même existé des ordinateurs où le ''byte'' faisait exactement 1 bit ! Mais sur presque tous les ordinateurs modernes, un ''byte'' fait effectivement 8 bits, ce qui fait que le terme ''byte'' est parfois utilisé en lieu et place d'''octet''. Mais c'est un abus de langage, attention aux confusions ! Dans ce cours, nous parlerons d'octet pour désigner un groupe de 8 bits, en réservant le terme ''byte'' pour sa véritable signification.
À l'intérieur d'un nombre, le '''bit de poids faible''' est celui qui est le plus à droite du nombre, alors que le '''bit de poids fort''' est celui non nul qui est placé le plus à gauche, comme illustré dans le schéma ci-dessous.
C'est le même principe que l'écriture des nombres en base décimale : le chiffre le plus significatif à gauche, et le moins significatif à droite (l'unité ou les décimales après la virgule).
[[File:Msb.svg|centre|vignette|upright=2|Bit de poids fort.]]
[[File:Lsb.svg|centre|vignette|upright=2|Bit de poids faible.]]
Cette terminologie s'applique aussi pour les bits à l'intérieur d'un octet, d'un ''nibble'', d'un doublet ou d'un quadruplet. Pour un nombre codés sur plusieurs octets, on peut aussi parler de l'octet de poids fort et de l'octet de poids faible, du doublet de poids fort ou de poids faible, etc.
====La traduction binaire→décimal====
Pour traduire un nombre binaire en décimal, il faut juste se rappeler que la position d'un bit indique par quelle puissance il faut le multiplier. Ainsi, le chiffre le plus à droite est le chiffre des unités : il doit être multiplié par 1 (<math>2^0</math>). Le chiffre situé immédiatement à gauche du chiffre des unités doit être multiplié par 2 (<math>2^1</math>). Le chiffre encore à gauche doit être multiplié par 4 (<math>2^2</math>), et ainsi de suite. Mathématiquement, on peut dire que le énième bit en partant de la droite doit être multiplié par <math>2^{n-1}</math>. Par exemple, la valeur du nombre noté 1011 en binaire est de <math>8 \times 1 + 4 \times 0 + 2 \times 1 + 1 \times 1 = 11</math>.
[[File:Value of digits in the Binary numeral system.svg|centre|vignette|upright=2|Valeur des chffres dans le système de numération binaire.]]
====La traduction décimal→binaire====
La traduction inverse, du décimal au binaire, demande d'effectuer des divisions successives par deux. Les divisions en question sont des divisions euclidiennes, avec un reste et un quotient. En lisant les restes des divisions dans un certain sens, on obtient le nombre en binaire. Voici comment il faut procéder, pour traduire le nombre 34 :
[[File:Exemple d'illustration de la méthode de conversion décimal vers binaire.png|centre|vignette|upright=2|Exemple d'illustration de la méthode de conversion décimal vers binaire.]]
====Quelques opérations en binaire====
Maintenant que l'on sait coder des nombres en binaire normal, il est utile de savoir comment faire quelques opérations usuelles en binaire. Nous utiliserons les acquis de cette section dans la suite du chapitre, bien que de manière assez marginale.
La première opération est assez spécifique au binaire. Il s'agit d'une opération qui inverse les bits d'un nombre : les 0 deviennent des 1 et réciproquement. Par exemple, le nombre 0001 1001 devient 1110 0110. Elle porte plusieurs noms : opération NOT, opération NON, complémentation, etc. Nous parlerons de '''complémentation''' ou d'opération NOT dans ce qui suit. Beaucoup d'ordinateurs gèrent cette opération, ils savent la faire en un seul calcul. Il faut dire que c'est une opération assez utile, bien que nous ne pouvons pas encore expliquer pourquoi.
[[File:Binary Addition Demonstration.svg|vignette|Exemple d'addition en binaire.]]
La seconde opération à aborder est l'addition. Elle se fait en binaire de la même manière qu'en décimal : Pour faire une addition en binaire, on additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. Sauf que l'on additionne des bits. Heureusement, la table d'addition est très simple en binaire :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
La troisième opération est une variante de l'addition appelée l''''opération XOR''', notée <math>\oplus</math>. Il s'agit d'une addition dans laquelle on ne propage pas les retenues. L'addition des deux bits des opérandes se fait normalement, mais les retenues sont simplement oubliées, on n'en tient pas compte. Le résultat est que l'addition se résume à appliquer la table d'addition précédente :
* 0 <math>\oplus</math> 0 = 0 ;
* 0 <math>\oplus</math> 1 = 1 ;
* 1 <math>\oplus</math> 0 = 1 ;
* 1 <math>\oplus</math> 1 = 0.
Pour résumer, le résultat vaut 1 si les deux bits sont différents, 0 s'ils sont identiques. L'opération XOR sera utilisée rapidement dans le chapitre suivant, quand nous parlerons rapidement du mot de parité. Et elle sera beaucoup utilisée dans la suite du cours, nous en feront fortement usage. Pour le moment, mémorisez juste cette opération, elle n'a rien de compliqué.
Une dernière opération est l'opération de '''''population count'''''. Il s'agit ni plus ni moins que de compter le nombre de bits qui sont à 1 dans un nombre. Par exemple, pour le nombre 0110 0010 1101 1110, elle donne pour résultat 9. Elle est utilisée dans certaines applications, comme le calcul de certains codes correcteurs d'erreur, comme on le verra dans le chapitre suivant. Elle est supportée sur de nombreux ordinateurs, encore que cela dépende du processeur considéré. Il s'agit cependant d'une opération assez courante, supportée par les processeurs ARM, les processeurs x86 modernes (ceux qui gèrent le SSE), et quelques autres.
===L'hexadécimal===
L’hexadécimal est basé sur le même principe que le binaire, sauf qu'il utilise les 16 chiffres suivants :
{| class="wikitable"
|- style="color:#8080ffff; text-align:center;"
! Chiffre hexadécimal
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || A || B || C || D || E || F
|- style="text-align:center;"
! Nombre décimal correspondant
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 10 || 11 || 12 || 13 || 14 || 15
|- style="text-align:center;"
! Notation binaire
| 0000 || 0001 || 0010 || 0011 || 0100 || 0101 || 0110 || 0111 || 1000 || 1001 || 1010 || 1011 || 1100 || 1101 || 1110 || 1111
|}
Dans les textes, afin de différencier les nombres décimaux des nombres hexadécimaux, les nombres hexadécimaux sont suivis par un petit h, indiqué en indice. Si cette notation n'existait pas, des nombres comme 2546 seraient ambigus : on ne saurait pas dire sans autre indication s'ils sont écrits en décimal ou en hexadécimal. Avec la notation, on sait de suite que 2546 est en décimal et que 2546h est en hexadécimal.
Dans les codes sources des programmes, la notation diffère selon le langage de programmation.
Certains supportent le suffixe <code>h</code> pour les nombres hexadécimaux, d'autres utilisent un préfixe <code>0x</code> ou <code>0h</code>.
Pour convertir un nombre hexadécimal en décimal, il suffit de multiplier chaque chiffre par la puissance de 16 qui lui est attribuée. Là encore, la position d'un chiffre indique par quelle puissance celui-ci doit être multiplié : le chiffre le plus à droite est celui des unités, le second chiffre le plus à droite doit être multiplié par 16, le troisième chiffre en partant de la droite doit être multiplié par 256 (16 * 16) et ainsi de suite. La technique pour convertir un nombre décimal vers de l’hexadécimal est similaire à celle utilisée pour traduire un nombre du décimal vers le binaire. On retrouve une suite de divisions successives, mais cette fois-ci les divisions ne sont pas des divisions par 2 : ce sont des divisions par 16.
La conversion inverse, de l'hexadécimal vers le binaire est très simple, nettement plus simple que les autres conversions. Pour passer de l'hexadécimal au binaire, il suffit de traduire chaque chiffre en sa valeur binaire, celle indiquée dans le tableau au tout début du paragraphe nommé « Hexadécimal ». Une fois cela fait, il suffit de faire le remplacement. La traduction inverse est tout aussi simple : il suffit de grouper les bits du nombre par 4, en commençant par la droite (si un groupe est incomplet, on le remplit avec des zéros). Il suffit alors de remplacer le groupe de 4 bits par le chiffre hexadécimal qui correspond.
==Interlude propédeutique : la capacité d'un entier et les débordements d'entiers==
Dans la section précédente, nous avons vu comment coder des entiers positifs en binaire ou dans des représentations proches. La logique voudrait que l'on aborde ensuite le codage des entiers négatifs. Mais nous allons déroger à cette logique simple, pour des raisons pédagogiques. Nous allons faire un interlude qui introduira des notions utiles pour la suite du chapitre. De plus, ces concepts seront abordés de nombreuses fois dans ce wikilivre et l'introduire ici est de loin la solution idéale.
===Les ordinateurs manipulent des nombres codés sur un nombre fixe de bits===
Vous avez certainement déjà entendu parler de processeurs 32 ou 64 bits. Et si vous avez joué aux jeux vidéos durant votre jeunesse et êtes assez agé, vous avez entendu parler de consoles de jeu 8 bits, 16 bits, 32 bits, voire 64 bits (pour la Jaguar, et c'était un peu trompeur). Derrière cette appellation qu'on retrouvait autrefois comme argument commercial dans la presse se cache un concept simple. Tout ordinateur manipule des nombres entiers dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Une console 16 bits manipulait des entiers codés en binaire sur 16 bits, pas un de plus, pas un de moins. Pareil pour les anciens ordinateurs 32 bits, qui manipulaient des nombres entiers codés sur 32 bits.
Aujourd'hui, les ordinateurs modernes utilisent presque un nombre de bits qui est une puissance de 2 : 8, 16, 32, 64, 128, 256, voire 512 bits. Mais cette règle souffre évidemment d'exceptions. Aux tout débuts de l'informatique, certaines machines utilisaient 3, 7, 13, 17, 23, 36 et 48 bits ; mais elles sont aujourd'hui tombées en désuétude. De nos jours, il ne reste que les processeurs dédiés au traitement de signal audio, que l'on trouve dans les chaînes HIFI, les décodeurs TNT, les lecteurs DVD, etc. Ceux-ci utilisent des nombres entiers de 24 bits, car l'information audio est souvent codée par des nombres de 24 bits.
Anecdote amusante, il a existé des ordinateurs de 1 bit, qui sont capables de manipuler des nombres codés sur 1 bit, pas plus. Un exemple est le Motorola MC14500B, commercialisé de 1976.
====Le lien entre nombre de bits et valeurs codables====
Évidemment, on ne peut pas coder tous les entiers possibles et imaginables avec seulement 8 bits, ou 16 bits. Et il parait intuitif que l'on ait plus de valeurs codables sur 16 bits qu'avec 8 bits, par exemple. Plus le nombre de bits est important, plus on pourra coder de valeurs entières différentes. Mais combien de plus ? Par exemple, si je passe de 8 bits à 16 bits, est-ce que le nombre de valeurs que l'on peut coder double, quadruple, pentuple ? De même, combien de valeurs différentes on peut coder avec <math>n</math> bits. Par exemple, combien de nombres différents peut-on coder avec 4, 8 ou 16 bits ? La section précédente vous l'expliquer.
Avec <math>n</math> bits, on peut coder <math>2^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>2^n - 1</math>. N'oubliez pas cette formule : elle sera assez utile dans la suite de ce tutoriel. Pour exemple, on peut coder 16 valeurs avec 4 bits, qui vont de 0 à 15. De même, on peut coder 256 valeurs avec un octet, qui vont de 0 à 255. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de bits
! Nombre de valeurs codables
|-
! 4
| 16
|-
! 8
| 256
|-
! 16
| 65 536
|-
! 32
| 4 294 967 296
|-
! 64
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien de bits il faut pour coder une valeur quelconque, que nous noterons N. Pour cela, il faut utiliser la formule précédente, mais à l'envers. On cherche alors <math>n</math> tel que <math>N = 2^n</math>. L'opération qui donne <math>n</math> est appelée le logarithme, et plus précisément un logarithme en base 2, noté <math>\log_2{(X)}</math>. Le problème est que le résultat du logarithme ne tombe juste que si le nombre X est une puissance de 2. Si ce n'est pas le cas, le résultat est un nombre à virgule, ce qui n'a pas de sens pratique. Par exemple, la formule nous dit que pour coder le nombre 13, on a besoin de 3,70043971814 bits, ce qui est impossible. Pour que le résultat ait un sens, il faut arrondir à l'entier supérieur. Pour l'exemple précédent, les 3,70043971814 bits s'arrondissent en 4 bits.
====Le lien entre nombre de chiffres hexadécimaux et valeurs codables====
Maintenant, voyons combien de valeurs peut-on coder avec <math>n</math> chiffres hexadécimaux. La réponse n'est pas très différente de celle obtenue en binaire, si ce n'est qu'il faut remplacer le 2 par un 16 dans la formule précédente. Avec <math>n</math> chiffres hexadécimaux, on peut coder <math>16^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>16^n - 1</math>. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de chiffres héxadécimaux
! Nombre de valeurs codables
|-
! 1 (4 bits)
| 16
|-
! 2 (8 bits)
| 256
|-
! 4 (16 bits)
| 65 536
|-
! 8 (32 bits)
| 4 294 967 296
|-
! 16 (64 bits)
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien faut-il de chiffres hexadécimaux pour coder une valeur quelconque en hexadécimal. La formule est là encore la même qu'en binaire, sauf qu'on remplace le 2 par un 16. Pour trouver le nombre de chiffres hexadécimaux pour encoder un nombre X, il faut calculer <math>\log_{16}{(X)}</math>. Notons que le logarithme utilisé est un logarithme en base 16, et non un logarithme en base 2, comme pour le binaire. Là encore, le résultat ne tombe juste que si le nombre X est une puissance de 16 et il faut arrondir à l'entier supérieur si ce n'est pas le cas.
Une propriété mathématique des logarithmes nous dit que l'on peut passer d'un logarithme en base X et à un logarithme en base Y avec une simple division, en utilisant la formule suivante :
: <math>\log_X{(N)} = {\log_{Y}{(N)} \over \log_Y(X)}</math>
Dans le cas qui nous intéresse, on a Y = 2 et X = 16, ce qui donne :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over \log_2(16)}</math>
Or, <math>\log_2(16)</math> est tout simplement égal à 4, car il faut 4 bits pour coder la valeur 16. On a donc :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over 4}</math>
En clair, il faut quatre fois moins de chiffres hexadécimaux que de bits, ce qui est assez intuitif vu qu'il faut 4 bits pour coder un chiffre hexadécimal.
===Les débordements d'entier===
On vient de voir que tout ordinateur manipule des nombres dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Et cela limite les valeurs qu'il peut encoder, qui sont comprises dans un intervalle bien précis. Mais que ce passe-t-il si jamais le résultat d'un calcul ne rentre pas dans cet intervalle ? Par exemple, pour du binaire normal, que faire si le résultat d'un calcul atteint ou dépasse <math>2^n</math> ? Dans ce cas, le résultat ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
On peut imaginer d'autres codages pour lesquels les entiers ne commencent pas à zéro ou ne terminent pas à <math>2^n - 1</math>. On peut prendre le cas où l'ordinateur gère les nombres négatifs, par exemple. Dans le cas général, l'ordinateur peut coder les valeurs comprises dans un intervalle, qui va de la valeur la plus basse <math>N_\text{min}</math> à la valeur la plus grande <math>N_\text{max}</math>. Et encore une fois, si un résultat de calcul sort de cet intervalle, on fait face à un débordement d'entier.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math>, avec <math>N_\text{max}</math> la plus grande valeur codable par l'ordinateur.
On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur basse de débordement vaut <math>N_\text{min} - 1</math>, avec <math>N_\text{min}</math> la plus petite valeur codable par l'ordinateur.
===La gestion des débordements d'entiers===
Face à un débordement d'entier, l'ordinateur peut utiliser deux méthodes : l'arithmétique saturée ou l'arithmétique modulaire.
L''''arithmétique saturée''' consiste à arrondir le résultat pour prendre la plus grande ou la plus petite valeur. Si le résultat d'un calcul dépasse la valeur haute de débordement, le résultat est remplacé par le plus grand entier supporté par l'ordinateur. La même chose est possible quand le résultat est inférieur à la plus petite valeur possible, par exemple lors d'une soustraction : l'ordinateur arrondit au plus petit entier possible.
Pour donner un exemple, voici ce que cela donne avec des entiers codés sur 4 bits, qui codent des nombres de 0 à 15. Si je fais le calcul 8 + 9, le résultat normal vaut 17, ce qui ne rentre pas dans l'intervalle. Le résultat est alors arrondi à 15. Inversement, si je fais le calcul 8 - 9, le résultat sera de -1, ce qui ne rentre pas dans l'intervalle : le résultat est alors arrondi à 0.
[[File:Odometer rollover.jpg|vignette|Exemple de débordement d'entier sur un compteur mécanique quelconque. L'image montre clairement que le compteur revient à zéro une fois la valeur maximale dépassée.]]
L''''arithmétique modulaire''' est plus compliquée et c'est elle qui va nous intéresser dans ce qui suit. Pour simplifier, imaginons que l'on décompte à partir de zéro. Quand on arrive à la valeur haute de débordement, on recommence à compter à partir de zéro. L'arithmétique modulaire n'est pas si contre-intuitive et vous l'utilisez sans doute au quotidien. Après tout, c'est comme cela que l'on compte les heures, les minutes et les secondes. Quand on compte les minutes, on revient à 0 au bout de 60 minutes. Pareil pour les heures : on revient à zéro quand on arrive à 24 heures. Divers compteurs mécaniques fonctionnent sur le même principe et reviennent à zéro quand ils dépassent la plus grande valeur possible, l'image ci-contre en montrant un exemple.
Mathématiquement, l'arithmétique modulaire implique des divisions euclidiennes, celles qui donnent un quotient et un reste. Lors d'un débordement, le résultat s'obtient comme suit : on divise le nombre qui déborde par la valeur haute de débordement, et que l'on conserve le reste de la division. Au passage, l'opération qui consiste à faire une division et à garder le reste au lieu du quotient est appelée le ''modulo''. Prenons l'exemple où l'ordinateur peut coder tous les nombres entre 0 et 1023, soit une valeur haute de débordement de 1024. Pour coder le nombre 4563, on fait le calcul 4563 / 1024. On obtient : <math>4563 = 4 \times 1024 + 467</math>. Le reste de la division est de 467 et c'est lui qui sera utilisé pour coder la valeur de départ, 4563. Le nombre 4563 est donc codé par la valeur 467 dans un tel ordinateur en arithmétique modulaire. Au passage, la valeur haute de débordement est toujours codée par un zéro dans ce genre d'arithmétique.
Les ordinateurs utilisent le plus souvent une valeur haute de débordement de <math>2^n</math>, avec n le nombre de bits utilisé pour coder les nombres entiers positifs. En faisant cela, l'opération modulo devient très simple et revient à éliminer les bits de poids forts au-delà du énième bit. Par exemple, reprenons l'exemple d'un ordinateur qui code ses nombres sur 4 bits. Imaginons qu'il fasse le calcul <math>13 + 3</math>, soit 1101 + 0011 = 1 0000 en binaire. Le résultat entraîne un débordement d'entier et l'ordinateur ne conserve que les 4 bits de poids faible. Cela donne : 1101 + 0011 = 0000. Comme autre exemple l'addition 1111 + 0010 ne donnera pas 17 (1 0001), mais 1 (0001).
L'avantage est que les calculs sont beaucoup plus simples avec cette méthode qu'avec les autres. L'ordinateur a juste à ne pas calculer les bits de poids fort. Pas besoin de faire une division pour calculer un modulo, pas besoin de corriger le résultat pour faire de l'arithmétique saturée.
===Aparté : quelques valeurs particulières en binaire===
Plus haut, on a dit qu'avec n bits, on peut encoder toutes les valeurs allant de <math>0</math> à <math>2^n - 1</math>. Ce simple fait permet de déterminer quelle est la valeur de certains entiers à vue d’œil. Dans ce qui va suivre, nous allons poser quelques bases que nous réutiliserons dans la suite du chapitre. Il s'agit de quelques trivias qui sont cependant assez utiles.
Le premier trivia concerne la valeur maximale <math>2^n - 1</math> : elle est encodée par un nombre dont tous les bits sont à 1.
Le deuxième trivia concerne les nombres de la forme 000...000 1111 1111, à savoir des nombres dont les x bits de poids faible sont à 1 et tous les autres valent 0. Par définition, de tels nombres codent la plus grande valeur possible sur x bits, ce qui fait qu'ils valent <math>2^x - 1</math>.
Le troisième trivia concerne les nombres de la forme 11111...0000. En clair, des nombres où on a une suite consécutive de 1 dans les bits de poids fort et les x bits de poids faibles à 0. De tels nombres valent : <math>2^n - 2^x</math>. La preuve est assez simple : ils s'obtiennent en prenant la valeur maximale <math>2^n - 1</math>, et en soustrayant un nombre de la forme <math>2^x - 1</math>.
[[File:Valeurs particulières en binaire.png|centre|vignette|upright=2|Valeurs particulières en binaire]]
Si on utilise l'arithmétique modulaire, la valeur <math>2^n</math> n'est autre que la valeur de débordement haute pour les nombres stockés sur n bits, et se code comme le zéro (il faudrait 1 bit de plus pour stocker le 1 de poids fort de <math>2^n</math>). Les 1er et 3ème nombres évoqués dans les paragraphes précédents peuvent donc se passer de <math>2^n</math> dans leur expression :
* <math>- 1</math> est codé 1111111111111111...111 (n bits à 1)
* <math>- 2^x</math> est codé 11111...111000..000000 (x bits à 0 précédés de n-x bits à 1)
==Les nombres entiers négatifs==
<noinclude>[[File:Binary Representations.svg|vignette|Représentations signées, exemples sur 4 bits.]]</noinclude>
Passons maintenant aux entiers négatifs en binaire : comment représenter le signe moins ("-") avec des 0 et des 1 ? Eh bien, il existe plusieurs méthodes :
* la représentation en signe-valeur absolue ;
* la représentation en complément à un ;
* la représentation en complément à deux;
* la représentation par excès ;
* la représentation dans une base négative ;
* d'autres représentations encore moins utilisées que les autres.
===La représentation en signe-valeur absolue===
La solution la plus simple pour représenter un entier négatif consiste à coder sa valeur absolue en binaire, et rajouter un bit qui précise si c'est un entier positif ou un entier négatif. Par convention, ce bit de signe vaut 0 si le nombre est positif et 1 s'il est négatif. On parle alors de représentation en signe-valeur absolue, aussi appelée '''représentation en signe-magnitude'''.
Avec cette technique, il y autant de nombres positifs que négatifs. Mieux : pour chaque nombre représentable en représentation signe-valeur absolue, son inverse l'est aussi. Ce qui fait qu'avec cette méthode, le zéro est codé deux fois : on a un -0, et un +0. Cela pose des problèmes lorsqu'on demande à notre ordinateur d'effectuer des calculs ou des comparaisons avec zéro.
[[File:Codage sur 4 bits en signe-valeur absolue.png|centre|vignette|upright=2|Codage sur 4 bits en signe-valeur absolue]]
===La représentation en complément à un===
La représentation en '''complément à un''' peut être vue, en première approximation, comme une variante de la représentation en signe-magnitude. Et je dis bien : "en première approximation", car il y a beaucoup à dire sur la représentation en complément à un, mais nous verrons cela dans la section suivante sur le complément à deux. Conceptuellement, la représentation en signe-magnitude et celle du complément à un sont drastiquement différentes et sont basées sur des concepts mathématiques totalement différents. Mais elles ont des ressemblances de surface, qui font que faire la comparaison entre les deux est assez utile pédagogiquement parlant.
En complément à un, les nombres sont codés en utilisant un bit de signe qui indique si le nombre de positif ou négatif, couplé à une valeur absolue. Par contre, la valeur absolue est codée différemment en binaire. Pour les nombres positifs, la valeur absolue est codée en binaire normal, pas de changement comparé aux autres représentations. Mais pour les valeurs négatives, la valeur absolue est codée en binaire normal, puis tous les bits sont inversés : les 0 deviennent des 1 et réciproquement. En clair, on utilise une opération de complémentation pour les nombres négatifs.
Prenons un exemple avec un nombre codé sur 4 bits, avec un cinquième bit de signe. La valeur 5 est codée comme suit : 0 pour le bit de signe, 5 donne 0101 en binaire, le résultat est 0 0101. Pour la valeur -5, le bit de signe est 1, 5 donne 0101 en binaire, on inverse les bits ce qui donne 1010 : cela donne 11010. Notez qu'on peut passer d'un résultat à l'autre avec une opération de complémentation, à savoir en inversant les bits de l'autre et réciproquement.
Il s'agit là d'une propriété générale avec le complément à 1 : l'opposé d'un nombre, à savoir passer de sa valeur positive à sa valeur négative ou inversement, se calcule avec une opération NOT, une opération de complémentation (d'où son nom). L'avantage est que les ordinateurs gèrent naturellement d'opération de complémentation. Par contre, en signe-magnitude, inverser le bit de signe est une opération spécifique et ne sert qu'à ça. Il faut donc rajouter une opération en plus pour calculer l'opposé d'un nombre.
La représentation en complément à un garde les défauts de la représentation en signe-magnitude. Le zéro est codé deux fois, avec un zéro positif et un zéro négatif. La différence est que les valeurs négatives sont dans l'ordre inverse, il y a une symétrie un peu meilleure. Les deux zéros sont d'ailleurs totalement éloignés, ils correspondent aux valeurs extrêmes encodables : le zéro positif a tous ses bits à 0, le zéro négatif a tous ses bits à 1. Le résultat est que l'implémentation des comparaisons et de certains calculs est plus complexe qu'avec le signe-magnitude, mais de peu.
[[File:Codage sur 4 bits en complément à 1.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 1.]]
===La représentation par excès===
La représentation par excès consiste à ajouter un biais aux nombres à encoder afin de les encoder par un entier positif. Pour encoder tous les nombres compris entre -X et Y en représentation par excès, il suffit de prendre la valeur du nombre à encoder, et de lui ajouter un biais égal à X. Ainsi, la valeur -X sera encodée par zéro, et toutes les autres valeurs le seront par un entier positif, le zéro sera encodé par X, 1, par X+1, etc. Par exemple, prenons des nombres compris entre -127 et 128. On va devoir ajouter un biais égal à 127, ce qui donne :
{|class="wikitable"
|-
!Valeur avant encodage!!Valeur après encodage
|-
||-127||0
|-
||-126||1
|-
||-125||2
|-
||…||…
|-
||0||127
|-
||…||…
|-
||127||254
|-
||128||255
|}
===La représentation en complément à deux===
La représentation en '''complément à deux''' est basée sur les mêmes mathématiques que le complément à un, mais fonctionne très différemment en pratique.
Si on regarde de loin, son principe est assez différent : il n'y a pas de bit de signe ni quoi que ce soit d'autre. À la place, un nombre en complément à deux est encodé comme en binaire normal, à un point près : le bit de poids fort est soustrait, et non additionné aux autres. Il a une valeur négative : on soustrait la puissance de deux associée s'il vaut 1, on ne la tient pas en compte s'il vaut 0.
Par exemple, la valeur du nombre noté 111001 en complément à deux s'obtient comme suit :
{|class="wikitable" style="margin:auto;"
|-
! -32 !! 16 !! 8 !! 4 !! 2 !! 1
|-
| 1 || 1 || 1 || 0 || 0 || 1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(8×1)+(4×0)+(2×1)+(1×1) = −32+16+8+1 = -7.
: ''Avec le complément à deux, comme avec le complément à un, le bit de poids fort vaut 0 pour les nombres positifs et 1 pour les négatifs.''
L'avantage de cette représentation est qu'elle n'a pas de double zéro : le zéro n'est encodé que par une seule valeur. Par contre, la valeur autrefois prise par le zéro négatif est réutilisée pour encoder une valeur négative. La conséquence est que l'on a un nombre négatif en plus d'encodé, il n'y a plus le même nombre de valeurs strictement positives et de valeurs négatives encodées. Le nombre négatif en question est appelé le '''nombre le plus négatif''', ce nom trahit le fait que c'est celui qui a la plus petite valeur (la plus grande valeur absolue). Il est impossible de coder l'entier positif associé, sa valeur absolue.
[[File:Codage sur 4 bits en complément à 2.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 2.]]
====Le calcul du complément à deux : première méthode====
Les représentations en complément à un et en complément à deux sont basées sur le même principe mathématique. Leur idée est de remplacer chaque nombre négatif par un nombre positif équivalent, appelé le ''complément''. Par équivalent, on veut dire que tout calcul donne le même résultat si on remplace un nombre négatif par son complément (idem avec un nombre positif, son complément aura un signe inverse). Et pour faire cela, elles se basent sur les débordements d'entier pour fonctionner, et plus précisément sur l'arithmétique modulaire abordée plus haut.
Si on fait les calculs avec le complément, les résultats du calcul entraînent un débordement d'entier, qui sera résolu par un '''modulo''' : l'ordinateur ne conserve que les bits de poids faible du résultat, les autres bits sont oubliés. Par exemple, prenons l'addition 15 + 2, 1111 + 0010 en binaire : le résultat ne sera pas 17 (10001), vu qu'on n'a pas assez de bits pour encoder le résultat, mais 1 (0001). Et le résultat après modulo sera identique au résultat qu'on aurait obtenu avec le nombre négatif sans modulo. En clair, c'est la gestion des débordements qui permet de corriger le résultat de manière à ce que l'opération avec le complément donne le même résultat qu'avec le nombre négatif voulu. Ainsi, on peut coder un nombre négatif en utilisant son complément positif.
: Cela ressemble beaucoup à la méthode de soustraction basée sur un complément à 9 pour ceux qui connaissent, sauf que c'est une version binaire qui nous intéresse ici.
Prenons un exemple, qui permettra d'introduire la suite. Encore une fois, on utilise un codage sur 4 bits dont la valeur haute de débordement est de 16. Prenons l'addition de 13 + 3 = 16. Avec l'arithmétique modulaire, 16 est équivalent à 0, ce qui donne : 13 + 3 = 0 ! On peut aussi reformuler en disant que 13 = -3, ou encore que 3 = -13. Dit autrement, 3 est le complément de -13 pour ce codage. Et ne croyez pas que ça marche uniquement dans cet exemple : cela se généralise assez rapidement à tout nombre négatif.
Prenons un nombre N dont un veut calculer le complément à deux K. Dans le cas général, on a :
: <math>N + K = 2^n</math>, vu que <math>2^n = 0</math> en utilisant le modulo.
En réorganisant très légèrement les termes pour isoler K, on a :
: <math>K = 2^n - N</math>
La formule précédent permet de calculer la complément à deux assez simplement, en faisant le calcul à la main.
Avant de poursuivre, prenons un exemple très intéressant : le cas où N = -1. Son complément à deux vaut donc :
: <math>K = 2^n - 1</math>
Le terme <math>2^n - 1</math> devrait vous rappeler quelque chose : il s'agit d'un nombre dont tous les bits sont à 1. En clair, le complément à deux de -1 est un nombre de la forme 1111...111. Et cela vaut quel que soit le nombre de bits n. La représentation de -1 est similaire, peu importe que l'on utilise des nombres de 16 bits, 32 bits, 64 bits, etc.
Voyons maintenant le cas des puissance de deux, par exemple 2, 4, 8, 16, etc. Leur complément à deux vaut :
: <math>K = 2^n - 2^x</math>
Nous avions vu précédemment dans le chapitre que ces nombres sont de la forme 11111...0000. En clair, des nombres dont les x bits de poids faible sont à 0, et tous les autres bits sont à 1. Par exemple, le complément à deux de -2 est un nombre dont les bits sont à 1, sauf le bit de poids faible. De même, le complément à deux de -4 a tous ses bits à 1, sauf les deux bits de poids faible. Et le complément à deux de -8 a tous ses bits à 1, sauf les trois bits de poids faible. Pour résumer, tout nombre de la forme <math>- 2^x</math> a tous ses bits à 1, sauf les x bits de poids faible qui sont à 0.
{|class="wikitable"
|+ Exemple avec des nombres de 8 bits
|-
! -1
| 1111 1111
|-
! -2
| 1111 1110
|-
! -4
| 1111 1100
|-
! -8
| 1111 1000
|-
! -16
| 1111 0000
|-
! -32
| 1110 0000
|-
! -64
| 1100 0000
|-
! -128
| 1000 0000
|-
! 0 / - 256
| 0000 0000
|}
====Le calcul du complément à deux : seconde méthode====
Il existe une seconde méthode pour calculer le complément à deux d'un nombre, la voici. Pour les nombres positifs, encodez-les comme en binaire normale. Pour les nombres négatifs, faites pareil, puis inversez tous les bits, avant d'ajouter un. La procédure est identique à celle du complément à un, sauf que l'on incrémente le résultat final. Et ce n'est pas une coïncidence, comme nous allons le voir immédiatement.
Pour comprendre pourquoi la méthode marche, repartons de la formule précédente <math>K = 2^n - N</math>. Elle peut se reformuler comme suit :
: <math>K = (2^n - 1 + 1) - N</math>
: <math>K = (2^n - 1) - N + 1</math>
La valeur <math>(2^n - 1)</math> est par définition un nombre dont tous les bits sont à 1. À cette valeur, on soustrait un nombre <math>|N|</math> dont certains bits sont à 1 et d'autres à 0. En clair, pour chaque colonne, on a deux possibilités : soit on doit faire la soustraction <math>1-1</math>, soit la soustraction <math>1-0</math>. Or, les règles de l'arithmétique binaire disent que <math>1-1=0</math> et <math>1-0=1</math>. En regardant attentivement, on se rend compte que le bit du résultat est l'inverse du bit de départ. De plus, les deux cas ne donnent pas de retenue : le calcul pour chaque bit n'influence pas les bits voisins.
Le terme <math>(2^n - 1) - N</math> est donc le complément à un du nombre N, un nombre égal à sa représentation en binaire dont tous les bits sont inversés. Notons <math>\overline{|N|}</math> le nombre formé en inversant tous les bits de N. On a alors :
: <math>K = \overline{N} + 1</math>
En clair, le complément à deux s'obtient en prenant le complément à un et en ajoutant 1. Dit autrement, il faut prendre le nombre N, en inverser tous les bits et ajouter 1.
Une autre manière équivalente consiste à faire le calcul suivant :
: <math>K = \overline{N - 1}</math>
On prend le nombre dont on veut le complément, on soustrait 1 et on inverse les bits.
: Notons que tout ce qui a été dit plus haut marche aussi pour le complément à un, avec cependant une petite différence : la valeur haute de débordement n'est pas la même, ce qui change les calculs. Pour des nombres codés sur <math>n</math> bits, la valeur haute de débordement est égale à <math>2^n</math> en complément à deux, alors qu'elle est de <math>2^n - 1</math> en complément à un. De ce fait, la gestion des débordements est plus simple en complément à deux.
====L'extension de signe====
Dans les ordinateurs, tous les nombres sont codés sur un nombre fixé et constant de bits. Ainsi, les circuits d'un ordinateur ne peuvent manipuler que des nombres de 4, 8, 12, 16, 32, 48, 64 bits, suivant la machine. Si l'on veut utiliser un entier codé sur 16 bits et que l'ordinateur ne peut manipuler que des nombres de 32 bits, il faut bien trouver un moyen de convertir le nombre de 16 bits en un nombre de 32 bits, sans changer sa valeur et en conservant son signe. Cette conversion d'un entier en un entier plus grand, qui conserve valeur et signe s'appelle l''''extension de signe'''.
L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
L'explication plus simple tient dans la manière de coder le bit de poids fort. Prenons l'exemple de la conversion d'un entier de 5 bits en un entier de 8bits. Les 4 bits de poids faible ont un poids positif (on les additionne), alors que le bit de poids fort a un poids négatif. Le nombre encodé vaut : <math>\text{Bit de poids fort} \times -16 + \text{Valeur encodée sur les 4 bits de poids faible}</math>. La valeur encodée sur 4 bits reste la même après extension de signe, car les poids des bits de poids faible ne changent pas. Par contre, le bit de poids fort change. Sur 8 bits, la valeur -16 est encodée par un nombre de la forme 1111 0000. En remplaçant le bit de poids fort par sa valeur calculée sur plus de bits, on remarque que les bits de poids fort ont été remplacés par des 1.
===La représentation négabinaire===
Enfin, il existe une dernière méthode, assez simple à comprendre, appelée '''représentation négabinaire'''. Dans cette méthode, les nombres sont codés non en base 2, mais en base -2. Oui, vous avez bien lu : la base est un nombre négatif. Dans les faits, la base -2 est similaire à la base 2 : il y a toujours deux chiffres (0 et 1), et la position dans un chiffre indique toujours par quelle puissance de 2 il faut multiplier, sauf qu'il faudra ajouter un signe moins une fois sur 2. Concrètement, les puissances de -2 sont les suivantes : 1, -2, 4, -8, 16, -32, 64, etc. En effet, un nombre négatif multiplié par un nombre négatif donne un nombre positif, ce qui fait qu'une puissance sur deux est négative, alors que les autres sont positives. Ainsi, on peut représenter des nombres négatifs, mais aussi des nombres positifs dans une puissance négative.
Par exemple, la valeur du nombre noté 11011 en base -2 s'obtient comme suit :
{|class="wikitable"
|-
!…
!-32
!16
!-8
!4
!-2
!1
|-
|…
|1
|1
|1
|0
|1
|1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(−8×1)+(4×0)+(−2×1)+(1×1)=−32+16−8−2+1=−25.
==Les nombres à virgule fixe==
On sait donc comment sont stockés nos nombres entiers dans un ordinateur. Néanmoins, les nombres entiers ne sont pas les seuls nombres que l'on utilise au quotidien : il nous arrive d'en utiliser à virgule. Notre ordinateur n'est pas en reste : il est lui aussi capable de manipuler de tels nombres. Dans les grandes lignes, il peut utiliser deux méthodes pour coder des nombres à virgule en binaire : La virgule fixe et la virgule flottante. Parlons d'abord de la virgule fixe, plus simple à comprendre.
===La virgule fixe : des entiers cachés===
La méthode de la virgule fixe consiste à émuler les nombres à virgule à partir de nombres entiers. Un nombre à virgule fixe est codé par un nombre entier proportionnel au nombre à virgule fixe. Pour obtenir la valeur de notre nombre à virgule fixe, il suffit de diviser l'entier servant à le représenter par le facteur de proportionnalité. Par exemple, pour coder 1,23 en virgule fixe, on peut choisir comme « facteur de conversion » 1000, ce qui donne l'entier 1230.
Généralement, les informaticiens utilisent une puissance de deux comme facteur de conversion, pour simplifier les calculs. En faisant cela, on peut écrire les nombres en binaire et les traduire en décimal facilement. Pour l'exemple, cela permet d'écrire des nombres à virgule en binaire comme ceci : 1011101,1011001. Et ces nombres peuvent se traduire en décimal avec la même méthode que des nombres entier, modulo une petite différence. Comme pour les chiffres situés à gauche de la virgule, chaque bit situé à droite de la virgule doit être multiplié par la puissance de deux adéquate. La différence, c'est que les chiffres situés à droite de la virgule sont multipliés par une puissance négative de deux, c'est à dire par <math>1 \over 2</math>, <math>1 \over 4</math>, <math>1 \over 8</math>, <math>1 \over 16</math>, <math>1 \over 32</math>, ...
[[File:Fixed-point binary representation.svg|centre|vignette|upright=2.5|Nombres à virgule fixe.]]
Cette méthode est assez peu utilisée de nos jours, quoiqu'elle puisse avoir quelques rares applications relativement connue. Un bon exemple est celui des banques : les sommes d'argent déposées sur les comptes ou transférées sont codés en virgule fixe. Les sommes manipulées par les ordinateurs ne sont pas exprimées en euros, mais en centimes d'euros. Et c'est une forme de codage en virgule fixe dont le facteur de conversion est égal à 100. La raison de ce choix est que les autres méthodes de codage des nombres à virgule peuvent donner des résultats imprécis : il se peut que les résultats doivent être tronqués ou arrondis, suivant les opérandes. Cela n'arrive jamais en virgule fixe, du moins quand on se limite aux additions et soustractions.
===Les nombres à virgule non-représentables en binaire===
Quel que soit la façon de représenter les nombres à virgules, il existe des nombres qui ne peuvent être représentés de manière exacte, à savoir avec un nombre fini de décimales après la virgule. En soi, ce n'est pas spécifique au binaire, on a la même chose en décimal. Par exemple, la fraction 1/3 en décimal s'écrit 0.3333333..., avec une infinité de 3. La même chose existe en binaire, mais pour des nombres différents.
Déjà, évacuons le cas des nombres irrationnels, à savoir les nombres qui ne peuvent pas s'écrire sous la forme d'une fraction, comme <math>pi</math> ou <math>e</math>. Ils ont une infinité de décimales que ce soit en binaire, en décimal, en hexadécimal, ou autre. Ils ne sont pas représentables avec un nombre fini de décimales quelle que soit la base utilisée. Concentrons-nous sur des nombres qui ne sont pas dans ce cas, et qui ont un nombre fini ou infini de décimales.
Le passage de la base 10 à la base 2 change le nombre de décimales, et peut faire passer d'un nombre fini de décimales à un nombre infini. Par exemple, <math>\frac{1}{5} = 0.2</math> est représenté en binaire avec une séquence infinie de bits : <math>0.001100110011...</math>. Les nombres étant en base binaire et représenté avec un nombre limité de bits, il existe certains nombres décimaux triviaux qui ne sont pas représentables avec un nombre fini de décimales.
Et la réciproque n'est pas vraie : tout nombre binaire avec un nombre fini de décimales en binaire est représentable avec un nombre fini de décimales en base 10. Ceci est lié à la décomposition en facteur premier des bases utilisées :
* <math>2 = 2^1</math>
* <math>10 = 2^1 \times 5^1</math>
Le nombre 10 possède tous les facteurs premiers de 2, mais 2 n'a pas de 5 dans sa décomposition.
==Les nombres flottants==
Les nombres à virgule fixe ont aujourd'hui été remplacés par les '''nombres à virgule flottante''', où le nombre de chiffres après la virgule est variable. Le codage d'un nombre flottant est basée sur son ''écriture scientifique''. Pour rappel, en décimal, l’écriture scientifique d'un nombre consiste à écrire celui-ci comme un produit entre un nombre <math>a</math> et une puissance de 10. Ce qui donne :
: <math>a \times 10^e</math>, avec <math>a \in [1, 10 [</math>
Le nombre <math>a</math> est appelé le ''significande'' et il est compris entre 1 (inclus) et 10 (exclu). Cette contrainte garantit que l'écriture scientifique d'un nombre est unique, qu'il n'y a qu'une seule façon d'écrire un nombre en notation scientifique. Pour cela, on impose le nombre de chiffre à gauche de la virgule et le plus simple est que celui-ci soit égal à 1. Mais il faut aussi que celui-ci ne soit pas nul. En effet, si on autorise de mettre un 0 à gauche de la virgule, il y a plusieurs manières équivalentes d'écrire un nombre. Ces deux contraintes font que le significande doit être égal ou plus grand que 1, mais strictement inférieur à 10. Par contre, on peut mettre autant de décimales que l'on veut.
En binaire, c'est la même chose, mais avec une puissance de deux. Cela implique de modifier la puissance utilisée : au lieu d'utiliser une puissance de 10, on utilise une puissance de 2.
: <math>a \times 2^e</math>, avec <math>a \in [1, 2 [</math>
Le significande est aussi altéré, au même titre que la puissance, même si les contraintes sont similaires à celles en base 10. En effet, le nombre <math>a</math> ne possède toujours qu'un seul chiffre à gauche de la virgule, comme en base 10. Vu que seuls deux chiffres sont possibles (0 et 1) en binaire, on s'attend à ce que le chiffre situé à gauche de la virgule soit un zéro ou un 1. Mais rappelons que le chiffre à gauche doit être non-nul, pour les mêmes raisons qu'en décimal. En clair, le significande a forcément un bit à 1 à gauche de la virgule. Pour récapituler, l'écriture scientifique binaire d'un nombre consiste à écrire celui-ci sous la forme :
: <math>1,m \times 2^e</math>, avec <math>m \in [0, 1 [</math>
La partie fractionnaire du nombre <math>a</math>, qu'on appelle la '''mantisse'''.
[[File:Mantissa.svg|centre|vignette|Écriture scientifique (anglais).]]
Pour déterminer l'écriture scientifique en binaire d'un nombre quelconque, la procédure dépend de la valeur du nombre en question. Tout dépend s'il est dans l'intervalle <math>[1, 2 [</math>, au-delà de 2 ou en-dessous de 1.
* Pour un nombre entre 1 (inclus) et 2 (exclu), il suffit de le traduire en binaire. Son exposant est 0.
* Pour un nombre au-delà de 2, il faut le diviser par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant est alors le nombre de fois qu'il a fallu diviser par 2.
* Pour un nombre plus petit que 1, il faut le multiplier par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant se calcule en prenant le nombre de fois qu'il a fallu multiplier par 2, et en prenant l'opposé (en mettant un signe - devant le résultat).
===Le codage des nombres flottants et la norme IEEE 754===
Pour coder cette écriture scientifique avec des nombres, l'idée la plus simple est d'utiliser trois nombres, pour coder respectivement la mantisse, l'exposant et un bit de signe. Coder la mantisse implique que le bit à gauche de la virgule vaut toujours 1, mais nous verrons qu'il y a quelques rares exceptions à cette règle.
Quelques nombres flottants spécialisés, les dénormaux, ne sont pas codés en respectant les règles pour le significande et ont un 0 à gauche de la virgule. Un bon exemple est tout simplement la valeur zéro, que l'on peut coder en virgule flottante, mais seulement en passant outre les règles sur le significande.
Toujours est-il que le bit à gauche de la virgule n'est pas codé, que ce soit pour les flottants normaux ou les fameux dénormaux qui font exception. On verra que ce bit peut se déduire en fonction de l'exposant utilisé pour encoder le nombre à virgule, ce qui lui vaut le nom de '''bit implicite'''. L'exposant peut être aussi bien positif que négatif (pour permettre de coder des nombres très petits), et est encodé en représentation par excès sur n bits avec un biais égal à <math>2^{n-1} - 1</math>.
[[File:IEEE754 Format General.png|centre|vignette|upright=2|IEEE754 Format Général]]
Le standard pour le codage des nombres à virgule flottante est la '''norme IEEE 754'''. Cette norme va (entre autres) définir quatre types de flottants différents, qui pourront stocker plus ou moins de valeurs différentes.
{|class="wikitable"
|-
!Classe de nombre flottant
!Nombre de bits utilisés pour coder un flottant
!Nombre de bits de l'exposant
!Nombre de bits pour la mantisse
!Décalage
|-
!Simple précision
| 32
| 8
| 23
| 127
|-
!Double précision
| 64
| 11
| 52
| 1023
|-
!Double précision étendue
| 80 ou plus
| 15 ou plus
| 64 ou plus
| 16383 ou plus
|}
Voici à peu-près comment les nombres flottants sont répartis sur une ligne numérique. Vous remarquerez que les flottants sont de plus en plus éloignés au fur et à mesure qu'on s'éloigne de zéro. La raison est l'encodage avec un exposant et une mantisse. L'écart entre deux flottants reste stable pour un même exposant, mais il y a une cassure quand on passe d'un exposant au suivant.
[[File:FloatingPointPrecisionAugmented.png|centre|vignette|upright=3|Floating Point Precision.]]
IEEE754 impose aussi le support de certains nombres flottants spéciaux qui servent notamment à stocker des valeurs comme l'infini. Commençons notre revue des flottants spéciaux par les '''dénormaux''', aussi appelés flottants dénormalisés. Ces flottants ont une particularité : leur bit implicite vaut 0. Ces dénormaux sont des nombres flottants où l'exposant est le plus petit possible. Le '''zéro''' est un dénormal particulier dont la mantisse est nulle. Au fait, remarquez que le zéro est codé deux fois à cause du bit de signe : on se retrouve avec un -0 et un +0.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur minimale (0 en binaire) || Mantisse différente de zéro (dénormal strict) ou égale à zéro (zéro)
|}
Fait étrange, la norme IEEE754 permet de représenter '''l'infini''', aussi bien en positif qu'en négatif. Celui-ci est codé en mettant l'exposant à sa valeur maximale et la mantisse à zéro. Et le pire, c'est qu'on peut effectuer des calculs sur ces flottants infinis. Mais cela a peu d'utilité.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse égale à zéro
|}
Mais malheureusement, l'invention des flottants infinis n'a pas réglé tous les problèmes. Par exemple, quel est le résultat de <math>\infty - \infty</math> ? Ou encore <math>0 - \infty</math> ? Autant prévenir tout de suite : mathématiquement, on ne peut pas savoir quel est le résultat de ces opérations. Pour pouvoir résoudre ces calculs, il a fallu inventer un nombre flottant qui signifie « je ne sais pas quel est le résultat de ton calcul pourri ». Ce nombre, c'est '''NaN'''. NaN est l'abréviation de ''Not A Number'', ce qui signifie : n'est pas un nombre. Ce NaN a un exposant dont la valeur est maximale, mais une mantisse différente de zéro. Pour être plus précis, il existe différents types de NaN, qui diffèrent par la valeur de leur mantisse, ainsi que par les effets qu'ils peuvent avoir. Malgré son nom explicite, on peut faire des opérations avec NaN, mais cela ne sert pas vraiment à grand chose : une opération arithmétique appliquée avec un NaN aura un résultat toujours égal à NaN.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse différente de zéro
|}
===Les arrondis et exceptions===
La norme impose aussi une gestion des arrondis ou erreurs, qui arrivent lors de calculs particuliers. En voici la liste :
{|class="wikitable"
|-
!Nom de l’exception
!Description
|-
!Invalid operation
|Opération qui produit un NAN. Elle est levée dans le cas de calculs ayant un résultat qui est un nombre complexe, ou quand le calcul est une forme indéterminée. Pour ceux qui ne savent pas ce que sont les formes indéterminées, voici en exclusivité la liste des calculs qui retournent NaN : <math>\infty - \infty</math>, <math>0 \times \infty</math>, <math>0^0</math>, <math>1^{\infty}</math>, <math>\infty^0</math>.
|-
!Overflow
|Résultat trop grand pour être stocké dans un flottant. Le plus souvent, on traite l'erreur en arrondissant le résultat en vue de la taille de la mantisse;
|-
!Underflow
|Pareil que le précédent, mais avec un résultat trop petit. Le plus souvent, on traite l'erreur en arrondissant le résultat vers 0.
|-
!Division par zéro
|Le nom parle de lui-même. La réponse la plus courante est de répondre + ou - l'infini.
|-
!'''Inexact'''
|Le résultat ne peut être représenté par un flottant et on doit l'arrondir.
|}
La gestion des arrondis pose souvent problème. Pour donner un exemple, on va prendre le nombre 0,1. En binaire, ce nombre s'écrit comme ceci : 0,1100110011001100... et ainsi de suite jusqu'à l'infini. Notre nombre utilise une infinité de décimales. Bien évidemment, on ne peut pas utiliser une infinité de bits pour stocker notre nombre et on doit impérativement l'arrondir. Comme vous le voyez avec la dernière exception, le codage des nombres flottants peut parfois poser problème : dans un ordinateur, il se peut qu'une opération sur deux nombres flottants donne un résultat qui ne peut être codé par un flottant. On est alors obligé d'arrondir ou de tronquer le résultat de façon à le faire rentrer dans un flottant. Pour éviter que des ordinateurs différents utilisent des méthodes d'arrondis différentes, on a décidé de normaliser les calculs sur les nombres flottants et les méthodes d'arrondis. Pour cela, la norme impose le support de quatre modes d'arrondis :
* Arrondir vers + l'infini ;
* vers - l'infini ;
* vers zéro ;
* vers le nombre flottant le plus proche.
===Les nombres flottants logarithmiques===
Les '''nombres flottants logarithmiques''' sont une spécialisation des nombres flottants IEEE754, ou tout du moins une spécialisation des flottants écrits en écriture scientifique. Un nombre logarithmique est donc composé d'un bit de signe et d'un exposant, sans mantisse. La mantisse est totalement implicite : tous les flottants logarithmiques ont la même mantisse, qui vaut 1.
Pour résumer, il ne reste que l'exposant, qui est tout simplement le logarithme en base 2 du nombre encodé, d'où le nom de codage flottant logarithmique donné à cette méthode. Attention toutefois : l'exposant est ici un nombre fractionnaire, codé en virgule fixe. Le choix d'un exposant fractionnaire permet de représenter pas mal de nombres de taille diverses.
{|class="wikitable"
|-
!
! Bit de signe
! Exposant
|-
! Représentation binaire
| 0
| 01110010101111
|-
! Représentation décimale
| +
| 1040,13245464
|}
L'utilité de cette représentation est de simplifier certains calculs, comme les multiplications, divisions, puissances, etc. En effet, les mathématiques nous disent que le logarithme d'un produit est égal à la somme des logarithmes : <math>\ln{(a \times b)} = \ln{a} + \ln{b}</math>. Or, il se trouve que les ordinateurs sont plus rapides pour faire des additions/soustractions que pour faire des multiplications/divisions. Donc, la représentation logarithmique permet de remplacer les multiplications/divisions par des additions/soustractions, plus simples et plus rapides pour l'ordinateur.
Évidemment, les applications des flottants logarithmiques sont rares, limitées à quelques situations bien précises (traitement d'image, calcul scientifique spécifiques).
==Les encodages binaires alternatifs==
Outre le binaire que nous venons de voir, il existe d'autres manières de coder des nombres avec des bits. Et nous allons les aborder dans cette section. Parmi celle-ci, nous parlerons du code Gray, de la représentation ''one hot'', du unaire et du ternaire. Nous en parlons car elles seront utiles dans la suite du cours, bien que de manière assez limitée. Autant nous passerons notre temps à parler du binaire normal, autant les représentations que nous allons voir sont aujourd'hui utilisées dans des cas assez spécifiques. Et elles sont plus courantes que vous ne le pensez.
===Le code Gray===
Le '''code Gray''' est un encodage binaire qui a une particularité très intéressante : deux nombres consécutifs n'ont qu'un seul bit de différence. Pour exemple, voici ce que donne le codage des 8 premiers entiers sur 3 bits :
{|class="wikitable"
! Décimal
! Binaire naturel
! Codage Gray
|-
|0||000||000
|-
|1||001||001
|-
|2||010||011
|-
|3||011||010
|-
|4||100||110
|-
|5||101||111
|-
|6||110||101
|-
|7||111||100
|}
Les utilisations du code Gray sont assez nombreuses, bien qu'on n'en croise pas tous les jours. Un exemple : le code Gray est très utile dans certains circuits appelés les compteurs, qui mémorisent un nombre et l'incrémentent (+1) ou le décrémentent (-1) suivant les besoins de l'utilisateur. Il est aussi utilisé dans des scénarios difficiles à expliquer ici (des codes correcteurs d'erreur ou des histoires de passages de domaines d'horloge). Mais le point important est que ce code sera absolument nécessaire dans quelques chapitres, quand nous parlerons des tables de Karnaugh, un concept important pour la conception de circuits électroniques. Ne passez pas à côté de cette section.
Pour construire ce code Gray, on peut procéder en suivant plusieurs méthodes, les deux plus connues étant la méthode du miroir et la méthode de l'inversion.
[[File:Binary-reflected Gray code construction.svg|vignette|Construction d'un code gray par la méthode du miroir.]]
La méthode du miroir est relativement simple. Pour connaître le code Gray des nombres codés sur n bits, il faut :
* partir du code Gray sur n-1 bits ;
* symétriser verticalement les nombres déjà obtenus (comme une réflexion dans un miroir) ;
* rajouter un 0 au début des anciens nombres, et un 1 au début des nouveaux nombres.
Il suffit de connaître le code Gray sur 1 bit pour appliquer la méthode : 0 est codé par le bit 0 et 1 par le bit 1.
Une autre méthode pour construire la suite des nombres en code Gray sur n bits est la méthode de l'inversion. Celle-ci permet de connaître le codage du nombre n à partir du codage du nombre n-1, comme la méthode du dessus. On part du nombre 0, systématiquement codé avec uniquement des zéros. Par la suite, on décide quel est le bit à inverser pour obtenir le nombre suivant, avec la règle suivante :
* si le nombre de 1 est pair, il faut inverser le dernier chiffre.
* si le nombre de 1 est impair, il faut localiser le 1 le plus à droite et inverser le chiffre situé à sa gauche.
Pour vous entraîner, essayez par vous-même avec 2, 3, voire 5.
===Les représentations ''one-hot'' et unaire===
La représentation ''one-hot'' et la représentation unaire sont deux représentations assez liées, mais légèrement différentes.
La '''représentation unaire''' est la représentation en base 1. Avec elle, le zéro est codé en mettant tous les bits du nombre à zéro, la valeur 1 est encodée avec la valeur 000...0001, le deux avec 000...0011, le trois avec 000...0111, etc. Pour résumer, le nombre N est encodé en mettant les N bits de poids faible à 1, à l'exception du zéro qui est encodé...par un zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! Unaire
|-
| 0 || 000 || 0000 0000
|-
| 1 || 001 || 0000 0001
|-
| 2 || 010 || 0000 0011
|-
| 3 || 011 || 0000 0111
|-
| 4 || 100 || 0000 1111
|-
| 5 || 101 || 0001 1111
|-
| 6 || 110 || 0011 1111
|-
| 7 || 111 || 0111 1111
|-
| 8 || 1000 || 1111 1111
|}
Pour convertir un nombre codé en unaire vers le binaire, il suffit de compter le nombre de 1. C'est ni plus ni moins ce que fait l'opération dite de ''population count'' dont nous avons parlé plus haut. La représentation unaire permet d'encoder N+1 valeurs sur N bits, car le zéro est encodé à part. Une valeur pour le zéro, plus une par bit.
Avec la '''représentation ''one-hot''''', un nombre est codé sur N bits, dont un seul bit est à 1. La position du 1 dans le nombre indique sa valeur. Précisément, la valeur encodée est égale au poids du bit à 1. En faisant cela, le zéro est codé en mettant le bit de poids faible à 1, la valeur 1 est codée avec les bits 00...00010, la valeur 2 est codée par les bits 00...000100, etc.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Elle permet d'encoder N valeurs différentes avec N bits, zéro inclus. On perd une valeur par rapport à l'unaire, car le zéro n'et pas encodé à part du reste. La traduction du binaire vers le ''one-hot'' est réalisé par un circuit nommé décodeur, que nous verrons d'ici quelques chapitres. De même, la traduction d'un nombre encodé en ''one-hot'' vers le binaire est réalisée par un circuit électronique appelé l'encodeur, qui est en quelque sorte l'inverse de l'encodeur. Les deux circuits sont très utilisés en électronique, ce sont des circuits de base, que nous n'aurons de cesse d'utiliser pour fabriquer d'autres circuits.
L'utilité de ces deux représentations n'est pas évidente. Mais sachez qu'elle le deviendra quand nous parlerons des circuits appelés les "compteurs", tout comme ce sera le cas pour le code Gray. Elles sont très utilisées dans des circuits appelés des machines à état, qui doivent incorporer des circuits compteurs efficients. Et ces représentations permettent d'avoir des circuits pour compter qui sont très simples, efficaces, rapides et économes en circuits électroniques. La représentation unaire sera aussi utile à la toute fin du cours, dans les chapitres liés à l'exécution dans le désordre. Il en sera fait référence quand nous parlerons de fenêtres d'instruction, d'émission dans l'ordre, de ''scoreboarding'', etc.
Il faut aussi noter qu'il a existé des ordinateur qui manipulaient des nombres codés en unaire. Enfin presque, la représentation utilisée était proche de l'unaire, mais ressemblait fortement. De tels ordinateurs étaient appelés des ''ordinateurs stochastiques'' et nous les aborderons à la toute fin de ce wikilivre, dans un chapitre annexe portant sur les ordinateurs ternaires (qui comptent en base 3) et unaires.
==Les encodages hybrides entre décimal et binaire==
Pour terminer, nous allons voir les représentations qui mixent binaire et décimal. Il s'agit en réalité d'encodage qui permettent de manipuler des nombres codés en décimal, mais les chiffres sont codés en utilisant des bits. L'encodage ''Binary Coded Decimal'' est le plus connu de cette catégorie, mais il y en a quelques autres, qui sont moins connus. De telles représentations étaient très utilisées au début de l'informatique, mais sont aujourd'hui tombées en désuétude. Pour désigner ces encodages, nous parlerons d''''encodages bit-décimaux''' : décimaux pour préciser qu'ils encodent des nombres codés en décimal, bit pour préciser que les chiffres sont codés avec des bits.
De tels encodages étaient utilisés sur les tous premiers ordinateurs pour faciliter le travail des programmeurs, mais aussi sur les premières calculettes. Ils sont utiles dans les applications où on doit manipuler chaque chiffre décimal séparément des autres. Un exemple classique est celui d'une horloge digitale. Ils ont aussi l'avantage de bien se marier avec les nombres à virgule. Les représentations des nombres à virgule fixe ou flottante ont le défaut que des arrondis peuvent survenir. Par exemple, la valeur 0,2 est codée comme suit en binaire normal : 0.00110011001100... et ainsi de suite jusqu’à l'infini. Avec les encodages bit-décimaux, on n'a pas ce problème, 0,2 étant codé 0000 , 0010.
Il a existé des ordinateurs qui travaillaient uniquement avec de tels encodages, appelés des '''ordinateurs décimaux'''. Ils étaient assez courants entre les années 60 et 70, même s'ils ne représentent pas la majorité des architectures de l'époque. Avec eux, la mémoire n'était pas organisée en octets, mais elle stockait des chiffres décimaux codés sur 5-6 bits. Le processeur faisait des calculs sur des chiffres bit-décimaux directement.
Leur grand avantage est leur très bonne performance dans les taches de bureautique, de comptabilité, et autres. Les processeurs de l'époque recevaient des entiers codés en bit-décimal de la part des entrées-sorties, et devaient les traiter. Les processeurs binaires devaient faire des conversions décimal-binaire pour communiquer avec les entrées-sorties, mais pas les processeurs décimaux. Le gain en performance pouvait être substantiel dans certaines applications.
Les ordinateurs décimaux se classent en deux sous-types bien précis. Les premiers gèrent des entiers qui ont un nombre de chiffres fixes. Par exemple, l'IBM 7070 gérait des entiers de 10 chiffres, plus un signe +/- pour les entiers signés. Le processeur faisait les calculs directement en bit-décimal, il gérait des entiers faisant environ 10-15 chiffres décimaux et savait faire des calculs avec de tels nombres. Le second sous-type effectue les calculs chiffre par chiffre et géraient des nombres de taille variable, sans limite de chiffres ! Nous reparlerons de ces derniers dans un chapitre ultérieur, quand nous parlerons de la différence entre byte et mot. Pour le moment, ne gardez à l'esprit que les processeurs gérant un nombre fixe de chiffres décimaux, plus simples à comprendre.
===Le ''Binary Coded Decimal''===
Le '''''Binary Coded Decimal''''', abrévié BCD, est une représentation qui mixe binaire et décimal. Avec cette représentation, les nombres sont écrits en décimal, comme nous en avons l'habitude dans la vie courante, sauf que chaque chiffre décimal est directement traduit en binaire sur 4 bits. Prenons l'exemple du nombre 624 : le 6, le 2 et le 4 sont codés en binaire séparément, ce qui donne 0110 0010 0100.
{| class="wikitable"
|+ Codage BCD
|-
! Nombre encodé (décimal)
! scope="col" colspan="4" | BCD
|-
| 0 || 0 || 0 || 0 || 0
|-
| 1 || 0 || 0 || 0 || 1
|-
| 2 || 0 || 0 || 1 || 0
|-
| 3 || 0 || 0 || 1 || 1
|-
| 4 || 0 || 1 || 0 || 0
|-
| 5 || 0 || 1 || 0 || 1
|-
| 6 || 0 || 1 || 1 || 0
|-
| 7 || 0 || 1 || 1 || 1
|-
| 8 || 1 || 0 || 0 || 0
|-
| 9 || 1 || 0 || 0 || 1
|}
On peut remarquer que 4 bits permettent de coder 16 valeurs, là où il n'y a que 10 chiffres. Dans le BCD proprement dit, les combinaisons de bits qui correspondent à 10, 11, 12, 13, 14 ou 15 ne sont tout simplement pas prises en compte. Sur quelques ordinateurs, ces combinaisons codent des chiffres décimaux en double : certains chiffres pouvaient être codés de deux manières différentes. Il est aussi possible d'utiliser ces valeurs pour coder quelque chose d'autre que des chiffres. Par exemple, il est possible de les utiliser pour coder un signe + ou -, afin de gérer les entiers relatifs. Une autre possibilité, complémentaire de la précédente, utilise ces valeurs en trop pour coder une virgule, afin de gérer les nombres non-entiers. Les possibilités sont nombreuses.
Le support du BCD implique souvent que le processeur supporte des '''opérations BCD''', à savoir des opérations capables de travailler sur des opérandes en BCD et de donner un résultat en BCD. Il faut bien faire la différence entre les opérations en binaire et les opérations en BCD. Par exemple, on n'effectue pas une addition de la même manière en binaire et en décimal/BCD, même si les grandes lignes sont presque identiques. Les différences font que les processeurs doivent avoir des opérations différentes pour les deux encodages, de la même manière que les processeurs gèrent les opérations sur les flottants et les entiers séparément.
Le support du codage BCD est abandonné de nos jours, c'est surtout quelque chose qu'on trouve sur les anciens processeurs. Les architectures 8 et 16 bits supportaient à la fois des opérations binaires et des opérations BCD. Mais il a aussi existé une méthode intermédiaire, qui utilisait des additions binaires normales sur des opérandes BCD. L'idée est que le résultat de l'addition est incorrect, car les valeurs 10 à 15 peuvent apparaître comme chiffre dans le résultat. Mais on peut le corriger pour obtenir le résultat exact en BCD. Pour résumé, les processeurs faisaient des additions en binaire, et corrigeaient le résultat avec une opération spécifique pour obtenir un résultat en BCD. Nous en reparlerons dans le chapitre sur le langage machine et l'assembleur, dans lequel nous étudierons les différentes opérations que supporte un processeur.
===Les encodages compacts du BCD===
Des variantes du BCD visent à réduire le nombre de bits utilisés pour encoder un nombre décimal. Nous allons les appeler les '''encodages BCD compacts'''. Avec certaines variantes, on peut utiliser seulement 7 bits pour coder deux chiffres décimaux, au lieu de 8 bits en BCD normal. Idem pour les nombres à 3 chiffres décimaux, qui prennent 10 bits au lieu de 12. Cette économie est réalisée par une variante du BCD assez compliquée, appelée l''''encodage Chen-Ho'''. Une alternative, appelée le ''Densely Packed Decimal'', arrive à une compression identique, mais avec quelques avantages au niveau de l'encodage. Ces encodages sont cependant assez compliqués à expliquer, surtout à ce niveau du cours, aussi je me contente de simplement mentionner leur existence.
Les encodages BCD compacts sont utilisés par les programmeurs pour stocker des données dans un fichier, en mémoire, mais guère plus. Ils ne sont pas gérés par le processeur directement, on ne peut pas faire de calculs avec, le processeur ne gére pas d'opération BCD supportant de tels encodages. C'est théoriquement possible, mais ça n'a jamais été implémenté dans un processeur, le cout en circuit n'en valait pas la chandelle. Pour faire des calculs sur des nombres en BCD compact, on doit les décompresser et les convertir en BCD normal ou en binaire, puis faire les calculs avec des opérations BCD/binaire usuelles.
===L'encodage ''Excess-3''===
La représentation '''''Excess-3''''' (XS-3) est une variante du BCD, qui a autrefois était utilisée sur d'anciens ordinateurs décimaux. Il s'agit d'une sorte d'hybride entre une représentation par excès et BCD. Chaque chiffre décimal est codé sur plusieurs bits en utilisant une représentation binaire par excès. Le biais est de 3 pour la représentation XS-3, il en existe des variantes avec un excès de 4, 5, mais elles sont moins utilisées. Avec elle, la conversion d'un chiffre décimal se fait comme suit : on prend le chiffre décimal, on ajoute 3, puis on traduit en binaire.
{| class="wikitable"
|+ Codage en ''Excess-3''
|-
! Décimal
! scope="col" colspan="4" | Binaire
|-
| -3 || 0 || 0 || 0 || 0
|-
| -2 || 0 || 0 || 0 || 1
|-
| -1 || 0 || 0 || 1 || 0
|-
| 0 || 0 || 0 || 1 || 1
|-
| 1 || 0 || 1 || 0 || 0
|-
| 2 || 0 || 1 || 0 || 1
|-
| 3 || 0 || 1 || 1 || 0
|-
| 4 || 0 || 1 || 1 || 1
|-
| 5 || 1 || 0 || 0 || 0
|-
| 6 || 1 || 0 || 0 || 1
|-
| 7 || 1 || 0 || 1 || 0
|-
| 8 || 1 || 0 || 1 || 1
|-
| 9 || 1 || 1 || 0 || 0
|-
| 10 || 1 || 1 || 0 || 1
|-
| 11 || 1 || 1 || 1 || 0
|-
| 12 || 1 || 1 || 1 || 1
|}
L'avantage de cette représentation est que l'on peut facilement calculer les soustractions en utilisant une méthode bien précise (celle du complément à 10, je ne détaille pas plus). Le défaut est que le calcul des additions est légèrement plus complexe.
L'XS-3 a été utilisé sur quelques ordinateurs décimaux assez anciens, notamment sur l'UNIVAC I et II.
===Les codes bi-quinaires===
Les '''codes bi-quinaires''' utilisent un mélange entre encodage binaire, encodage ''one-hot'' et encodage décimal. L'idée de base derrière ces codes est d'utiliser la représentation ''one-hot'' pour coder un chiffre BCD. Le problème est que cela demande d'utiliser 10 bits pour encoder un chiffre BCD, ce qui est beaucoup. L'encodage bi-quinaire optimise le tout de manière à diviser par deux le nombre de bits requis pour l'encodage.
Pour simplifier, un chiffre encodé en bi-quinaire est composé de deux parties : une partie binaire de un bit, suivie par d'une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits pour encoder les valeurs 0, 1, 2, 3 et 4. La partie binaire indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9 : 0 à 4 si la partie binaire vaut 0, 5 à 9 si elle vaut 1. La valeur du chiffre se calcule comme suit :
* si la partie binaire vaut 0, elle est égale à la partie quinaire ;
* si la partie binaire vaut 1, on ajoute 5 à la partie quinaire.
L'originalité de ce codage est qu'il permet de facilement compter sur ses doigts : on a deux mains, cinq doigts. De nombreuses langues utilisent ce système pour coder des nombres, comme le Wolof. Et ce système est utilisé dans certains pays pour compter sur ses doigts, voire dans la vie de tous les jours.
[[File:Code Biquinaer.svg|vignette|Code bi-quinaire où partie binaire et quinaire sont encodées en représentation ''one-hot''.]]
L'encodage abordé au-dessus est une version simplifiée de l'encodage bi-quinaire, il en existe d'autres variantes. Le bi-quinaire vu juste au-dessus encode la partie binaire en binaire, et la partie quinaire en ''one-hot''. Mais il est aussi possible d'inverser l'encodage, à savoir encoder la partie binaire avec une représentation ''one-hot'', alors que la partie bi-quinaire est encodée en binaire normal.
Cependant, la version la plus intéressante utilise l'encodage ''one-hot'' pour les deux ! Elle est illustrée ci-contre. Avec cette dernière, un chiffre BCD est encodé sur 7 bits, dont 2 sont obligatoirement à 1 et les 5 restants sont à 0. Cette propriété permet de détecter qu'un bit a été inversé suite à une erreur de transmission ou un problème de stockage. Si un bit est inversé, le résultat aura 1 bit à 1 et 6 à 0, ou alors 3 bits à 1 et 4 à 0, ce qui ne colle pas. Le résultat après inversion ne donnera pas un chiffre bi-quinaire valide.
Une autre variante du bi-quinaire a été utilisée sur l'IBM 1401. Avec elle, la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre encodé par la partie quinaire. L'avantage de ce système est que la gestion des retenues lors des additions était grandement simplifiée.
Le bi-quinaire et ses variantes ont été utilisés sur d'anciens ordinateurs, dont l'IBM 650, l'UNIVAC Solid State et le UNIVAC LARC. La plupart utilisaient des variantes de l'encodage bi-quinaire proprement dit. Pour rentrer vraiment dans le détail, voici les encodages utilisés sur ces machines. La lecture est facultative, il est possible de passer directement à la section suivante :
{| class="wikitable"
|-
! !! IBM 650 !! Remington Rand 409 !! UNIVAC Solid State !! UNIVAC LARC
|-
| Chiffre || 1357-9 bits || 05-01234 || p-5-421 bits || p-5-qqq bits
|-
| 0 || 10-10000 || 0000-0 || 1-0-000 || 1-0-000
|-
| 1 || 10-01000 || 1000-0 || 0-0-001 || 0-0-001
|-
| 2 || 10-00100 || 1000-1 || 0-0-010 || 1-0-011
|-
| 3 || 10-00010 || 0100-0 || 1-0-011 || 0-0-111
|-
| 4 || 10-00001 || 0100-1 || 0-0-100 || 1-0-110
|-
| 5 || 01-10000 || 0010-0 || 0-1-000 || 0-1-000
|-
| 6 || 01-01000 || 0010-1 || 1-1-001 || 1-1-001
|-
| 7 || 01-00100 || 0001-0 || 1-1-010|| 0-1-011
|-
| 8 || 01-00010 || 0001-1 || 0-1-011 || 1-1-111
|-
| 9 || 01-00001 || 0000-1 || 1-1-100|| 0-1-110
|}
==Annexe : pourquoi l'octet existe==
Nous avons vu plus haut que les nombres encodés en binaire sont découpés en octets, des paquets de 8 bits. Et les octets sont présents partout dans la conception d'un ordinateur. Par exemple, la mémoire RAM d'un ordinateur moderne est organisée en octets. Elle contient des cases mémoire, chacune contenant un octet. Les octets sont numérotés, ce qui permet de sélectionner un octet parmi tous les autres. La capacité de la mémoire est le nombre d'octets qu'elle contient, elle s'exprime en kilo-octets (1024 octets), mébi-octets (1024 kilo-octets), gibi-octets (1024 mébi-octets). Les octets sont donc présents partout dans l'informatique moderne.
{|class="wikitable"
|+ Contenu de la RAM, simplifié
|-
! Numéro
! Octet
|-
! 0
| 0000 1111
|-
! 1
| 0101 0101
|-
! 2
| 0111 0110
|-
! 3
| 0011 1010
|-
! 4
| 1001 1001
|-
! ...
| ...
|-
! 1023
| 0110 1010
|-
! 1024
| 0001 1110
|}
Mais pourquoi est-ce que les nombres encodés en binaire sont souvent découpés en octets ? Pourquoi ne pas utiliser des groupes de 3, 5, 6, 7, 9, 10 bits ? La question est d'autant plus légitime que d'anciens ordinateurs faisaient ainsi. Leurs mémoires étaient découpé en cases mémoire, mais chacune faisait 7 ou 9 bits, rien d'impossible à cela. À vrai dire, l'usage des octets au tout début de l'informatique était assez rare. Il y a eu un basculement progressif vers l'octet pendant les années 80-90. Reste à expliquer pourquoi. Si on devait résumer, les deux raisons principales sont le support du BCD et le support des caractères pour le texte. Voyons d'abord l'encodage des caractères en binaire.
===L'encodage du texte : ASCII et autres encodages===
Pour les caractères, les encodages se sont concentrés sur l'encodage des alphabets occidentaux. Ils demandaient d'encoder au minimum toutes les lettres, en majuscules et minuscules, plus les chiffres, une dizaine de caractères pour la ponctuation. À cela, il faut ajouter des symboles utiles comme %, µ, $, £, #, {} et bien d'autres. Au minimum, cela fait au minimum 52 lettres (26 lettres doublées pour les majuscules) + 10 chiffres + une dizaine de caractères pour la ponctuation : cela fait plus de 72 valeurs à encoder. Pour encoder cela en binaire, le minimum est de 7 bits, ce qui permet d'encoder 128 caractères : les 72 caractères minimum, plus quelques autres. L'encodage ASCII utilisait d'ailleurs 7 bits pour encoder des nombres.
On pourrait croire que c'était une raison suffisante pour utiliser des groupes de 7 bits. Mais il y a quelques problèmes avec cela. Premièrement, les encodages 7 bits pour le texte marchent bien pour gérer la langue anglaise et la plupart des langues occidentales, mais d'autres langues ont besoin d'encoder des caractères locaux. Les 128 caractères de l'ASCII ne sont pas suffisant, ce qui demande de passer de 7 à 8 bits. On retombe donc sur un octet ! D'ailleurs, les encodages ASCII améliorés utilisaient les 127 premières valeurs pour l'ASCII normal, mais les 128 suivantes encodaient d'autres caractères. Les encodages en question varient d'un pays ou d'une langue à l'autre, mais ils sont terriblement importants, ce qui est un argument pour l'usage des octets.
===Le support des données encodées sur un ''nibble''===
Une autre raison pour l'usage des octets est qu'il est facile de les découper en deux ''nibbles''. Pour rappel, les ''nibbles'' sont des groupes de 4 bits. Et il se trouve que de nombreuses données sont encodées sur 4 bits, ni plus ni moins. Les ''nibbles'' sont très important pour le support du BCD et de l’hexadécimal. Et c'était un avantage important au début de l'informatique, moins maintenant. Un octet regroupe deux ''nibbles'' : un ''nibble'' de poids faible, un ''nibble'' de poids fort.
[[File:Nibbles in a byte.PNG|centre|vignette|upright=2|Nibbles dans un octet.]]
Un chiffre BCD occupe un ''nibble'', ce qui fait que la taille idéale de l'unité mémoire doit être un multiple de 4 bits : 4 bits, 8 bits, 12 bits, 16 bits, etc. Un ''nibble'' de 4 bits est insuffisant pour encoder des caractères, 8 bits fonctionne parfaitement, pas besoin d'aller plus loin. De plus, l'usage des ''nibbles'' permet de gérer la représentation hexadécimale très facilement. Un chiffre hexadécimal est encodé sur exactement un ''nibble'', un octet regroupe deux chiffres hexadécimaux, deux octets 4 chiffres hexadécimaux, etc. Enfin, 8 bits est une puissance de deux, ce qui simplifie grandement l'encodage des entiers en binaire est est une source de nombreuses simplifications. Les circuits de l'ordinateur sont plus simple à concevoir quand ils manipulent des opérandes de 2, 4, 8, 16, 32, 64, 128 bits.
===L'influence sur la taille des opérandes===
Les anciens ordinateurs étaient conçus pour le calcul scientifique, ou du moins des applications similaires. Ils n'avaient pas besoin de supporter le BCD, ni même de gérer du texte. Ils ne faisaient que des calculs et utilisaient des opérandes flottantes. La taille des opérandes était dictée par des considérations calculatoires. La norme à l'époque était que les calculs scientifiques devaient gérer des opérandes pouvant aller jusqu'à 10 chiffres décimaux. Et cela demande d'utiliser des opérandes entiers de 35 bits, avec un bit en plus pour encoder des opérandes positives ou négatives. Les 36 bits étaient parfois découpés en 6 groupes de 6 bits chacun, au lieu des octets de 8 bits.
Mais ces groupes de 6 bits étaient souvent peu utilisés. Prenons l'exemple des mémoires RAM. Plus haut, j'ai dit qu'une mémoire RAM normale est composée de plusieurs octets, tous numérotés. Intuitivement, vous vous dites que les anciens ''mainframes'' remplaçaient ces octets par des groupes de 6 bits dans la RAM. Mais en réalité, l'équivalent des RAM de l'époque utilisait des groupes de 36 bits. les groupes de 36 bits étaient aussi numérotés, les numéros étant appelés des adresses mémoire. Par contre, les adresses/numéros étaient codés sur 12 ou 18 bits, c'est à dire sur 2 ou 3 groupes de 6 bits. La raison est que ces numéros sont manipulés par le processeur, qui gère des opérandes de 36 bits. L'idée est que le numéro ne prend que la moitié ou un tiers des 36 bits gérés par le processeur.
Avec l'apparition de l'informatique commerciale, les ordinateurs ont du s'adapter aux besoins des entreprises, pour gérer la comptabilité, des applications financières, des documents texte, etc. Et cela demandait de supporter à la fois le BCD et les caractères/lettres. Il a fallu trouver une unité mémoire capable de gérer à la fois nombres entiers, chiffres BCD et caractères. L'octet était parfait pour ça. Les ''mainframes'' sont adaptés en supportant des caractères codés sur 6, 7 ou 8 bits. Mais le support demandait d'utiliser des circuits qui découpaient un groupe de 36 bits en plusieurs caractères ou en plusieurs chiffres BCD. Le tout demandait des circuits de décalage et de masquage assez complexes. Les ordinateurs 8 et 16 bits manipulaient des octets nativement : leur mémoire RAM gérait des octets, le processeur traitait des opérandes de 8, 16 ou 32 bits. Ils n'avaient pas besoin de matériel complexe pour extraire un caractère ou un chiffre BCD d'un octet/doublet/multiplet. C'était un avantage que les anciens ''mainframes'' n'avaient pas.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'encodage des données
| prevText=L'encodage des données
| next=Les codes de détection/correction d'erreur
| nextText=Les codes de détection/correction d'erreur
}}
</noinclude>
ffrvkf0tqniznfjqunznpl9tj7vgp7k
775642
775640
2026-11-02T18:57:34Z
Mewtow
31375
/* Les nombres flottants */
775642
wikitext
text/x-wiki
Dans le chapitre précédent, nous avons vu que les ordinateurs actuels utilisent un codage binaire. Ce codage binaire ne vous est peut-être pas familier. Aussi, dans ce chapitre, nous allons apprendre comment coder des nombres en binaire. Nous allons commencer par le cas le plus simple : les nombres positifs. Par la suite, nous aborderons les nombres négatifs. Et nous terminerons par les nombres à virgules, appelés aussi nombres flottants.
==Le codage des nombres entiers positifs==
Pour coder des nombres entiers positifs, il existe plusieurs méthodes : le binaire, l’hexadécimal, le code Gray, le décimal codé binaire et bien d'autres encore. La plus connue est certainement le binaire, secondée par l'hexadécimal, les autres étant plus anecdotiques. Pour comprendre ce qu'est le binaire, il nous faut faire un rappel sur les nombres entiers tel que vous les avez appris en primaire, à savoir les entiers écrits en décimal. Prenons un nombre écrit en décimal : le chiffre le plus à droite est le chiffre des unités, celui à côté est pour les dizaines, suivi du chiffre des centaines, et ainsi de suite. Dans un tel nombre :
* on utilise une dizaine de chiffres, de 0 à 9 ;
* chaque chiffre est multiplié par une puissance de 10 : 1, 10, 100, 1000, etc. ;
* la position d'un chiffre dans le nombre indique par quelle puissance de 10 il faut le multiplier : le chiffre des unités doit être multiplié par 1, celui des dizaines par 10, celui des centaines par 100, et ainsi de suite.
Exemple avec le nombre 1337 :
<math>1337 = 1 \times 1000 + 3 \times 100 + 3 \times 10 + 7 \times 1</math>
Pour résumer, un nombre en décimal s'écrit comme la somme de produits, chaque produit multipliant un chiffre par une puissance de 10. On dit alors que le nombre est en base 10.
<math>1337 = 1 \times 10^3 + 3 \times 10^2 + 3 \times 10^1 + 7 \times 10^0</math>
Ce qui peut être fait avec des puissances de 10 peut être fait avec des puissances de 2, 3, 4, 125, etc : n'importe quel nombre entier strictement positif peut servir de '''base'''. En informatique, on utilise rarement la base 10 à laquelle nous sommes tant habitués. On utilise à la place deux autres bases :
* La base 2 (système binaire) : les chiffres utilisés sont 0 et 1 ;
* La base 16 (système hexadécimal) : les chiffres utilisés sont 0, 1, 2, 3, 4, 5, 6, 7, 8 et 9 ; auxquels s'ajoutent les six premières lettres de notre alphabet : A, B, C, D, E et F.
===Le système binaire===
En binaire, on compte en base 2. Cela veut dire qu'au lieu d'utiliser des puissances de 10 comme en décimal, on utilise des puissances de deux : n'importe quel nombre entier peut être écrit sous la forme d'une somme de puissances de 2. Par exemple 6 s'écrira donc 0110 en binaire : <math>0 \times 2^3 + 1 \times 2^2 + 1 \times 2^1 + 0 \times 2^0 = 6</math>. On peut remarquer que le binaire n'autorise que deux chiffres, à savoir 0 ou 1 : ces chiffres binaires sont appelés des '''bits''' (abréviation de Binary Digit). Pour simplifier, on peut dire qu'un bit est un truc qui vaut 0 ou 1. Pour résumer, tout nombre en binaire s'écrit sous la forme d'un produit entre bits et puissances de deux de la forme :
<math>a_0 \times 2^0 + a_1 \times 2^1 + a_2 \times 2^2 + a_3 \times 2^3 + a_4 \times 2^4 +...+ a_n \times 2^n</math>
Les coefficients <math>a_0, a_1, a_2</math> sont les bits, l'exposant n qui correspond à un bit <math>a_n</math> est appelé le '''poids''' du bit.
====La terminologie du binaire====
En informatique, il est rare que l'on code une information sur un seul bit. Dans la plupart des cas, l'ordinateur manipule des nombres codés sur plusieurs bits. Les informaticiens ont donné des noms aux groupes de bits suivant leur taille. Le plus connu est certainement l''''octet''', qui désigne un groupe de 8 bits. Moins connu, on parle de '''''nibble''''' pour un groupe de 4 bits (un demi-octet), de '''doublet''' pour un groupe de 16 bits (deux octets) et de '''quadruplet''' pour un groupe de 32 bits (quatre octets).
Précisons qu'en anglais, le terme ''byte'' n'est pas synonyme d'octet. En réalité, le terme ''octet'' marche aussi bien en français qu'en anglais. Quant au terme ''byte'', il désigne un concept complètement différent, que nous aborderons plus tard (c'est la plus petite unité de mémoire que le processeur peut adresser). Il a existé dans le passé des ordinateurs où le ''byte'' faisait 4, 7, 9, 16, voire 48 bits, par exemple. Il a même existé des ordinateurs où le ''byte'' faisait exactement 1 bit ! Mais sur presque tous les ordinateurs modernes, un ''byte'' fait effectivement 8 bits, ce qui fait que le terme ''byte'' est parfois utilisé en lieu et place d'''octet''. Mais c'est un abus de langage, attention aux confusions ! Dans ce cours, nous parlerons d'octet pour désigner un groupe de 8 bits, en réservant le terme ''byte'' pour sa véritable signification.
À l'intérieur d'un nombre, le '''bit de poids faible''' est celui qui est le plus à droite du nombre, alors que le '''bit de poids fort''' est celui non nul qui est placé le plus à gauche, comme illustré dans le schéma ci-dessous.
C'est le même principe que l'écriture des nombres en base décimale : le chiffre le plus significatif à gauche, et le moins significatif à droite (l'unité ou les décimales après la virgule).
[[File:Msb.svg|centre|vignette|upright=2|Bit de poids fort.]]
[[File:Lsb.svg|centre|vignette|upright=2|Bit de poids faible.]]
Cette terminologie s'applique aussi pour les bits à l'intérieur d'un octet, d'un ''nibble'', d'un doublet ou d'un quadruplet. Pour un nombre codés sur plusieurs octets, on peut aussi parler de l'octet de poids fort et de l'octet de poids faible, du doublet de poids fort ou de poids faible, etc.
====La traduction binaire→décimal====
Pour traduire un nombre binaire en décimal, il faut juste se rappeler que la position d'un bit indique par quelle puissance il faut le multiplier. Ainsi, le chiffre le plus à droite est le chiffre des unités : il doit être multiplié par 1 (<math>2^0</math>). Le chiffre situé immédiatement à gauche du chiffre des unités doit être multiplié par 2 (<math>2^1</math>). Le chiffre encore à gauche doit être multiplié par 4 (<math>2^2</math>), et ainsi de suite. Mathématiquement, on peut dire que le énième bit en partant de la droite doit être multiplié par <math>2^{n-1}</math>. Par exemple, la valeur du nombre noté 1011 en binaire est de <math>8 \times 1 + 4 \times 0 + 2 \times 1 + 1 \times 1 = 11</math>.
[[File:Value of digits in the Binary numeral system.svg|centre|vignette|upright=2|Valeur des chffres dans le système de numération binaire.]]
====La traduction décimal→binaire====
La traduction inverse, du décimal au binaire, demande d'effectuer des divisions successives par deux. Les divisions en question sont des divisions euclidiennes, avec un reste et un quotient. En lisant les restes des divisions dans un certain sens, on obtient le nombre en binaire. Voici comment il faut procéder, pour traduire le nombre 34 :
[[File:Exemple d'illustration de la méthode de conversion décimal vers binaire.png|centre|vignette|upright=2|Exemple d'illustration de la méthode de conversion décimal vers binaire.]]
====Quelques opérations en binaire====
Maintenant que l'on sait coder des nombres en binaire normal, il est utile de savoir comment faire quelques opérations usuelles en binaire. Nous utiliserons les acquis de cette section dans la suite du chapitre, bien que de manière assez marginale.
La première opération est assez spécifique au binaire. Il s'agit d'une opération qui inverse les bits d'un nombre : les 0 deviennent des 1 et réciproquement. Par exemple, le nombre 0001 1001 devient 1110 0110. Elle porte plusieurs noms : opération NOT, opération NON, complémentation, etc. Nous parlerons de '''complémentation''' ou d'opération NOT dans ce qui suit. Beaucoup d'ordinateurs gèrent cette opération, ils savent la faire en un seul calcul. Il faut dire que c'est une opération assez utile, bien que nous ne pouvons pas encore expliquer pourquoi.
[[File:Binary Addition Demonstration.svg|vignette|Exemple d'addition en binaire.]]
La seconde opération à aborder est l'addition. Elle se fait en binaire de la même manière qu'en décimal : Pour faire une addition en binaire, on additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. Sauf que l'on additionne des bits. Heureusement, la table d'addition est très simple en binaire :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
La troisième opération est une variante de l'addition appelée l''''opération XOR''', notée <math>\oplus</math>. Il s'agit d'une addition dans laquelle on ne propage pas les retenues. L'addition des deux bits des opérandes se fait normalement, mais les retenues sont simplement oubliées, on n'en tient pas compte. Le résultat est que l'addition se résume à appliquer la table d'addition précédente :
* 0 <math>\oplus</math> 0 = 0 ;
* 0 <math>\oplus</math> 1 = 1 ;
* 1 <math>\oplus</math> 0 = 1 ;
* 1 <math>\oplus</math> 1 = 0.
Pour résumer, le résultat vaut 1 si les deux bits sont différents, 0 s'ils sont identiques. L'opération XOR sera utilisée rapidement dans le chapitre suivant, quand nous parlerons rapidement du mot de parité. Et elle sera beaucoup utilisée dans la suite du cours, nous en feront fortement usage. Pour le moment, mémorisez juste cette opération, elle n'a rien de compliqué.
Une dernière opération est l'opération de '''''population count'''''. Il s'agit ni plus ni moins que de compter le nombre de bits qui sont à 1 dans un nombre. Par exemple, pour le nombre 0110 0010 1101 1110, elle donne pour résultat 9. Elle est utilisée dans certaines applications, comme le calcul de certains codes correcteurs d'erreur, comme on le verra dans le chapitre suivant. Elle est supportée sur de nombreux ordinateurs, encore que cela dépende du processeur considéré. Il s'agit cependant d'une opération assez courante, supportée par les processeurs ARM, les processeurs x86 modernes (ceux qui gèrent le SSE), et quelques autres.
===L'hexadécimal===
L’hexadécimal est basé sur le même principe que le binaire, sauf qu'il utilise les 16 chiffres suivants :
{| class="wikitable"
|- style="color:#8080ffff; text-align:center;"
! Chiffre hexadécimal
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || A || B || C || D || E || F
|- style="text-align:center;"
! Nombre décimal correspondant
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 10 || 11 || 12 || 13 || 14 || 15
|- style="text-align:center;"
! Notation binaire
| 0000 || 0001 || 0010 || 0011 || 0100 || 0101 || 0110 || 0111 || 1000 || 1001 || 1010 || 1011 || 1100 || 1101 || 1110 || 1111
|}
Dans les textes, afin de différencier les nombres décimaux des nombres hexadécimaux, les nombres hexadécimaux sont suivis par un petit h, indiqué en indice. Si cette notation n'existait pas, des nombres comme 2546 seraient ambigus : on ne saurait pas dire sans autre indication s'ils sont écrits en décimal ou en hexadécimal. Avec la notation, on sait de suite que 2546 est en décimal et que 2546h est en hexadécimal.
Dans les codes sources des programmes, la notation diffère selon le langage de programmation.
Certains supportent le suffixe <code>h</code> pour les nombres hexadécimaux, d'autres utilisent un préfixe <code>0x</code> ou <code>0h</code>.
Pour convertir un nombre hexadécimal en décimal, il suffit de multiplier chaque chiffre par la puissance de 16 qui lui est attribuée. Là encore, la position d'un chiffre indique par quelle puissance celui-ci doit être multiplié : le chiffre le plus à droite est celui des unités, le second chiffre le plus à droite doit être multiplié par 16, le troisième chiffre en partant de la droite doit être multiplié par 256 (16 * 16) et ainsi de suite. La technique pour convertir un nombre décimal vers de l’hexadécimal est similaire à celle utilisée pour traduire un nombre du décimal vers le binaire. On retrouve une suite de divisions successives, mais cette fois-ci les divisions ne sont pas des divisions par 2 : ce sont des divisions par 16.
La conversion inverse, de l'hexadécimal vers le binaire est très simple, nettement plus simple que les autres conversions. Pour passer de l'hexadécimal au binaire, il suffit de traduire chaque chiffre en sa valeur binaire, celle indiquée dans le tableau au tout début du paragraphe nommé « Hexadécimal ». Une fois cela fait, il suffit de faire le remplacement. La traduction inverse est tout aussi simple : il suffit de grouper les bits du nombre par 4, en commençant par la droite (si un groupe est incomplet, on le remplit avec des zéros). Il suffit alors de remplacer le groupe de 4 bits par le chiffre hexadécimal qui correspond.
==Interlude propédeutique : la capacité d'un entier et les débordements d'entiers==
Dans la section précédente, nous avons vu comment coder des entiers positifs en binaire ou dans des représentations proches. La logique voudrait que l'on aborde ensuite le codage des entiers négatifs. Mais nous allons déroger à cette logique simple, pour des raisons pédagogiques. Nous allons faire un interlude qui introduira des notions utiles pour la suite du chapitre. De plus, ces concepts seront abordés de nombreuses fois dans ce wikilivre et l'introduire ici est de loin la solution idéale.
===Les ordinateurs manipulent des nombres codés sur un nombre fixe de bits===
Vous avez certainement déjà entendu parler de processeurs 32 ou 64 bits. Et si vous avez joué aux jeux vidéos durant votre jeunesse et êtes assez agé, vous avez entendu parler de consoles de jeu 8 bits, 16 bits, 32 bits, voire 64 bits (pour la Jaguar, et c'était un peu trompeur). Derrière cette appellation qu'on retrouvait autrefois comme argument commercial dans la presse se cache un concept simple. Tout ordinateur manipule des nombres entiers dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Une console 16 bits manipulait des entiers codés en binaire sur 16 bits, pas un de plus, pas un de moins. Pareil pour les anciens ordinateurs 32 bits, qui manipulaient des nombres entiers codés sur 32 bits.
Aujourd'hui, les ordinateurs modernes utilisent presque un nombre de bits qui est une puissance de 2 : 8, 16, 32, 64, 128, 256, voire 512 bits. Mais cette règle souffre évidemment d'exceptions. Aux tout débuts de l'informatique, certaines machines utilisaient 3, 7, 13, 17, 23, 36 et 48 bits ; mais elles sont aujourd'hui tombées en désuétude. De nos jours, il ne reste que les processeurs dédiés au traitement de signal audio, que l'on trouve dans les chaînes HIFI, les décodeurs TNT, les lecteurs DVD, etc. Ceux-ci utilisent des nombres entiers de 24 bits, car l'information audio est souvent codée par des nombres de 24 bits.
Anecdote amusante, il a existé des ordinateurs de 1 bit, qui sont capables de manipuler des nombres codés sur 1 bit, pas plus. Un exemple est le Motorola MC14500B, commercialisé de 1976.
====Le lien entre nombre de bits et valeurs codables====
Évidemment, on ne peut pas coder tous les entiers possibles et imaginables avec seulement 8 bits, ou 16 bits. Et il parait intuitif que l'on ait plus de valeurs codables sur 16 bits qu'avec 8 bits, par exemple. Plus le nombre de bits est important, plus on pourra coder de valeurs entières différentes. Mais combien de plus ? Par exemple, si je passe de 8 bits à 16 bits, est-ce que le nombre de valeurs que l'on peut coder double, quadruple, pentuple ? De même, combien de valeurs différentes on peut coder avec <math>n</math> bits. Par exemple, combien de nombres différents peut-on coder avec 4, 8 ou 16 bits ? La section précédente vous l'expliquer.
Avec <math>n</math> bits, on peut coder <math>2^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>2^n - 1</math>. N'oubliez pas cette formule : elle sera assez utile dans la suite de ce tutoriel. Pour exemple, on peut coder 16 valeurs avec 4 bits, qui vont de 0 à 15. De même, on peut coder 256 valeurs avec un octet, qui vont de 0 à 255. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de bits
! Nombre de valeurs codables
|-
! 4
| 16
|-
! 8
| 256
|-
! 16
| 65 536
|-
! 32
| 4 294 967 296
|-
! 64
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien de bits il faut pour coder une valeur quelconque, que nous noterons N. Pour cela, il faut utiliser la formule précédente, mais à l'envers. On cherche alors <math>n</math> tel que <math>N = 2^n</math>. L'opération qui donne <math>n</math> est appelée le logarithme, et plus précisément un logarithme en base 2, noté <math>\log_2{(X)}</math>. Le problème est que le résultat du logarithme ne tombe juste que si le nombre X est une puissance de 2. Si ce n'est pas le cas, le résultat est un nombre à virgule, ce qui n'a pas de sens pratique. Par exemple, la formule nous dit que pour coder le nombre 13, on a besoin de 3,70043971814 bits, ce qui est impossible. Pour que le résultat ait un sens, il faut arrondir à l'entier supérieur. Pour l'exemple précédent, les 3,70043971814 bits s'arrondissent en 4 bits.
====Le lien entre nombre de chiffres hexadécimaux et valeurs codables====
Maintenant, voyons combien de valeurs peut-on coder avec <math>n</math> chiffres hexadécimaux. La réponse n'est pas très différente de celle obtenue en binaire, si ce n'est qu'il faut remplacer le 2 par un 16 dans la formule précédente. Avec <math>n</math> chiffres hexadécimaux, on peut coder <math>16^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>16^n - 1</math>. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de chiffres héxadécimaux
! Nombre de valeurs codables
|-
! 1 (4 bits)
| 16
|-
! 2 (8 bits)
| 256
|-
! 4 (16 bits)
| 65 536
|-
! 8 (32 bits)
| 4 294 967 296
|-
! 16 (64 bits)
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien faut-il de chiffres hexadécimaux pour coder une valeur quelconque en hexadécimal. La formule est là encore la même qu'en binaire, sauf qu'on remplace le 2 par un 16. Pour trouver le nombre de chiffres hexadécimaux pour encoder un nombre X, il faut calculer <math>\log_{16}{(X)}</math>. Notons que le logarithme utilisé est un logarithme en base 16, et non un logarithme en base 2, comme pour le binaire. Là encore, le résultat ne tombe juste que si le nombre X est une puissance de 16 et il faut arrondir à l'entier supérieur si ce n'est pas le cas.
Une propriété mathématique des logarithmes nous dit que l'on peut passer d'un logarithme en base X et à un logarithme en base Y avec une simple division, en utilisant la formule suivante :
: <math>\log_X{(N)} = {\log_{Y}{(N)} \over \log_Y(X)}</math>
Dans le cas qui nous intéresse, on a Y = 2 et X = 16, ce qui donne :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over \log_2(16)}</math>
Or, <math>\log_2(16)</math> est tout simplement égal à 4, car il faut 4 bits pour coder la valeur 16. On a donc :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over 4}</math>
En clair, il faut quatre fois moins de chiffres hexadécimaux que de bits, ce qui est assez intuitif vu qu'il faut 4 bits pour coder un chiffre hexadécimal.
===Les débordements d'entier===
On vient de voir que tout ordinateur manipule des nombres dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Et cela limite les valeurs qu'il peut encoder, qui sont comprises dans un intervalle bien précis. Mais que ce passe-t-il si jamais le résultat d'un calcul ne rentre pas dans cet intervalle ? Par exemple, pour du binaire normal, que faire si le résultat d'un calcul atteint ou dépasse <math>2^n</math> ? Dans ce cas, le résultat ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
On peut imaginer d'autres codages pour lesquels les entiers ne commencent pas à zéro ou ne terminent pas à <math>2^n - 1</math>. On peut prendre le cas où l'ordinateur gère les nombres négatifs, par exemple. Dans le cas général, l'ordinateur peut coder les valeurs comprises dans un intervalle, qui va de la valeur la plus basse <math>N_\text{min}</math> à la valeur la plus grande <math>N_\text{max}</math>. Et encore une fois, si un résultat de calcul sort de cet intervalle, on fait face à un débordement d'entier.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math>, avec <math>N_\text{max}</math> la plus grande valeur codable par l'ordinateur.
On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur basse de débordement vaut <math>N_\text{min} - 1</math>, avec <math>N_\text{min}</math> la plus petite valeur codable par l'ordinateur.
===La gestion des débordements d'entiers===
Face à un débordement d'entier, l'ordinateur peut utiliser deux méthodes : l'arithmétique saturée ou l'arithmétique modulaire.
L''''arithmétique saturée''' consiste à arrondir le résultat pour prendre la plus grande ou la plus petite valeur. Si le résultat d'un calcul dépasse la valeur haute de débordement, le résultat est remplacé par le plus grand entier supporté par l'ordinateur. La même chose est possible quand le résultat est inférieur à la plus petite valeur possible, par exemple lors d'une soustraction : l'ordinateur arrondit au plus petit entier possible.
Pour donner un exemple, voici ce que cela donne avec des entiers codés sur 4 bits, qui codent des nombres de 0 à 15. Si je fais le calcul 8 + 9, le résultat normal vaut 17, ce qui ne rentre pas dans l'intervalle. Le résultat est alors arrondi à 15. Inversement, si je fais le calcul 8 - 9, le résultat sera de -1, ce qui ne rentre pas dans l'intervalle : le résultat est alors arrondi à 0.
[[File:Odometer rollover.jpg|vignette|Exemple de débordement d'entier sur un compteur mécanique quelconque. L'image montre clairement que le compteur revient à zéro une fois la valeur maximale dépassée.]]
L''''arithmétique modulaire''' est plus compliquée et c'est elle qui va nous intéresser dans ce qui suit. Pour simplifier, imaginons que l'on décompte à partir de zéro. Quand on arrive à la valeur haute de débordement, on recommence à compter à partir de zéro. L'arithmétique modulaire n'est pas si contre-intuitive et vous l'utilisez sans doute au quotidien. Après tout, c'est comme cela que l'on compte les heures, les minutes et les secondes. Quand on compte les minutes, on revient à 0 au bout de 60 minutes. Pareil pour les heures : on revient à zéro quand on arrive à 24 heures. Divers compteurs mécaniques fonctionnent sur le même principe et reviennent à zéro quand ils dépassent la plus grande valeur possible, l'image ci-contre en montrant un exemple.
Mathématiquement, l'arithmétique modulaire implique des divisions euclidiennes, celles qui donnent un quotient et un reste. Lors d'un débordement, le résultat s'obtient comme suit : on divise le nombre qui déborde par la valeur haute de débordement, et que l'on conserve le reste de la division. Au passage, l'opération qui consiste à faire une division et à garder le reste au lieu du quotient est appelée le ''modulo''. Prenons l'exemple où l'ordinateur peut coder tous les nombres entre 0 et 1023, soit une valeur haute de débordement de 1024. Pour coder le nombre 4563, on fait le calcul 4563 / 1024. On obtient : <math>4563 = 4 \times 1024 + 467</math>. Le reste de la division est de 467 et c'est lui qui sera utilisé pour coder la valeur de départ, 4563. Le nombre 4563 est donc codé par la valeur 467 dans un tel ordinateur en arithmétique modulaire. Au passage, la valeur haute de débordement est toujours codée par un zéro dans ce genre d'arithmétique.
Les ordinateurs utilisent le plus souvent une valeur haute de débordement de <math>2^n</math>, avec n le nombre de bits utilisé pour coder les nombres entiers positifs. En faisant cela, l'opération modulo devient très simple et revient à éliminer les bits de poids forts au-delà du énième bit. Par exemple, reprenons l'exemple d'un ordinateur qui code ses nombres sur 4 bits. Imaginons qu'il fasse le calcul <math>13 + 3</math>, soit 1101 + 0011 = 1 0000 en binaire. Le résultat entraîne un débordement d'entier et l'ordinateur ne conserve que les 4 bits de poids faible. Cela donne : 1101 + 0011 = 0000. Comme autre exemple l'addition 1111 + 0010 ne donnera pas 17 (1 0001), mais 1 (0001).
L'avantage est que les calculs sont beaucoup plus simples avec cette méthode qu'avec les autres. L'ordinateur a juste à ne pas calculer les bits de poids fort. Pas besoin de faire une division pour calculer un modulo, pas besoin de corriger le résultat pour faire de l'arithmétique saturée.
===Aparté : quelques valeurs particulières en binaire===
Plus haut, on a dit qu'avec n bits, on peut encoder toutes les valeurs allant de <math>0</math> à <math>2^n - 1</math>. Ce simple fait permet de déterminer quelle est la valeur de certains entiers à vue d’œil. Dans ce qui va suivre, nous allons poser quelques bases que nous réutiliserons dans la suite du chapitre. Il s'agit de quelques trivias qui sont cependant assez utiles.
Le premier trivia concerne la valeur maximale <math>2^n - 1</math> : elle est encodée par un nombre dont tous les bits sont à 1.
Le deuxième trivia concerne les nombres de la forme 000...000 1111 1111, à savoir des nombres dont les x bits de poids faible sont à 1 et tous les autres valent 0. Par définition, de tels nombres codent la plus grande valeur possible sur x bits, ce qui fait qu'ils valent <math>2^x - 1</math>.
Le troisième trivia concerne les nombres de la forme 11111...0000. En clair, des nombres où on a une suite consécutive de 1 dans les bits de poids fort et les x bits de poids faibles à 0. De tels nombres valent : <math>2^n - 2^x</math>. La preuve est assez simple : ils s'obtiennent en prenant la valeur maximale <math>2^n - 1</math>, et en soustrayant un nombre de la forme <math>2^x - 1</math>.
[[File:Valeurs particulières en binaire.png|centre|vignette|upright=2|Valeurs particulières en binaire]]
Si on utilise l'arithmétique modulaire, la valeur <math>2^n</math> n'est autre que la valeur de débordement haute pour les nombres stockés sur n bits, et se code comme le zéro (il faudrait 1 bit de plus pour stocker le 1 de poids fort de <math>2^n</math>). Les 1er et 3ème nombres évoqués dans les paragraphes précédents peuvent donc se passer de <math>2^n</math> dans leur expression :
* <math>- 1</math> est codé 1111111111111111...111 (n bits à 1)
* <math>- 2^x</math> est codé 11111...111000..000000 (x bits à 0 précédés de n-x bits à 1)
==Les nombres entiers négatifs==
<noinclude>[[File:Binary Representations.svg|vignette|Représentations signées, exemples sur 4 bits.]]</noinclude>
Passons maintenant aux entiers négatifs en binaire : comment représenter le signe moins ("-") avec des 0 et des 1 ? Eh bien, il existe plusieurs méthodes :
* la représentation en signe-valeur absolue ;
* la représentation en complément à un ;
* la représentation en complément à deux;
* la représentation par excès ;
* la représentation dans une base négative ;
* d'autres représentations encore moins utilisées que les autres.
===La représentation en signe-valeur absolue===
La solution la plus simple pour représenter un entier négatif consiste à coder sa valeur absolue en binaire, et rajouter un bit qui précise si c'est un entier positif ou un entier négatif. Par convention, ce bit de signe vaut 0 si le nombre est positif et 1 s'il est négatif. On parle alors de représentation en signe-valeur absolue, aussi appelée '''représentation en signe-magnitude'''.
Avec cette technique, il y autant de nombres positifs que négatifs. Mieux : pour chaque nombre représentable en représentation signe-valeur absolue, son inverse l'est aussi. Ce qui fait qu'avec cette méthode, le zéro est codé deux fois : on a un -0, et un +0. Cela pose des problèmes lorsqu'on demande à notre ordinateur d'effectuer des calculs ou des comparaisons avec zéro.
[[File:Codage sur 4 bits en signe-valeur absolue.png|centre|vignette|upright=2|Codage sur 4 bits en signe-valeur absolue]]
===La représentation en complément à un===
La représentation en '''complément à un''' peut être vue, en première approximation, comme une variante de la représentation en signe-magnitude. Et je dis bien : "en première approximation", car il y a beaucoup à dire sur la représentation en complément à un, mais nous verrons cela dans la section suivante sur le complément à deux. Conceptuellement, la représentation en signe-magnitude et celle du complément à un sont drastiquement différentes et sont basées sur des concepts mathématiques totalement différents. Mais elles ont des ressemblances de surface, qui font que faire la comparaison entre les deux est assez utile pédagogiquement parlant.
En complément à un, les nombres sont codés en utilisant un bit de signe qui indique si le nombre de positif ou négatif, couplé à une valeur absolue. Par contre, la valeur absolue est codée différemment en binaire. Pour les nombres positifs, la valeur absolue est codée en binaire normal, pas de changement comparé aux autres représentations. Mais pour les valeurs négatives, la valeur absolue est codée en binaire normal, puis tous les bits sont inversés : les 0 deviennent des 1 et réciproquement. En clair, on utilise une opération de complémentation pour les nombres négatifs.
Prenons un exemple avec un nombre codé sur 4 bits, avec un cinquième bit de signe. La valeur 5 est codée comme suit : 0 pour le bit de signe, 5 donne 0101 en binaire, le résultat est 0 0101. Pour la valeur -5, le bit de signe est 1, 5 donne 0101 en binaire, on inverse les bits ce qui donne 1010 : cela donne 11010. Notez qu'on peut passer d'un résultat à l'autre avec une opération de complémentation, à savoir en inversant les bits de l'autre et réciproquement.
Il s'agit là d'une propriété générale avec le complément à 1 : l'opposé d'un nombre, à savoir passer de sa valeur positive à sa valeur négative ou inversement, se calcule avec une opération NOT, une opération de complémentation (d'où son nom). L'avantage est que les ordinateurs gèrent naturellement d'opération de complémentation. Par contre, en signe-magnitude, inverser le bit de signe est une opération spécifique et ne sert qu'à ça. Il faut donc rajouter une opération en plus pour calculer l'opposé d'un nombre.
La représentation en complément à un garde les défauts de la représentation en signe-magnitude. Le zéro est codé deux fois, avec un zéro positif et un zéro négatif. La différence est que les valeurs négatives sont dans l'ordre inverse, il y a une symétrie un peu meilleure. Les deux zéros sont d'ailleurs totalement éloignés, ils correspondent aux valeurs extrêmes encodables : le zéro positif a tous ses bits à 0, le zéro négatif a tous ses bits à 1. Le résultat est que l'implémentation des comparaisons et de certains calculs est plus complexe qu'avec le signe-magnitude, mais de peu.
[[File:Codage sur 4 bits en complément à 1.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 1.]]
===La représentation par excès===
La représentation par excès consiste à ajouter un biais aux nombres à encoder afin de les encoder par un entier positif. Pour encoder tous les nombres compris entre -X et Y en représentation par excès, il suffit de prendre la valeur du nombre à encoder, et de lui ajouter un biais égal à X. Ainsi, la valeur -X sera encodée par zéro, et toutes les autres valeurs le seront par un entier positif, le zéro sera encodé par X, 1, par X+1, etc. Par exemple, prenons des nombres compris entre -127 et 128. On va devoir ajouter un biais égal à 127, ce qui donne :
{|class="wikitable"
|-
!Valeur avant encodage!!Valeur après encodage
|-
||-127||0
|-
||-126||1
|-
||-125||2
|-
||…||…
|-
||0||127
|-
||…||…
|-
||127||254
|-
||128||255
|}
===La représentation en complément à deux===
La représentation en '''complément à deux''' est basée sur les mêmes mathématiques que le complément à un, mais fonctionne très différemment en pratique.
Si on regarde de loin, son principe est assez différent : il n'y a pas de bit de signe ni quoi que ce soit d'autre. À la place, un nombre en complément à deux est encodé comme en binaire normal, à un point près : le bit de poids fort est soustrait, et non additionné aux autres. Il a une valeur négative : on soustrait la puissance de deux associée s'il vaut 1, on ne la tient pas en compte s'il vaut 0.
Par exemple, la valeur du nombre noté 111001 en complément à deux s'obtient comme suit :
{|class="wikitable" style="margin:auto;"
|-
! -32 !! 16 !! 8 !! 4 !! 2 !! 1
|-
| 1 || 1 || 1 || 0 || 0 || 1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(8×1)+(4×0)+(2×1)+(1×1) = −32+16+8+1 = -7.
: ''Avec le complément à deux, comme avec le complément à un, le bit de poids fort vaut 0 pour les nombres positifs et 1 pour les négatifs.''
L'avantage de cette représentation est qu'elle n'a pas de double zéro : le zéro n'est encodé que par une seule valeur. Par contre, la valeur autrefois prise par le zéro négatif est réutilisée pour encoder une valeur négative. La conséquence est que l'on a un nombre négatif en plus d'encodé, il n'y a plus le même nombre de valeurs strictement positives et de valeurs négatives encodées. Le nombre négatif en question est appelé le '''nombre le plus négatif''', ce nom trahit le fait que c'est celui qui a la plus petite valeur (la plus grande valeur absolue). Il est impossible de coder l'entier positif associé, sa valeur absolue.
[[File:Codage sur 4 bits en complément à 2.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 2.]]
====Le calcul du complément à deux : première méthode====
Les représentations en complément à un et en complément à deux sont basées sur le même principe mathématique. Leur idée est de remplacer chaque nombre négatif par un nombre positif équivalent, appelé le ''complément''. Par équivalent, on veut dire que tout calcul donne le même résultat si on remplace un nombre négatif par son complément (idem avec un nombre positif, son complément aura un signe inverse). Et pour faire cela, elles se basent sur les débordements d'entier pour fonctionner, et plus précisément sur l'arithmétique modulaire abordée plus haut.
Si on fait les calculs avec le complément, les résultats du calcul entraînent un débordement d'entier, qui sera résolu par un '''modulo''' : l'ordinateur ne conserve que les bits de poids faible du résultat, les autres bits sont oubliés. Par exemple, prenons l'addition 15 + 2, 1111 + 0010 en binaire : le résultat ne sera pas 17 (10001), vu qu'on n'a pas assez de bits pour encoder le résultat, mais 1 (0001). Et le résultat après modulo sera identique au résultat qu'on aurait obtenu avec le nombre négatif sans modulo. En clair, c'est la gestion des débordements qui permet de corriger le résultat de manière à ce que l'opération avec le complément donne le même résultat qu'avec le nombre négatif voulu. Ainsi, on peut coder un nombre négatif en utilisant son complément positif.
: Cela ressemble beaucoup à la méthode de soustraction basée sur un complément à 9 pour ceux qui connaissent, sauf que c'est une version binaire qui nous intéresse ici.
Prenons un exemple, qui permettra d'introduire la suite. Encore une fois, on utilise un codage sur 4 bits dont la valeur haute de débordement est de 16. Prenons l'addition de 13 + 3 = 16. Avec l'arithmétique modulaire, 16 est équivalent à 0, ce qui donne : 13 + 3 = 0 ! On peut aussi reformuler en disant que 13 = -3, ou encore que 3 = -13. Dit autrement, 3 est le complément de -13 pour ce codage. Et ne croyez pas que ça marche uniquement dans cet exemple : cela se généralise assez rapidement à tout nombre négatif.
Prenons un nombre N dont un veut calculer le complément à deux K. Dans le cas général, on a :
: <math>N + K = 2^n</math>, vu que <math>2^n = 0</math> en utilisant le modulo.
En réorganisant très légèrement les termes pour isoler K, on a :
: <math>K = 2^n - N</math>
La formule précédent permet de calculer la complément à deux assez simplement, en faisant le calcul à la main.
Avant de poursuivre, prenons un exemple très intéressant : le cas où N = -1. Son complément à deux vaut donc :
: <math>K = 2^n - 1</math>
Le terme <math>2^n - 1</math> devrait vous rappeler quelque chose : il s'agit d'un nombre dont tous les bits sont à 1. En clair, le complément à deux de -1 est un nombre de la forme 1111...111. Et cela vaut quel que soit le nombre de bits n. La représentation de -1 est similaire, peu importe que l'on utilise des nombres de 16 bits, 32 bits, 64 bits, etc.
Voyons maintenant le cas des puissance de deux, par exemple 2, 4, 8, 16, etc. Leur complément à deux vaut :
: <math>K = 2^n - 2^x</math>
Nous avions vu précédemment dans le chapitre que ces nombres sont de la forme 11111...0000. En clair, des nombres dont les x bits de poids faible sont à 0, et tous les autres bits sont à 1. Par exemple, le complément à deux de -2 est un nombre dont les bits sont à 1, sauf le bit de poids faible. De même, le complément à deux de -4 a tous ses bits à 1, sauf les deux bits de poids faible. Et le complément à deux de -8 a tous ses bits à 1, sauf les trois bits de poids faible. Pour résumer, tout nombre de la forme <math>- 2^x</math> a tous ses bits à 1, sauf les x bits de poids faible qui sont à 0.
{|class="wikitable"
|+ Exemple avec des nombres de 8 bits
|-
! -1
| 1111 1111
|-
! -2
| 1111 1110
|-
! -4
| 1111 1100
|-
! -8
| 1111 1000
|-
! -16
| 1111 0000
|-
! -32
| 1110 0000
|-
! -64
| 1100 0000
|-
! -128
| 1000 0000
|-
! 0 / - 256
| 0000 0000
|}
====Le calcul du complément à deux : seconde méthode====
Il existe une seconde méthode pour calculer le complément à deux d'un nombre, la voici. Pour les nombres positifs, encodez-les comme en binaire normale. Pour les nombres négatifs, faites pareil, puis inversez tous les bits, avant d'ajouter un. La procédure est identique à celle du complément à un, sauf que l'on incrémente le résultat final. Et ce n'est pas une coïncidence, comme nous allons le voir immédiatement.
Pour comprendre pourquoi la méthode marche, repartons de la formule précédente <math>K = 2^n - N</math>. Elle peut se reformuler comme suit :
: <math>K = (2^n - 1 + 1) - N</math>
: <math>K = (2^n - 1) - N + 1</math>
La valeur <math>(2^n - 1)</math> est par définition un nombre dont tous les bits sont à 1. À cette valeur, on soustrait un nombre <math>|N|</math> dont certains bits sont à 1 et d'autres à 0. En clair, pour chaque colonne, on a deux possibilités : soit on doit faire la soustraction <math>1-1</math>, soit la soustraction <math>1-0</math>. Or, les règles de l'arithmétique binaire disent que <math>1-1=0</math> et <math>1-0=1</math>. En regardant attentivement, on se rend compte que le bit du résultat est l'inverse du bit de départ. De plus, les deux cas ne donnent pas de retenue : le calcul pour chaque bit n'influence pas les bits voisins.
Le terme <math>(2^n - 1) - N</math> est donc le complément à un du nombre N, un nombre égal à sa représentation en binaire dont tous les bits sont inversés. Notons <math>\overline{|N|}</math> le nombre formé en inversant tous les bits de N. On a alors :
: <math>K = \overline{N} + 1</math>
En clair, le complément à deux s'obtient en prenant le complément à un et en ajoutant 1. Dit autrement, il faut prendre le nombre N, en inverser tous les bits et ajouter 1.
Une autre manière équivalente consiste à faire le calcul suivant :
: <math>K = \overline{N - 1}</math>
On prend le nombre dont on veut le complément, on soustrait 1 et on inverse les bits.
: Notons que tout ce qui a été dit plus haut marche aussi pour le complément à un, avec cependant une petite différence : la valeur haute de débordement n'est pas la même, ce qui change les calculs. Pour des nombres codés sur <math>n</math> bits, la valeur haute de débordement est égale à <math>2^n</math> en complément à deux, alors qu'elle est de <math>2^n - 1</math> en complément à un. De ce fait, la gestion des débordements est plus simple en complément à deux.
====L'extension de signe====
Dans les ordinateurs, tous les nombres sont codés sur un nombre fixé et constant de bits. Ainsi, les circuits d'un ordinateur ne peuvent manipuler que des nombres de 4, 8, 12, 16, 32, 48, 64 bits, suivant la machine. Si l'on veut utiliser un entier codé sur 16 bits et que l'ordinateur ne peut manipuler que des nombres de 32 bits, il faut bien trouver un moyen de convertir le nombre de 16 bits en un nombre de 32 bits, sans changer sa valeur et en conservant son signe. Cette conversion d'un entier en un entier plus grand, qui conserve valeur et signe s'appelle l''''extension de signe'''.
L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
L'explication plus simple tient dans la manière de coder le bit de poids fort. Prenons l'exemple de la conversion d'un entier de 5 bits en un entier de 8bits. Les 4 bits de poids faible ont un poids positif (on les additionne), alors que le bit de poids fort a un poids négatif. Le nombre encodé vaut : <math>\text{Bit de poids fort} \times -16 + \text{Valeur encodée sur les 4 bits de poids faible}</math>. La valeur encodée sur 4 bits reste la même après extension de signe, car les poids des bits de poids faible ne changent pas. Par contre, le bit de poids fort change. Sur 8 bits, la valeur -16 est encodée par un nombre de la forme 1111 0000. En remplaçant le bit de poids fort par sa valeur calculée sur plus de bits, on remarque que les bits de poids fort ont été remplacés par des 1.
===La représentation négabinaire===
Enfin, il existe une dernière méthode, assez simple à comprendre, appelée '''représentation négabinaire'''. Dans cette méthode, les nombres sont codés non en base 2, mais en base -2. Oui, vous avez bien lu : la base est un nombre négatif. Dans les faits, la base -2 est similaire à la base 2 : il y a toujours deux chiffres (0 et 1), et la position dans un chiffre indique toujours par quelle puissance de 2 il faut multiplier, sauf qu'il faudra ajouter un signe moins une fois sur 2. Concrètement, les puissances de -2 sont les suivantes : 1, -2, 4, -8, 16, -32, 64, etc. En effet, un nombre négatif multiplié par un nombre négatif donne un nombre positif, ce qui fait qu'une puissance sur deux est négative, alors que les autres sont positives. Ainsi, on peut représenter des nombres négatifs, mais aussi des nombres positifs dans une puissance négative.
Par exemple, la valeur du nombre noté 11011 en base -2 s'obtient comme suit :
{|class="wikitable"
|-
!…
!-32
!16
!-8
!4
!-2
!1
|-
|…
|1
|1
|1
|0
|1
|1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(−8×1)+(4×0)+(−2×1)+(1×1)=−32+16−8−2+1=−25.
==Les nombres à virgule fixe==
On sait donc comment sont stockés nos nombres entiers dans un ordinateur. Néanmoins, les nombres entiers ne sont pas les seuls nombres que l'on utilise au quotidien : il nous arrive d'en utiliser à virgule. Notre ordinateur n'est pas en reste : il est lui aussi capable de manipuler de tels nombres. Dans les grandes lignes, il peut utiliser deux méthodes pour coder des nombres à virgule en binaire : La virgule fixe et la virgule flottante. Parlons d'abord de la virgule fixe, plus simple à comprendre.
===La virgule fixe : des entiers cachés===
La méthode de la virgule fixe consiste à émuler les nombres à virgule à partir de nombres entiers. Un nombre à virgule fixe est codé par un nombre entier proportionnel au nombre à virgule fixe. Pour obtenir la valeur de notre nombre à virgule fixe, il suffit de diviser l'entier servant à le représenter par le facteur de proportionnalité. Par exemple, pour coder 1,23 en virgule fixe, on peut choisir comme « facteur de conversion » 1000, ce qui donne l'entier 1230.
Généralement, les informaticiens utilisent une puissance de deux comme facteur de conversion, pour simplifier les calculs. En faisant cela, on peut écrire les nombres en binaire et les traduire en décimal facilement. Pour l'exemple, cela permet d'écrire des nombres à virgule en binaire comme ceci : 1011101,1011001. Et ces nombres peuvent se traduire en décimal avec la même méthode que des nombres entier, modulo une petite différence. Comme pour les chiffres situés à gauche de la virgule, chaque bit situé à droite de la virgule doit être multiplié par la puissance de deux adéquate. La différence, c'est que les chiffres situés à droite de la virgule sont multipliés par une puissance négative de deux, c'est à dire par <math>1 \over 2</math>, <math>1 \over 4</math>, <math>1 \over 8</math>, <math>1 \over 16</math>, <math>1 \over 32</math>, ...
[[File:Fixed-point binary representation.svg|centre|vignette|upright=2.5|Nombres à virgule fixe.]]
Cette méthode est assez peu utilisée de nos jours, quoiqu'elle puisse avoir quelques rares applications relativement connue. Un bon exemple est celui des banques : les sommes d'argent déposées sur les comptes ou transférées sont codés en virgule fixe. Les sommes manipulées par les ordinateurs ne sont pas exprimées en euros, mais en centimes d'euros. Et c'est une forme de codage en virgule fixe dont le facteur de conversion est égal à 100. La raison de ce choix est que les autres méthodes de codage des nombres à virgule peuvent donner des résultats imprécis : il se peut que les résultats doivent être tronqués ou arrondis, suivant les opérandes. Cela n'arrive jamais en virgule fixe, du moins quand on se limite aux additions et soustractions.
===Les nombres à virgule non-représentables en binaire===
Quel que soit la façon de représenter les nombres à virgules, il existe des nombres qui ne peuvent être représentés de manière exacte, à savoir avec un nombre fini de décimales après la virgule. En soi, ce n'est pas spécifique au binaire, on a la même chose en décimal. Par exemple, la fraction 1/3 en décimal s'écrit 0.3333333..., avec une infinité de 3. La même chose existe en binaire, mais pour des nombres différents.
Déjà, évacuons le cas des nombres irrationnels, à savoir les nombres qui ne peuvent pas s'écrire sous la forme d'une fraction, comme <math>pi</math> ou <math>e</math>. Ils ont une infinité de décimales que ce soit en binaire, en décimal, en hexadécimal, ou autre. Ils ne sont pas représentables avec un nombre fini de décimales quelle que soit la base utilisée. Concentrons-nous sur des nombres qui ne sont pas dans ce cas, et qui ont un nombre fini ou infini de décimales.
Le passage de la base 10 à la base 2 change le nombre de décimales, et peut faire passer d'un nombre fini de décimales à un nombre infini. Par exemple, <math>\frac{1}{5} = 0.2</math> est représenté en binaire avec une séquence infinie de bits : <math>0.001100110011...</math>. Les nombres étant en base binaire et représenté avec un nombre limité de bits, il existe certains nombres décimaux triviaux qui ne sont pas représentables avec un nombre fini de décimales.
Et la réciproque n'est pas vraie : tout nombre binaire avec un nombre fini de décimales en binaire est représentable avec un nombre fini de décimales en base 10. Ceci est lié à la décomposition en facteur premier des bases utilisées :
* <math>2 = 2^1</math>
* <math>10 = 2^1 \times 5^1</math>
Le nombre 10 possède tous les facteurs premiers de 2, mais 2 n'a pas de 5 dans sa décomposition.
==Les nombres flottants==
Les nombres à virgule fixe ont aujourd'hui été remplacés par les '''nombres à virgule flottante''', où le nombre de chiffres après la virgule est variable. Le codage d'un nombre flottant est basée sur son ''écriture scientifique''. Pour rappel, en décimal, l’écriture scientifique d'un nombre consiste à écrire celui-ci comme un produit entre un nombre <math>a</math> et une puissance de 10, avec <math>a</math> étant compris entre 1 et 9. Ce qui donne :
: <math>a \times 10^e</math>, avec <math>a \in [1, 10 [</math>
Le nombre <math>a</math> est appelé le ''significande'' et il est compris entre 1 (inclus) et 10 (exclu). Cette contrainte garantit que l'écriture scientifique d'un nombre est unique, qu'il n'y a qu'une seule façon d'écrire un nombre en notation scientifique. Pour cela, on impose le nombre de chiffre à gauche de la virgule et le plus simple est que celui-ci soit égal à 1. Mais il faut aussi que celui-ci ne soit pas nul. En effet, si on autorise de mettre un 0 à gauche de la virgule, il y a plusieurs manières équivalentes d'écrire un nombre. Ces deux contraintes font que le significande doit être égal ou plus grand que 1, mais strictement inférieur à 10. Par contre, on peut mettre autant de décimales que l'on veut.
En binaire, c'est la même chose, mais avec une puissance de deux. Cela implique de modifier la puissance utilisée : au lieu d'utiliser une puissance de 10, on utilise une puissance de 2.
: <math>a \times 2^e</math>, avec <math>a \in [1, 2 [</math>
Le significande est aussi altéré, au même titre que la puissance, même si les contraintes sont similaires à celles en base 10. En effet, le nombre <math>a</math> ne possède toujours qu'un seul chiffre à gauche de la virgule, comme en base 10. Vu que seuls deux chiffres sont possibles (0 et 1) en binaire, on s'attend à ce que le chiffre situé à gauche de la virgule soit un zéro ou un 1. Mais rappelons que le chiffre à gauche doit être non-nul, pour les mêmes raisons qu'en décimal. En clair, le significande a forcément un bit à 1 à gauche de la virgule. Pour récapituler, l'écriture scientifique binaire d'un nombre consiste à écrire celui-ci sous la forme :
: <math>1,m \times 2^e</math>, avec <math>m \in [0, 1 [</math>
La partie fractionnaire du nombre <math>a</math>, qu'on appelle la '''mantisse'''.
[[File:Mantissa.svg|centre|vignette|Écriture scientifique (anglais).]]
Pour déterminer l'écriture scientifique en binaire d'un nombre quelconque, la procédure dépend de la valeur du nombre en question. Tout dépend s'il est dans l'intervalle <math>[1, 2 [</math>, au-delà de 2 ou en-dessous de 1.
* Pour un nombre entre 1 (inclus) et 2 (exclu), il suffit de le traduire en binaire. Son exposant est 0.
* Pour un nombre au-delà de 2, il faut le diviser par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant est alors le nombre de fois qu'il a fallu diviser par 2.
* Pour un nombre plus petit que 1, il faut le multiplier par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant se calcule en prenant le nombre de fois qu'il a fallu multiplier par 2, et en prenant l'opposé (en mettant un signe - devant le résultat).
===Le codage des nombres flottants et la norme IEEE 754===
Pour coder cette écriture scientifique avec des nombres, l'idée la plus simple est d'utiliser trois nombres, pour coder respectivement la mantisse, l'exposant et un bit de signe. Coder la mantisse implique que le bit à gauche de la virgule vaut toujours 1, mais nous verrons qu'il y a quelques rares exceptions à cette règle.
Quelques nombres flottants spécialisés, les dénormaux, ne sont pas codés en respectant les règles pour le significande et ont un 0 à gauche de la virgule. Un bon exemple est tout simplement la valeur zéro, que l'on peut coder en virgule flottante, mais seulement en passant outre les règles sur le significande.
Toujours est-il que le bit à gauche de la virgule n'est pas codé, que ce soit pour les flottants normaux ou les fameux dénormaux qui font exception. On verra que ce bit peut se déduire en fonction de l'exposant utilisé pour encoder le nombre à virgule, ce qui lui vaut le nom de '''bit implicite'''. L'exposant peut être aussi bien positif que négatif (pour permettre de coder des nombres très petits), et est encodé en représentation par excès sur n bits avec un biais égal à <math>2^{n-1} - 1</math>.
[[File:IEEE754 Format General.png|centre|vignette|upright=2|IEEE754 Format Général]]
Le standard pour le codage des nombres à virgule flottante est la '''norme IEEE 754'''. Cette norme va (entre autres) définir quatre types de flottants différents, qui pourront stocker plus ou moins de valeurs différentes.
{|class="wikitable"
|-
!Classe de nombre flottant
!Nombre de bits utilisés pour coder un flottant
!Nombre de bits de l'exposant
!Nombre de bits pour la mantisse
!Décalage
|-
!Simple précision
| 32
| 8
| 23
| 127
|-
!Double précision
| 64
| 11
| 52
| 1023
|-
!Double précision étendue
| 80 ou plus
| 15 ou plus
| 64 ou plus
| 16383 ou plus
|}
Voici à peu-près comment les nombres flottants sont répartis sur une ligne numérique. Vous remarquerez que les flottants sont de plus en plus éloignés au fur et à mesure qu'on s'éloigne de zéro. La raison est l'encodage avec un exposant et une mantisse. L'écart entre deux flottants reste stable pour un même exposant, mais il y a une cassure quand on passe d'un exposant au suivant.
[[File:FloatingPointPrecisionAugmented.png|centre|vignette|upright=3|Floating Point Precision.]]
IEEE754 impose aussi le support de certains nombres flottants spéciaux qui servent notamment à stocker des valeurs comme l'infini. Commençons notre revue des flottants spéciaux par les '''dénormaux''', aussi appelés flottants dénormalisés. Ces flottants ont une particularité : leur bit implicite vaut 0. Ces dénormaux sont des nombres flottants où l'exposant est le plus petit possible. Le '''zéro''' est un dénormal particulier dont la mantisse est nulle. Au fait, remarquez que le zéro est codé deux fois à cause du bit de signe : on se retrouve avec un -0 et un +0.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur minimale (0 en binaire) || Mantisse différente de zéro (dénormal strict) ou égale à zéro (zéro)
|}
Fait étrange, la norme IEEE754 permet de représenter '''l'infini''', aussi bien en positif qu'en négatif. Celui-ci est codé en mettant l'exposant à sa valeur maximale et la mantisse à zéro. Et le pire, c'est qu'on peut effectuer des calculs sur ces flottants infinis. Mais cela a peu d'utilité.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse égale à zéro
|}
Mais malheureusement, l'invention des flottants infinis n'a pas réglé tous les problèmes. Par exemple, quel est le résultat de <math>\infty - \infty</math> ? Ou encore <math>0 - \infty</math> ? Autant prévenir tout de suite : mathématiquement, on ne peut pas savoir quel est le résultat de ces opérations. Pour pouvoir résoudre ces calculs, il a fallu inventer un nombre flottant qui signifie « je ne sais pas quel est le résultat de ton calcul pourri ». Ce nombre, c'est '''NaN'''. NaN est l'abréviation de ''Not A Number'', ce qui signifie : n'est pas un nombre. Ce NaN a un exposant dont la valeur est maximale, mais une mantisse différente de zéro. Pour être plus précis, il existe différents types de NaN, qui diffèrent par la valeur de leur mantisse, ainsi que par les effets qu'ils peuvent avoir. Malgré son nom explicite, on peut faire des opérations avec NaN, mais cela ne sert pas vraiment à grand chose : une opération arithmétique appliquée avec un NaN aura un résultat toujours égal à NaN.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse différente de zéro
|}
===Les arrondis et exceptions===
La norme impose aussi une gestion des arrondis ou erreurs, qui arrivent lors de calculs particuliers. En voici la liste :
{|class="wikitable"
|-
!Nom de l’exception
!Description
|-
!Invalid operation
|Opération qui produit un NAN. Elle est levée dans le cas de calculs ayant un résultat qui est un nombre complexe, ou quand le calcul est une forme indéterminée. Pour ceux qui ne savent pas ce que sont les formes indéterminées, voici en exclusivité la liste des calculs qui retournent NaN : <math>\infty - \infty</math>, <math>0 \times \infty</math>, <math>0^0</math>, <math>1^{\infty}</math>, <math>\infty^0</math>.
|-
!Overflow
|Résultat trop grand pour être stocké dans un flottant. Le plus souvent, on traite l'erreur en arrondissant le résultat en vue de la taille de la mantisse;
|-
!Underflow
|Pareil que le précédent, mais avec un résultat trop petit. Le plus souvent, on traite l'erreur en arrondissant le résultat vers 0.
|-
!Division par zéro
|Le nom parle de lui-même. La réponse la plus courante est de répondre + ou - l'infini.
|-
!'''Inexact'''
|Le résultat ne peut être représenté par un flottant et on doit l'arrondir.
|}
La gestion des arrondis pose souvent problème. Pour donner un exemple, on va prendre le nombre 0,1. En binaire, ce nombre s'écrit comme ceci : 0,1100110011001100... et ainsi de suite jusqu'à l'infini. Notre nombre utilise une infinité de décimales. Bien évidemment, on ne peut pas utiliser une infinité de bits pour stocker notre nombre et on doit impérativement l'arrondir. Comme vous le voyez avec la dernière exception, le codage des nombres flottants peut parfois poser problème : dans un ordinateur, il se peut qu'une opération sur deux nombres flottants donne un résultat qui ne peut être codé par un flottant. On est alors obligé d'arrondir ou de tronquer le résultat de façon à le faire rentrer dans un flottant. Pour éviter que des ordinateurs différents utilisent des méthodes d'arrondis différentes, on a décidé de normaliser les calculs sur les nombres flottants et les méthodes d'arrondis. Pour cela, la norme impose le support de quatre modes d'arrondis :
* Arrondir vers + l'infini ;
* vers - l'infini ;
* vers zéro ;
* vers le nombre flottant le plus proche.
===Les nombres flottants logarithmiques===
Les '''nombres flottants logarithmiques''' sont une spécialisation des nombres flottants IEEE754, ou tout du moins une spécialisation des flottants écrits en écriture scientifique. Un nombre logarithmique est donc composé d'un bit de signe et d'un exposant, sans mantisse. La mantisse est totalement implicite : tous les flottants logarithmiques ont la même mantisse, qui vaut 1.
Pour résumer, il ne reste que l'exposant, qui est tout simplement le logarithme en base 2 du nombre encodé, d'où le nom de codage flottant logarithmique donné à cette méthode. Attention toutefois : l'exposant est ici un nombre fractionnaire, codé en virgule fixe. Le choix d'un exposant fractionnaire permet de représenter pas mal de nombres de taille diverses.
{|class="wikitable"
|-
!
! Bit de signe
! Exposant
|-
! Représentation binaire
| 0
| 01110010101111
|-
! Représentation décimale
| +
| 1040,13245464
|}
L'utilité de cette représentation est de simplifier certains calculs, comme les multiplications, divisions, puissances, etc. En effet, les mathématiques nous disent que le logarithme d'un produit est égal à la somme des logarithmes : <math>\ln{(a \times b)} = \ln{a} + \ln{b}</math>. Or, il se trouve que les ordinateurs sont plus rapides pour faire des additions/soustractions que pour faire des multiplications/divisions. Donc, la représentation logarithmique permet de remplacer les multiplications/divisions par des additions/soustractions, plus simples et plus rapides pour l'ordinateur.
Évidemment, les applications des flottants logarithmiques sont rares, limitées à quelques situations bien précises (traitement d'image, calcul scientifique spécifiques).
==Les encodages binaires alternatifs==
Outre le binaire que nous venons de voir, il existe d'autres manières de coder des nombres avec des bits. Et nous allons les aborder dans cette section. Parmi celle-ci, nous parlerons du code Gray, de la représentation ''one hot'', du unaire et du ternaire. Nous en parlons car elles seront utiles dans la suite du cours, bien que de manière assez limitée. Autant nous passerons notre temps à parler du binaire normal, autant les représentations que nous allons voir sont aujourd'hui utilisées dans des cas assez spécifiques. Et elles sont plus courantes que vous ne le pensez.
===Le code Gray===
Le '''code Gray''' est un encodage binaire qui a une particularité très intéressante : deux nombres consécutifs n'ont qu'un seul bit de différence. Pour exemple, voici ce que donne le codage des 8 premiers entiers sur 3 bits :
{|class="wikitable"
! Décimal
! Binaire naturel
! Codage Gray
|-
|0||000||000
|-
|1||001||001
|-
|2||010||011
|-
|3||011||010
|-
|4||100||110
|-
|5||101||111
|-
|6||110||101
|-
|7||111||100
|}
Les utilisations du code Gray sont assez nombreuses, bien qu'on n'en croise pas tous les jours. Un exemple : le code Gray est très utile dans certains circuits appelés les compteurs, qui mémorisent un nombre et l'incrémentent (+1) ou le décrémentent (-1) suivant les besoins de l'utilisateur. Il est aussi utilisé dans des scénarios difficiles à expliquer ici (des codes correcteurs d'erreur ou des histoires de passages de domaines d'horloge). Mais le point important est que ce code sera absolument nécessaire dans quelques chapitres, quand nous parlerons des tables de Karnaugh, un concept important pour la conception de circuits électroniques. Ne passez pas à côté de cette section.
Pour construire ce code Gray, on peut procéder en suivant plusieurs méthodes, les deux plus connues étant la méthode du miroir et la méthode de l'inversion.
[[File:Binary-reflected Gray code construction.svg|vignette|Construction d'un code gray par la méthode du miroir.]]
La méthode du miroir est relativement simple. Pour connaître le code Gray des nombres codés sur n bits, il faut :
* partir du code Gray sur n-1 bits ;
* symétriser verticalement les nombres déjà obtenus (comme une réflexion dans un miroir) ;
* rajouter un 0 au début des anciens nombres, et un 1 au début des nouveaux nombres.
Il suffit de connaître le code Gray sur 1 bit pour appliquer la méthode : 0 est codé par le bit 0 et 1 par le bit 1.
Une autre méthode pour construire la suite des nombres en code Gray sur n bits est la méthode de l'inversion. Celle-ci permet de connaître le codage du nombre n à partir du codage du nombre n-1, comme la méthode du dessus. On part du nombre 0, systématiquement codé avec uniquement des zéros. Par la suite, on décide quel est le bit à inverser pour obtenir le nombre suivant, avec la règle suivante :
* si le nombre de 1 est pair, il faut inverser le dernier chiffre.
* si le nombre de 1 est impair, il faut localiser le 1 le plus à droite et inverser le chiffre situé à sa gauche.
Pour vous entraîner, essayez par vous-même avec 2, 3, voire 5.
===Les représentations ''one-hot'' et unaire===
La représentation ''one-hot'' et la représentation unaire sont deux représentations assez liées, mais légèrement différentes.
La '''représentation unaire''' est la représentation en base 1. Avec elle, le zéro est codé en mettant tous les bits du nombre à zéro, la valeur 1 est encodée avec la valeur 000...0001, le deux avec 000...0011, le trois avec 000...0111, etc. Pour résumer, le nombre N est encodé en mettant les N bits de poids faible à 1, à l'exception du zéro qui est encodé...par un zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! Unaire
|-
| 0 || 000 || 0000 0000
|-
| 1 || 001 || 0000 0001
|-
| 2 || 010 || 0000 0011
|-
| 3 || 011 || 0000 0111
|-
| 4 || 100 || 0000 1111
|-
| 5 || 101 || 0001 1111
|-
| 6 || 110 || 0011 1111
|-
| 7 || 111 || 0111 1111
|-
| 8 || 1000 || 1111 1111
|}
Pour convertir un nombre codé en unaire vers le binaire, il suffit de compter le nombre de 1. C'est ni plus ni moins ce que fait l'opération dite de ''population count'' dont nous avons parlé plus haut. La représentation unaire permet d'encoder N+1 valeurs sur N bits, car le zéro est encodé à part. Une valeur pour le zéro, plus une par bit.
Avec la '''représentation ''one-hot''''', un nombre est codé sur N bits, dont un seul bit est à 1. La position du 1 dans le nombre indique sa valeur. Précisément, la valeur encodée est égale au poids du bit à 1. En faisant cela, le zéro est codé en mettant le bit de poids faible à 1, la valeur 1 est codée avec les bits 00...00010, la valeur 2 est codée par les bits 00...000100, etc.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Elle permet d'encoder N valeurs différentes avec N bits, zéro inclus. On perd une valeur par rapport à l'unaire, car le zéro n'et pas encodé à part du reste. La traduction du binaire vers le ''one-hot'' est réalisé par un circuit nommé décodeur, que nous verrons d'ici quelques chapitres. De même, la traduction d'un nombre encodé en ''one-hot'' vers le binaire est réalisée par un circuit électronique appelé l'encodeur, qui est en quelque sorte l'inverse de l'encodeur. Les deux circuits sont très utilisés en électronique, ce sont des circuits de base, que nous n'aurons de cesse d'utiliser pour fabriquer d'autres circuits.
L'utilité de ces deux représentations n'est pas évidente. Mais sachez qu'elle le deviendra quand nous parlerons des circuits appelés les "compteurs", tout comme ce sera le cas pour le code Gray. Elles sont très utilisées dans des circuits appelés des machines à état, qui doivent incorporer des circuits compteurs efficients. Et ces représentations permettent d'avoir des circuits pour compter qui sont très simples, efficaces, rapides et économes en circuits électroniques. La représentation unaire sera aussi utile à la toute fin du cours, dans les chapitres liés à l'exécution dans le désordre. Il en sera fait référence quand nous parlerons de fenêtres d'instruction, d'émission dans l'ordre, de ''scoreboarding'', etc.
Il faut aussi noter qu'il a existé des ordinateur qui manipulaient des nombres codés en unaire. Enfin presque, la représentation utilisée était proche de l'unaire, mais ressemblait fortement. De tels ordinateurs étaient appelés des ''ordinateurs stochastiques'' et nous les aborderons à la toute fin de ce wikilivre, dans un chapitre annexe portant sur les ordinateurs ternaires (qui comptent en base 3) et unaires.
==Les encodages hybrides entre décimal et binaire==
Pour terminer, nous allons voir les représentations qui mixent binaire et décimal. Il s'agit en réalité d'encodage qui permettent de manipuler des nombres codés en décimal, mais les chiffres sont codés en utilisant des bits. L'encodage ''Binary Coded Decimal'' est le plus connu de cette catégorie, mais il y en a quelques autres, qui sont moins connus. De telles représentations étaient très utilisées au début de l'informatique, mais sont aujourd'hui tombées en désuétude. Pour désigner ces encodages, nous parlerons d''''encodages bit-décimaux''' : décimaux pour préciser qu'ils encodent des nombres codés en décimal, bit pour préciser que les chiffres sont codés avec des bits.
De tels encodages étaient utilisés sur les tous premiers ordinateurs pour faciliter le travail des programmeurs, mais aussi sur les premières calculettes. Ils sont utiles dans les applications où on doit manipuler chaque chiffre décimal séparément des autres. Un exemple classique est celui d'une horloge digitale. Ils ont aussi l'avantage de bien se marier avec les nombres à virgule. Les représentations des nombres à virgule fixe ou flottante ont le défaut que des arrondis peuvent survenir. Par exemple, la valeur 0,2 est codée comme suit en binaire normal : 0.00110011001100... et ainsi de suite jusqu’à l'infini. Avec les encodages bit-décimaux, on n'a pas ce problème, 0,2 étant codé 0000 , 0010.
Il a existé des ordinateurs qui travaillaient uniquement avec de tels encodages, appelés des '''ordinateurs décimaux'''. Ils étaient assez courants entre les années 60 et 70, même s'ils ne représentent pas la majorité des architectures de l'époque. Avec eux, la mémoire n'était pas organisée en octets, mais elle stockait des chiffres décimaux codés sur 5-6 bits. Le processeur faisait des calculs sur des chiffres bit-décimaux directement.
Leur grand avantage est leur très bonne performance dans les taches de bureautique, de comptabilité, et autres. Les processeurs de l'époque recevaient des entiers codés en bit-décimal de la part des entrées-sorties, et devaient les traiter. Les processeurs binaires devaient faire des conversions décimal-binaire pour communiquer avec les entrées-sorties, mais pas les processeurs décimaux. Le gain en performance pouvait être substantiel dans certaines applications.
Les ordinateurs décimaux se classent en deux sous-types bien précis. Les premiers gèrent des entiers qui ont un nombre de chiffres fixes. Par exemple, l'IBM 7070 gérait des entiers de 10 chiffres, plus un signe +/- pour les entiers signés. Le processeur faisait les calculs directement en bit-décimal, il gérait des entiers faisant environ 10-15 chiffres décimaux et savait faire des calculs avec de tels nombres. Le second sous-type effectue les calculs chiffre par chiffre et géraient des nombres de taille variable, sans limite de chiffres ! Nous reparlerons de ces derniers dans un chapitre ultérieur, quand nous parlerons de la différence entre byte et mot. Pour le moment, ne gardez à l'esprit que les processeurs gérant un nombre fixe de chiffres décimaux, plus simples à comprendre.
===Le ''Binary Coded Decimal''===
Le '''''Binary Coded Decimal''''', abrévié BCD, est une représentation qui mixe binaire et décimal. Avec cette représentation, les nombres sont écrits en décimal, comme nous en avons l'habitude dans la vie courante, sauf que chaque chiffre décimal est directement traduit en binaire sur 4 bits. Prenons l'exemple du nombre 624 : le 6, le 2 et le 4 sont codés en binaire séparément, ce qui donne 0110 0010 0100.
{| class="wikitable"
|+ Codage BCD
|-
! Nombre encodé (décimal)
! scope="col" colspan="4" | BCD
|-
| 0 || 0 || 0 || 0 || 0
|-
| 1 || 0 || 0 || 0 || 1
|-
| 2 || 0 || 0 || 1 || 0
|-
| 3 || 0 || 0 || 1 || 1
|-
| 4 || 0 || 1 || 0 || 0
|-
| 5 || 0 || 1 || 0 || 1
|-
| 6 || 0 || 1 || 1 || 0
|-
| 7 || 0 || 1 || 1 || 1
|-
| 8 || 1 || 0 || 0 || 0
|-
| 9 || 1 || 0 || 0 || 1
|}
On peut remarquer que 4 bits permettent de coder 16 valeurs, là où il n'y a que 10 chiffres. Dans le BCD proprement dit, les combinaisons de bits qui correspondent à 10, 11, 12, 13, 14 ou 15 ne sont tout simplement pas prises en compte. Sur quelques ordinateurs, ces combinaisons codent des chiffres décimaux en double : certains chiffres pouvaient être codés de deux manières différentes. Il est aussi possible d'utiliser ces valeurs pour coder quelque chose d'autre que des chiffres. Par exemple, il est possible de les utiliser pour coder un signe + ou -, afin de gérer les entiers relatifs. Une autre possibilité, complémentaire de la précédente, utilise ces valeurs en trop pour coder une virgule, afin de gérer les nombres non-entiers. Les possibilités sont nombreuses.
Le support du BCD implique souvent que le processeur supporte des '''opérations BCD''', à savoir des opérations capables de travailler sur des opérandes en BCD et de donner un résultat en BCD. Il faut bien faire la différence entre les opérations en binaire et les opérations en BCD. Par exemple, on n'effectue pas une addition de la même manière en binaire et en décimal/BCD, même si les grandes lignes sont presque identiques. Les différences font que les processeurs doivent avoir des opérations différentes pour les deux encodages, de la même manière que les processeurs gèrent les opérations sur les flottants et les entiers séparément.
Le support du codage BCD est abandonné de nos jours, c'est surtout quelque chose qu'on trouve sur les anciens processeurs. Les architectures 8 et 16 bits supportaient à la fois des opérations binaires et des opérations BCD. Mais il a aussi existé une méthode intermédiaire, qui utilisait des additions binaires normales sur des opérandes BCD. L'idée est que le résultat de l'addition est incorrect, car les valeurs 10 à 15 peuvent apparaître comme chiffre dans le résultat. Mais on peut le corriger pour obtenir le résultat exact en BCD. Pour résumé, les processeurs faisaient des additions en binaire, et corrigeaient le résultat avec une opération spécifique pour obtenir un résultat en BCD. Nous en reparlerons dans le chapitre sur le langage machine et l'assembleur, dans lequel nous étudierons les différentes opérations que supporte un processeur.
===Les encodages compacts du BCD===
Des variantes du BCD visent à réduire le nombre de bits utilisés pour encoder un nombre décimal. Nous allons les appeler les '''encodages BCD compacts'''. Avec certaines variantes, on peut utiliser seulement 7 bits pour coder deux chiffres décimaux, au lieu de 8 bits en BCD normal. Idem pour les nombres à 3 chiffres décimaux, qui prennent 10 bits au lieu de 12. Cette économie est réalisée par une variante du BCD assez compliquée, appelée l''''encodage Chen-Ho'''. Une alternative, appelée le ''Densely Packed Decimal'', arrive à une compression identique, mais avec quelques avantages au niveau de l'encodage. Ces encodages sont cependant assez compliqués à expliquer, surtout à ce niveau du cours, aussi je me contente de simplement mentionner leur existence.
Les encodages BCD compacts sont utilisés par les programmeurs pour stocker des données dans un fichier, en mémoire, mais guère plus. Ils ne sont pas gérés par le processeur directement, on ne peut pas faire de calculs avec, le processeur ne gére pas d'opération BCD supportant de tels encodages. C'est théoriquement possible, mais ça n'a jamais été implémenté dans un processeur, le cout en circuit n'en valait pas la chandelle. Pour faire des calculs sur des nombres en BCD compact, on doit les décompresser et les convertir en BCD normal ou en binaire, puis faire les calculs avec des opérations BCD/binaire usuelles.
===L'encodage ''Excess-3''===
La représentation '''''Excess-3''''' (XS-3) est une variante du BCD, qui a autrefois était utilisée sur d'anciens ordinateurs décimaux. Il s'agit d'une sorte d'hybride entre une représentation par excès et BCD. Chaque chiffre décimal est codé sur plusieurs bits en utilisant une représentation binaire par excès. Le biais est de 3 pour la représentation XS-3, il en existe des variantes avec un excès de 4, 5, mais elles sont moins utilisées. Avec elle, la conversion d'un chiffre décimal se fait comme suit : on prend le chiffre décimal, on ajoute 3, puis on traduit en binaire.
{| class="wikitable"
|+ Codage en ''Excess-3''
|-
! Décimal
! scope="col" colspan="4" | Binaire
|-
| -3 || 0 || 0 || 0 || 0
|-
| -2 || 0 || 0 || 0 || 1
|-
| -1 || 0 || 0 || 1 || 0
|-
| 0 || 0 || 0 || 1 || 1
|-
| 1 || 0 || 1 || 0 || 0
|-
| 2 || 0 || 1 || 0 || 1
|-
| 3 || 0 || 1 || 1 || 0
|-
| 4 || 0 || 1 || 1 || 1
|-
| 5 || 1 || 0 || 0 || 0
|-
| 6 || 1 || 0 || 0 || 1
|-
| 7 || 1 || 0 || 1 || 0
|-
| 8 || 1 || 0 || 1 || 1
|-
| 9 || 1 || 1 || 0 || 0
|-
| 10 || 1 || 1 || 0 || 1
|-
| 11 || 1 || 1 || 1 || 0
|-
| 12 || 1 || 1 || 1 || 1
|}
L'avantage de cette représentation est que l'on peut facilement calculer les soustractions en utilisant une méthode bien précise (celle du complément à 10, je ne détaille pas plus). Le défaut est que le calcul des additions est légèrement plus complexe.
L'XS-3 a été utilisé sur quelques ordinateurs décimaux assez anciens, notamment sur l'UNIVAC I et II.
===Les codes bi-quinaires===
Les '''codes bi-quinaires''' utilisent un mélange entre encodage binaire, encodage ''one-hot'' et encodage décimal. L'idée de base derrière ces codes est d'utiliser la représentation ''one-hot'' pour coder un chiffre BCD. Le problème est que cela demande d'utiliser 10 bits pour encoder un chiffre BCD, ce qui est beaucoup. L'encodage bi-quinaire optimise le tout de manière à diviser par deux le nombre de bits requis pour l'encodage.
Pour simplifier, un chiffre encodé en bi-quinaire est composé de deux parties : une partie binaire de un bit, suivie par d'une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits pour encoder les valeurs 0, 1, 2, 3 et 4. La partie binaire indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9 : 0 à 4 si la partie binaire vaut 0, 5 à 9 si elle vaut 1. La valeur du chiffre se calcule comme suit :
* si la partie binaire vaut 0, elle est égale à la partie quinaire ;
* si la partie binaire vaut 1, on ajoute 5 à la partie quinaire.
L'originalité de ce codage est qu'il permet de facilement compter sur ses doigts : on a deux mains, cinq doigts. De nombreuses langues utilisent ce système pour coder des nombres, comme le Wolof. Et ce système est utilisé dans certains pays pour compter sur ses doigts, voire dans la vie de tous les jours.
[[File:Code Biquinaer.svg|vignette|Code bi-quinaire où partie binaire et quinaire sont encodées en représentation ''one-hot''.]]
L'encodage abordé au-dessus est une version simplifiée de l'encodage bi-quinaire, il en existe d'autres variantes. Le bi-quinaire vu juste au-dessus encode la partie binaire en binaire, et la partie quinaire en ''one-hot''. Mais il est aussi possible d'inverser l'encodage, à savoir encoder la partie binaire avec une représentation ''one-hot'', alors que la partie bi-quinaire est encodée en binaire normal.
Cependant, la version la plus intéressante utilise l'encodage ''one-hot'' pour les deux ! Elle est illustrée ci-contre. Avec cette dernière, un chiffre BCD est encodé sur 7 bits, dont 2 sont obligatoirement à 1 et les 5 restants sont à 0. Cette propriété permet de détecter qu'un bit a été inversé suite à une erreur de transmission ou un problème de stockage. Si un bit est inversé, le résultat aura 1 bit à 1 et 6 à 0, ou alors 3 bits à 1 et 4 à 0, ce qui ne colle pas. Le résultat après inversion ne donnera pas un chiffre bi-quinaire valide.
Une autre variante du bi-quinaire a été utilisée sur l'IBM 1401. Avec elle, la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre encodé par la partie quinaire. L'avantage de ce système est que la gestion des retenues lors des additions était grandement simplifiée.
Le bi-quinaire et ses variantes ont été utilisés sur d'anciens ordinateurs, dont l'IBM 650, l'UNIVAC Solid State et le UNIVAC LARC. La plupart utilisaient des variantes de l'encodage bi-quinaire proprement dit. Pour rentrer vraiment dans le détail, voici les encodages utilisés sur ces machines. La lecture est facultative, il est possible de passer directement à la section suivante :
{| class="wikitable"
|-
! !! IBM 650 !! Remington Rand 409 !! UNIVAC Solid State !! UNIVAC LARC
|-
| Chiffre || 1357-9 bits || 05-01234 || p-5-421 bits || p-5-qqq bits
|-
| 0 || 10-10000 || 0000-0 || 1-0-000 || 1-0-000
|-
| 1 || 10-01000 || 1000-0 || 0-0-001 || 0-0-001
|-
| 2 || 10-00100 || 1000-1 || 0-0-010 || 1-0-011
|-
| 3 || 10-00010 || 0100-0 || 1-0-011 || 0-0-111
|-
| 4 || 10-00001 || 0100-1 || 0-0-100 || 1-0-110
|-
| 5 || 01-10000 || 0010-0 || 0-1-000 || 0-1-000
|-
| 6 || 01-01000 || 0010-1 || 1-1-001 || 1-1-001
|-
| 7 || 01-00100 || 0001-0 || 1-1-010|| 0-1-011
|-
| 8 || 01-00010 || 0001-1 || 0-1-011 || 1-1-111
|-
| 9 || 01-00001 || 0000-1 || 1-1-100|| 0-1-110
|}
==Annexe : pourquoi l'octet existe==
Nous avons vu plus haut que les nombres encodés en binaire sont découpés en octets, des paquets de 8 bits. Et les octets sont présents partout dans la conception d'un ordinateur. Par exemple, la mémoire RAM d'un ordinateur moderne est organisée en octets. Elle contient des cases mémoire, chacune contenant un octet. Les octets sont numérotés, ce qui permet de sélectionner un octet parmi tous les autres. La capacité de la mémoire est le nombre d'octets qu'elle contient, elle s'exprime en kilo-octets (1024 octets), mébi-octets (1024 kilo-octets), gibi-octets (1024 mébi-octets). Les octets sont donc présents partout dans l'informatique moderne.
{|class="wikitable"
|+ Contenu de la RAM, simplifié
|-
! Numéro
! Octet
|-
! 0
| 0000 1111
|-
! 1
| 0101 0101
|-
! 2
| 0111 0110
|-
! 3
| 0011 1010
|-
! 4
| 1001 1001
|-
! ...
| ...
|-
! 1023
| 0110 1010
|-
! 1024
| 0001 1110
|}
Mais pourquoi est-ce que les nombres encodés en binaire sont souvent découpés en octets ? Pourquoi ne pas utiliser des groupes de 3, 5, 6, 7, 9, 10 bits ? La question est d'autant plus légitime que d'anciens ordinateurs faisaient ainsi. Leurs mémoires étaient découpé en cases mémoire, mais chacune faisait 7 ou 9 bits, rien d'impossible à cela. À vrai dire, l'usage des octets au tout début de l'informatique était assez rare. Il y a eu un basculement progressif vers l'octet pendant les années 80-90. Reste à expliquer pourquoi. Si on devait résumer, les deux raisons principales sont le support du BCD et le support des caractères pour le texte. Voyons d'abord l'encodage des caractères en binaire.
===L'encodage du texte : ASCII et autres encodages===
Pour les caractères, les encodages se sont concentrés sur l'encodage des alphabets occidentaux. Ils demandaient d'encoder au minimum toutes les lettres, en majuscules et minuscules, plus les chiffres, une dizaine de caractères pour la ponctuation. À cela, il faut ajouter des symboles utiles comme %, µ, $, £, #, {} et bien d'autres. Au minimum, cela fait au minimum 52 lettres (26 lettres doublées pour les majuscules) + 10 chiffres + une dizaine de caractères pour la ponctuation : cela fait plus de 72 valeurs à encoder. Pour encoder cela en binaire, le minimum est de 7 bits, ce qui permet d'encoder 128 caractères : les 72 caractères minimum, plus quelques autres. L'encodage ASCII utilisait d'ailleurs 7 bits pour encoder des nombres.
On pourrait croire que c'était une raison suffisante pour utiliser des groupes de 7 bits. Mais il y a quelques problèmes avec cela. Premièrement, les encodages 7 bits pour le texte marchent bien pour gérer la langue anglaise et la plupart des langues occidentales, mais d'autres langues ont besoin d'encoder des caractères locaux. Les 128 caractères de l'ASCII ne sont pas suffisant, ce qui demande de passer de 7 à 8 bits. On retombe donc sur un octet ! D'ailleurs, les encodages ASCII améliorés utilisaient les 127 premières valeurs pour l'ASCII normal, mais les 128 suivantes encodaient d'autres caractères. Les encodages en question varient d'un pays ou d'une langue à l'autre, mais ils sont terriblement importants, ce qui est un argument pour l'usage des octets.
===Le support des données encodées sur un ''nibble''===
Une autre raison pour l'usage des octets est qu'il est facile de les découper en deux ''nibbles''. Pour rappel, les ''nibbles'' sont des groupes de 4 bits. Et il se trouve que de nombreuses données sont encodées sur 4 bits, ni plus ni moins. Les ''nibbles'' sont très important pour le support du BCD et de l’hexadécimal. Et c'était un avantage important au début de l'informatique, moins maintenant. Un octet regroupe deux ''nibbles'' : un ''nibble'' de poids faible, un ''nibble'' de poids fort.
[[File:Nibbles in a byte.PNG|centre|vignette|upright=2|Nibbles dans un octet.]]
Un chiffre BCD occupe un ''nibble'', ce qui fait que la taille idéale de l'unité mémoire doit être un multiple de 4 bits : 4 bits, 8 bits, 12 bits, 16 bits, etc. Un ''nibble'' de 4 bits est insuffisant pour encoder des caractères, 8 bits fonctionne parfaitement, pas besoin d'aller plus loin. De plus, l'usage des ''nibbles'' permet de gérer la représentation hexadécimale très facilement. Un chiffre hexadécimal est encodé sur exactement un ''nibble'', un octet regroupe deux chiffres hexadécimaux, deux octets 4 chiffres hexadécimaux, etc. Enfin, 8 bits est une puissance de deux, ce qui simplifie grandement l'encodage des entiers en binaire est est une source de nombreuses simplifications. Les circuits de l'ordinateur sont plus simple à concevoir quand ils manipulent des opérandes de 2, 4, 8, 16, 32, 64, 128 bits.
===L'influence sur la taille des opérandes===
Les anciens ordinateurs étaient conçus pour le calcul scientifique, ou du moins des applications similaires. Ils n'avaient pas besoin de supporter le BCD, ni même de gérer du texte. Ils ne faisaient que des calculs et utilisaient des opérandes flottantes. La taille des opérandes était dictée par des considérations calculatoires. La norme à l'époque était que les calculs scientifiques devaient gérer des opérandes pouvant aller jusqu'à 10 chiffres décimaux. Et cela demande d'utiliser des opérandes entiers de 35 bits, avec un bit en plus pour encoder des opérandes positives ou négatives. Les 36 bits étaient parfois découpés en 6 groupes de 6 bits chacun, au lieu des octets de 8 bits.
Mais ces groupes de 6 bits étaient souvent peu utilisés. Prenons l'exemple des mémoires RAM. Plus haut, j'ai dit qu'une mémoire RAM normale est composée de plusieurs octets, tous numérotés. Intuitivement, vous vous dites que les anciens ''mainframes'' remplaçaient ces octets par des groupes de 6 bits dans la RAM. Mais en réalité, l'équivalent des RAM de l'époque utilisait des groupes de 36 bits. les groupes de 36 bits étaient aussi numérotés, les numéros étant appelés des adresses mémoire. Par contre, les adresses/numéros étaient codés sur 12 ou 18 bits, c'est à dire sur 2 ou 3 groupes de 6 bits. La raison est que ces numéros sont manipulés par le processeur, qui gère des opérandes de 36 bits. L'idée est que le numéro ne prend que la moitié ou un tiers des 36 bits gérés par le processeur.
Avec l'apparition de l'informatique commerciale, les ordinateurs ont du s'adapter aux besoins des entreprises, pour gérer la comptabilité, des applications financières, des documents texte, etc. Et cela demandait de supporter à la fois le BCD et les caractères/lettres. Il a fallu trouver une unité mémoire capable de gérer à la fois nombres entiers, chiffres BCD et caractères. L'octet était parfait pour ça. Les ''mainframes'' sont adaptés en supportant des caractères codés sur 6, 7 ou 8 bits. Mais le support demandait d'utiliser des circuits qui découpaient un groupe de 36 bits en plusieurs caractères ou en plusieurs chiffres BCD. Le tout demandait des circuits de décalage et de masquage assez complexes. Les ordinateurs 8 et 16 bits manipulaient des octets nativement : leur mémoire RAM gérait des octets, le processeur traitait des opérandes de 8, 16 ou 32 bits. Ils n'avaient pas besoin de matériel complexe pour extraire un caractère ou un chiffre BCD d'un octet/doublet/multiplet. C'était un avantage que les anciens ''mainframes'' n'avaient pas.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'encodage des données
| prevText=L'encodage des données
| next=Les codes de détection/correction d'erreur
| nextText=Les codes de détection/correction d'erreur
}}
</noinclude>
rbrbelf2cyrv2vpqe6bqo3e2hj2g0qn
775643
775642
2026-11-02T19:33:05Z
Mewtow
31375
/* Le codage des nombres flottants et la norme IEEE 754 */
775643
wikitext
text/x-wiki
Dans le chapitre précédent, nous avons vu que les ordinateurs actuels utilisent un codage binaire. Ce codage binaire ne vous est peut-être pas familier. Aussi, dans ce chapitre, nous allons apprendre comment coder des nombres en binaire. Nous allons commencer par le cas le plus simple : les nombres positifs. Par la suite, nous aborderons les nombres négatifs. Et nous terminerons par les nombres à virgules, appelés aussi nombres flottants.
==Le codage des nombres entiers positifs==
Pour coder des nombres entiers positifs, il existe plusieurs méthodes : le binaire, l’hexadécimal, le code Gray, le décimal codé binaire et bien d'autres encore. La plus connue est certainement le binaire, secondée par l'hexadécimal, les autres étant plus anecdotiques. Pour comprendre ce qu'est le binaire, il nous faut faire un rappel sur les nombres entiers tel que vous les avez appris en primaire, à savoir les entiers écrits en décimal. Prenons un nombre écrit en décimal : le chiffre le plus à droite est le chiffre des unités, celui à côté est pour les dizaines, suivi du chiffre des centaines, et ainsi de suite. Dans un tel nombre :
* on utilise une dizaine de chiffres, de 0 à 9 ;
* chaque chiffre est multiplié par une puissance de 10 : 1, 10, 100, 1000, etc. ;
* la position d'un chiffre dans le nombre indique par quelle puissance de 10 il faut le multiplier : le chiffre des unités doit être multiplié par 1, celui des dizaines par 10, celui des centaines par 100, et ainsi de suite.
Exemple avec le nombre 1337 :
<math>1337 = 1 \times 1000 + 3 \times 100 + 3 \times 10 + 7 \times 1</math>
Pour résumer, un nombre en décimal s'écrit comme la somme de produits, chaque produit multipliant un chiffre par une puissance de 10. On dit alors que le nombre est en base 10.
<math>1337 = 1 \times 10^3 + 3 \times 10^2 + 3 \times 10^1 + 7 \times 10^0</math>
Ce qui peut être fait avec des puissances de 10 peut être fait avec des puissances de 2, 3, 4, 125, etc : n'importe quel nombre entier strictement positif peut servir de '''base'''. En informatique, on utilise rarement la base 10 à laquelle nous sommes tant habitués. On utilise à la place deux autres bases :
* La base 2 (système binaire) : les chiffres utilisés sont 0 et 1 ;
* La base 16 (système hexadécimal) : les chiffres utilisés sont 0, 1, 2, 3, 4, 5, 6, 7, 8 et 9 ; auxquels s'ajoutent les six premières lettres de notre alphabet : A, B, C, D, E et F.
===Le système binaire===
En binaire, on compte en base 2. Cela veut dire qu'au lieu d'utiliser des puissances de 10 comme en décimal, on utilise des puissances de deux : n'importe quel nombre entier peut être écrit sous la forme d'une somme de puissances de 2. Par exemple 6 s'écrira donc 0110 en binaire : <math>0 \times 2^3 + 1 \times 2^2 + 1 \times 2^1 + 0 \times 2^0 = 6</math>. On peut remarquer que le binaire n'autorise que deux chiffres, à savoir 0 ou 1 : ces chiffres binaires sont appelés des '''bits''' (abréviation de Binary Digit). Pour simplifier, on peut dire qu'un bit est un truc qui vaut 0 ou 1. Pour résumer, tout nombre en binaire s'écrit sous la forme d'un produit entre bits et puissances de deux de la forme :
<math>a_0 \times 2^0 + a_1 \times 2^1 + a_2 \times 2^2 + a_3 \times 2^3 + a_4 \times 2^4 +...+ a_n \times 2^n</math>
Les coefficients <math>a_0, a_1, a_2</math> sont les bits, l'exposant n qui correspond à un bit <math>a_n</math> est appelé le '''poids''' du bit.
====La terminologie du binaire====
En informatique, il est rare que l'on code une information sur un seul bit. Dans la plupart des cas, l'ordinateur manipule des nombres codés sur plusieurs bits. Les informaticiens ont donné des noms aux groupes de bits suivant leur taille. Le plus connu est certainement l''''octet''', qui désigne un groupe de 8 bits. Moins connu, on parle de '''''nibble''''' pour un groupe de 4 bits (un demi-octet), de '''doublet''' pour un groupe de 16 bits (deux octets) et de '''quadruplet''' pour un groupe de 32 bits (quatre octets).
Précisons qu'en anglais, le terme ''byte'' n'est pas synonyme d'octet. En réalité, le terme ''octet'' marche aussi bien en français qu'en anglais. Quant au terme ''byte'', il désigne un concept complètement différent, que nous aborderons plus tard (c'est la plus petite unité de mémoire que le processeur peut adresser). Il a existé dans le passé des ordinateurs où le ''byte'' faisait 4, 7, 9, 16, voire 48 bits, par exemple. Il a même existé des ordinateurs où le ''byte'' faisait exactement 1 bit ! Mais sur presque tous les ordinateurs modernes, un ''byte'' fait effectivement 8 bits, ce qui fait que le terme ''byte'' est parfois utilisé en lieu et place d'''octet''. Mais c'est un abus de langage, attention aux confusions ! Dans ce cours, nous parlerons d'octet pour désigner un groupe de 8 bits, en réservant le terme ''byte'' pour sa véritable signification.
À l'intérieur d'un nombre, le '''bit de poids faible''' est celui qui est le plus à droite du nombre, alors que le '''bit de poids fort''' est celui non nul qui est placé le plus à gauche, comme illustré dans le schéma ci-dessous.
C'est le même principe que l'écriture des nombres en base décimale : le chiffre le plus significatif à gauche, et le moins significatif à droite (l'unité ou les décimales après la virgule).
[[File:Msb.svg|centre|vignette|upright=2|Bit de poids fort.]]
[[File:Lsb.svg|centre|vignette|upright=2|Bit de poids faible.]]
Cette terminologie s'applique aussi pour les bits à l'intérieur d'un octet, d'un ''nibble'', d'un doublet ou d'un quadruplet. Pour un nombre codés sur plusieurs octets, on peut aussi parler de l'octet de poids fort et de l'octet de poids faible, du doublet de poids fort ou de poids faible, etc.
====La traduction binaire→décimal====
Pour traduire un nombre binaire en décimal, il faut juste se rappeler que la position d'un bit indique par quelle puissance il faut le multiplier. Ainsi, le chiffre le plus à droite est le chiffre des unités : il doit être multiplié par 1 (<math>2^0</math>). Le chiffre situé immédiatement à gauche du chiffre des unités doit être multiplié par 2 (<math>2^1</math>). Le chiffre encore à gauche doit être multiplié par 4 (<math>2^2</math>), et ainsi de suite. Mathématiquement, on peut dire que le énième bit en partant de la droite doit être multiplié par <math>2^{n-1}</math>. Par exemple, la valeur du nombre noté 1011 en binaire est de <math>8 \times 1 + 4 \times 0 + 2 \times 1 + 1 \times 1 = 11</math>.
[[File:Value of digits in the Binary numeral system.svg|centre|vignette|upright=2|Valeur des chffres dans le système de numération binaire.]]
====La traduction décimal→binaire====
La traduction inverse, du décimal au binaire, demande d'effectuer des divisions successives par deux. Les divisions en question sont des divisions euclidiennes, avec un reste et un quotient. En lisant les restes des divisions dans un certain sens, on obtient le nombre en binaire. Voici comment il faut procéder, pour traduire le nombre 34 :
[[File:Exemple d'illustration de la méthode de conversion décimal vers binaire.png|centre|vignette|upright=2|Exemple d'illustration de la méthode de conversion décimal vers binaire.]]
====Quelques opérations en binaire====
Maintenant que l'on sait coder des nombres en binaire normal, il est utile de savoir comment faire quelques opérations usuelles en binaire. Nous utiliserons les acquis de cette section dans la suite du chapitre, bien que de manière assez marginale.
La première opération est assez spécifique au binaire. Il s'agit d'une opération qui inverse les bits d'un nombre : les 0 deviennent des 1 et réciproquement. Par exemple, le nombre 0001 1001 devient 1110 0110. Elle porte plusieurs noms : opération NOT, opération NON, complémentation, etc. Nous parlerons de '''complémentation''' ou d'opération NOT dans ce qui suit. Beaucoup d'ordinateurs gèrent cette opération, ils savent la faire en un seul calcul. Il faut dire que c'est une opération assez utile, bien que nous ne pouvons pas encore expliquer pourquoi.
[[File:Binary Addition Demonstration.svg|vignette|Exemple d'addition en binaire.]]
La seconde opération à aborder est l'addition. Elle se fait en binaire de la même manière qu'en décimal : Pour faire une addition en binaire, on additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. Sauf que l'on additionne des bits. Heureusement, la table d'addition est très simple en binaire :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
La troisième opération est une variante de l'addition appelée l''''opération XOR''', notée <math>\oplus</math>. Il s'agit d'une addition dans laquelle on ne propage pas les retenues. L'addition des deux bits des opérandes se fait normalement, mais les retenues sont simplement oubliées, on n'en tient pas compte. Le résultat est que l'addition se résume à appliquer la table d'addition précédente :
* 0 <math>\oplus</math> 0 = 0 ;
* 0 <math>\oplus</math> 1 = 1 ;
* 1 <math>\oplus</math> 0 = 1 ;
* 1 <math>\oplus</math> 1 = 0.
Pour résumer, le résultat vaut 1 si les deux bits sont différents, 0 s'ils sont identiques. L'opération XOR sera utilisée rapidement dans le chapitre suivant, quand nous parlerons rapidement du mot de parité. Et elle sera beaucoup utilisée dans la suite du cours, nous en feront fortement usage. Pour le moment, mémorisez juste cette opération, elle n'a rien de compliqué.
Une dernière opération est l'opération de '''''population count'''''. Il s'agit ni plus ni moins que de compter le nombre de bits qui sont à 1 dans un nombre. Par exemple, pour le nombre 0110 0010 1101 1110, elle donne pour résultat 9. Elle est utilisée dans certaines applications, comme le calcul de certains codes correcteurs d'erreur, comme on le verra dans le chapitre suivant. Elle est supportée sur de nombreux ordinateurs, encore que cela dépende du processeur considéré. Il s'agit cependant d'une opération assez courante, supportée par les processeurs ARM, les processeurs x86 modernes (ceux qui gèrent le SSE), et quelques autres.
===L'hexadécimal===
L’hexadécimal est basé sur le même principe que le binaire, sauf qu'il utilise les 16 chiffres suivants :
{| class="wikitable"
|- style="color:#8080ffff; text-align:center;"
! Chiffre hexadécimal
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || A || B || C || D || E || F
|- style="text-align:center;"
! Nombre décimal correspondant
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 10 || 11 || 12 || 13 || 14 || 15
|- style="text-align:center;"
! Notation binaire
| 0000 || 0001 || 0010 || 0011 || 0100 || 0101 || 0110 || 0111 || 1000 || 1001 || 1010 || 1011 || 1100 || 1101 || 1110 || 1111
|}
Dans les textes, afin de différencier les nombres décimaux des nombres hexadécimaux, les nombres hexadécimaux sont suivis par un petit h, indiqué en indice. Si cette notation n'existait pas, des nombres comme 2546 seraient ambigus : on ne saurait pas dire sans autre indication s'ils sont écrits en décimal ou en hexadécimal. Avec la notation, on sait de suite que 2546 est en décimal et que 2546h est en hexadécimal.
Dans les codes sources des programmes, la notation diffère selon le langage de programmation.
Certains supportent le suffixe <code>h</code> pour les nombres hexadécimaux, d'autres utilisent un préfixe <code>0x</code> ou <code>0h</code>.
Pour convertir un nombre hexadécimal en décimal, il suffit de multiplier chaque chiffre par la puissance de 16 qui lui est attribuée. Là encore, la position d'un chiffre indique par quelle puissance celui-ci doit être multiplié : le chiffre le plus à droite est celui des unités, le second chiffre le plus à droite doit être multiplié par 16, le troisième chiffre en partant de la droite doit être multiplié par 256 (16 * 16) et ainsi de suite. La technique pour convertir un nombre décimal vers de l’hexadécimal est similaire à celle utilisée pour traduire un nombre du décimal vers le binaire. On retrouve une suite de divisions successives, mais cette fois-ci les divisions ne sont pas des divisions par 2 : ce sont des divisions par 16.
La conversion inverse, de l'hexadécimal vers le binaire est très simple, nettement plus simple que les autres conversions. Pour passer de l'hexadécimal au binaire, il suffit de traduire chaque chiffre en sa valeur binaire, celle indiquée dans le tableau au tout début du paragraphe nommé « Hexadécimal ». Une fois cela fait, il suffit de faire le remplacement. La traduction inverse est tout aussi simple : il suffit de grouper les bits du nombre par 4, en commençant par la droite (si un groupe est incomplet, on le remplit avec des zéros). Il suffit alors de remplacer le groupe de 4 bits par le chiffre hexadécimal qui correspond.
==Interlude propédeutique : la capacité d'un entier et les débordements d'entiers==
Dans la section précédente, nous avons vu comment coder des entiers positifs en binaire ou dans des représentations proches. La logique voudrait que l'on aborde ensuite le codage des entiers négatifs. Mais nous allons déroger à cette logique simple, pour des raisons pédagogiques. Nous allons faire un interlude qui introduira des notions utiles pour la suite du chapitre. De plus, ces concepts seront abordés de nombreuses fois dans ce wikilivre et l'introduire ici est de loin la solution idéale.
===Les ordinateurs manipulent des nombres codés sur un nombre fixe de bits===
Vous avez certainement déjà entendu parler de processeurs 32 ou 64 bits. Et si vous avez joué aux jeux vidéos durant votre jeunesse et êtes assez agé, vous avez entendu parler de consoles de jeu 8 bits, 16 bits, 32 bits, voire 64 bits (pour la Jaguar, et c'était un peu trompeur). Derrière cette appellation qu'on retrouvait autrefois comme argument commercial dans la presse se cache un concept simple. Tout ordinateur manipule des nombres entiers dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Une console 16 bits manipulait des entiers codés en binaire sur 16 bits, pas un de plus, pas un de moins. Pareil pour les anciens ordinateurs 32 bits, qui manipulaient des nombres entiers codés sur 32 bits.
Aujourd'hui, les ordinateurs modernes utilisent presque un nombre de bits qui est une puissance de 2 : 8, 16, 32, 64, 128, 256, voire 512 bits. Mais cette règle souffre évidemment d'exceptions. Aux tout débuts de l'informatique, certaines machines utilisaient 3, 7, 13, 17, 23, 36 et 48 bits ; mais elles sont aujourd'hui tombées en désuétude. De nos jours, il ne reste que les processeurs dédiés au traitement de signal audio, que l'on trouve dans les chaînes HIFI, les décodeurs TNT, les lecteurs DVD, etc. Ceux-ci utilisent des nombres entiers de 24 bits, car l'information audio est souvent codée par des nombres de 24 bits.
Anecdote amusante, il a existé des ordinateurs de 1 bit, qui sont capables de manipuler des nombres codés sur 1 bit, pas plus. Un exemple est le Motorola MC14500B, commercialisé de 1976.
====Le lien entre nombre de bits et valeurs codables====
Évidemment, on ne peut pas coder tous les entiers possibles et imaginables avec seulement 8 bits, ou 16 bits. Et il parait intuitif que l'on ait plus de valeurs codables sur 16 bits qu'avec 8 bits, par exemple. Plus le nombre de bits est important, plus on pourra coder de valeurs entières différentes. Mais combien de plus ? Par exemple, si je passe de 8 bits à 16 bits, est-ce que le nombre de valeurs que l'on peut coder double, quadruple, pentuple ? De même, combien de valeurs différentes on peut coder avec <math>n</math> bits. Par exemple, combien de nombres différents peut-on coder avec 4, 8 ou 16 bits ? La section précédente vous l'expliquer.
Avec <math>n</math> bits, on peut coder <math>2^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>2^n - 1</math>. N'oubliez pas cette formule : elle sera assez utile dans la suite de ce tutoriel. Pour exemple, on peut coder 16 valeurs avec 4 bits, qui vont de 0 à 15. De même, on peut coder 256 valeurs avec un octet, qui vont de 0 à 255. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de bits
! Nombre de valeurs codables
|-
! 4
| 16
|-
! 8
| 256
|-
! 16
| 65 536
|-
! 32
| 4 294 967 296
|-
! 64
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien de bits il faut pour coder une valeur quelconque, que nous noterons N. Pour cela, il faut utiliser la formule précédente, mais à l'envers. On cherche alors <math>n</math> tel que <math>N = 2^n</math>. L'opération qui donne <math>n</math> est appelée le logarithme, et plus précisément un logarithme en base 2, noté <math>\log_2{(X)}</math>. Le problème est que le résultat du logarithme ne tombe juste que si le nombre X est une puissance de 2. Si ce n'est pas le cas, le résultat est un nombre à virgule, ce qui n'a pas de sens pratique. Par exemple, la formule nous dit que pour coder le nombre 13, on a besoin de 3,70043971814 bits, ce qui est impossible. Pour que le résultat ait un sens, il faut arrondir à l'entier supérieur. Pour l'exemple précédent, les 3,70043971814 bits s'arrondissent en 4 bits.
====Le lien entre nombre de chiffres hexadécimaux et valeurs codables====
Maintenant, voyons combien de valeurs peut-on coder avec <math>n</math> chiffres hexadécimaux. La réponse n'est pas très différente de celle obtenue en binaire, si ce n'est qu'il faut remplacer le 2 par un 16 dans la formule précédente. Avec <math>n</math> chiffres hexadécimaux, on peut coder <math>16^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>16^n - 1</math>. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de chiffres héxadécimaux
! Nombre de valeurs codables
|-
! 1 (4 bits)
| 16
|-
! 2 (8 bits)
| 256
|-
! 4 (16 bits)
| 65 536
|-
! 8 (32 bits)
| 4 294 967 296
|-
! 16 (64 bits)
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien faut-il de chiffres hexadécimaux pour coder une valeur quelconque en hexadécimal. La formule est là encore la même qu'en binaire, sauf qu'on remplace le 2 par un 16. Pour trouver le nombre de chiffres hexadécimaux pour encoder un nombre X, il faut calculer <math>\log_{16}{(X)}</math>. Notons que le logarithme utilisé est un logarithme en base 16, et non un logarithme en base 2, comme pour le binaire. Là encore, le résultat ne tombe juste que si le nombre X est une puissance de 16 et il faut arrondir à l'entier supérieur si ce n'est pas le cas.
Une propriété mathématique des logarithmes nous dit que l'on peut passer d'un logarithme en base X et à un logarithme en base Y avec une simple division, en utilisant la formule suivante :
: <math>\log_X{(N)} = {\log_{Y}{(N)} \over \log_Y(X)}</math>
Dans le cas qui nous intéresse, on a Y = 2 et X = 16, ce qui donne :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over \log_2(16)}</math>
Or, <math>\log_2(16)</math> est tout simplement égal à 4, car il faut 4 bits pour coder la valeur 16. On a donc :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over 4}</math>
En clair, il faut quatre fois moins de chiffres hexadécimaux que de bits, ce qui est assez intuitif vu qu'il faut 4 bits pour coder un chiffre hexadécimal.
===Les débordements d'entier===
On vient de voir que tout ordinateur manipule des nombres dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Et cela limite les valeurs qu'il peut encoder, qui sont comprises dans un intervalle bien précis. Mais que ce passe-t-il si jamais le résultat d'un calcul ne rentre pas dans cet intervalle ? Par exemple, pour du binaire normal, que faire si le résultat d'un calcul atteint ou dépasse <math>2^n</math> ? Dans ce cas, le résultat ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
On peut imaginer d'autres codages pour lesquels les entiers ne commencent pas à zéro ou ne terminent pas à <math>2^n - 1</math>. On peut prendre le cas où l'ordinateur gère les nombres négatifs, par exemple. Dans le cas général, l'ordinateur peut coder les valeurs comprises dans un intervalle, qui va de la valeur la plus basse <math>N_\text{min}</math> à la valeur la plus grande <math>N_\text{max}</math>. Et encore une fois, si un résultat de calcul sort de cet intervalle, on fait face à un débordement d'entier.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math>, avec <math>N_\text{max}</math> la plus grande valeur codable par l'ordinateur.
On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur basse de débordement vaut <math>N_\text{min} - 1</math>, avec <math>N_\text{min}</math> la plus petite valeur codable par l'ordinateur.
===La gestion des débordements d'entiers===
Face à un débordement d'entier, l'ordinateur peut utiliser deux méthodes : l'arithmétique saturée ou l'arithmétique modulaire.
L''''arithmétique saturée''' consiste à arrondir le résultat pour prendre la plus grande ou la plus petite valeur. Si le résultat d'un calcul dépasse la valeur haute de débordement, le résultat est remplacé par le plus grand entier supporté par l'ordinateur. La même chose est possible quand le résultat est inférieur à la plus petite valeur possible, par exemple lors d'une soustraction : l'ordinateur arrondit au plus petit entier possible.
Pour donner un exemple, voici ce que cela donne avec des entiers codés sur 4 bits, qui codent des nombres de 0 à 15. Si je fais le calcul 8 + 9, le résultat normal vaut 17, ce qui ne rentre pas dans l'intervalle. Le résultat est alors arrondi à 15. Inversement, si je fais le calcul 8 - 9, le résultat sera de -1, ce qui ne rentre pas dans l'intervalle : le résultat est alors arrondi à 0.
[[File:Odometer rollover.jpg|vignette|Exemple de débordement d'entier sur un compteur mécanique quelconque. L'image montre clairement que le compteur revient à zéro une fois la valeur maximale dépassée.]]
L''''arithmétique modulaire''' est plus compliquée et c'est elle qui va nous intéresser dans ce qui suit. Pour simplifier, imaginons que l'on décompte à partir de zéro. Quand on arrive à la valeur haute de débordement, on recommence à compter à partir de zéro. L'arithmétique modulaire n'est pas si contre-intuitive et vous l'utilisez sans doute au quotidien. Après tout, c'est comme cela que l'on compte les heures, les minutes et les secondes. Quand on compte les minutes, on revient à 0 au bout de 60 minutes. Pareil pour les heures : on revient à zéro quand on arrive à 24 heures. Divers compteurs mécaniques fonctionnent sur le même principe et reviennent à zéro quand ils dépassent la plus grande valeur possible, l'image ci-contre en montrant un exemple.
Mathématiquement, l'arithmétique modulaire implique des divisions euclidiennes, celles qui donnent un quotient et un reste. Lors d'un débordement, le résultat s'obtient comme suit : on divise le nombre qui déborde par la valeur haute de débordement, et que l'on conserve le reste de la division. Au passage, l'opération qui consiste à faire une division et à garder le reste au lieu du quotient est appelée le ''modulo''. Prenons l'exemple où l'ordinateur peut coder tous les nombres entre 0 et 1023, soit une valeur haute de débordement de 1024. Pour coder le nombre 4563, on fait le calcul 4563 / 1024. On obtient : <math>4563 = 4 \times 1024 + 467</math>. Le reste de la division est de 467 et c'est lui qui sera utilisé pour coder la valeur de départ, 4563. Le nombre 4563 est donc codé par la valeur 467 dans un tel ordinateur en arithmétique modulaire. Au passage, la valeur haute de débordement est toujours codée par un zéro dans ce genre d'arithmétique.
Les ordinateurs utilisent le plus souvent une valeur haute de débordement de <math>2^n</math>, avec n le nombre de bits utilisé pour coder les nombres entiers positifs. En faisant cela, l'opération modulo devient très simple et revient à éliminer les bits de poids forts au-delà du énième bit. Par exemple, reprenons l'exemple d'un ordinateur qui code ses nombres sur 4 bits. Imaginons qu'il fasse le calcul <math>13 + 3</math>, soit 1101 + 0011 = 1 0000 en binaire. Le résultat entraîne un débordement d'entier et l'ordinateur ne conserve que les 4 bits de poids faible. Cela donne : 1101 + 0011 = 0000. Comme autre exemple l'addition 1111 + 0010 ne donnera pas 17 (1 0001), mais 1 (0001).
L'avantage est que les calculs sont beaucoup plus simples avec cette méthode qu'avec les autres. L'ordinateur a juste à ne pas calculer les bits de poids fort. Pas besoin de faire une division pour calculer un modulo, pas besoin de corriger le résultat pour faire de l'arithmétique saturée.
===Aparté : quelques valeurs particulières en binaire===
Plus haut, on a dit qu'avec n bits, on peut encoder toutes les valeurs allant de <math>0</math> à <math>2^n - 1</math>. Ce simple fait permet de déterminer quelle est la valeur de certains entiers à vue d’œil. Dans ce qui va suivre, nous allons poser quelques bases que nous réutiliserons dans la suite du chapitre. Il s'agit de quelques trivias qui sont cependant assez utiles.
Le premier trivia concerne la valeur maximale <math>2^n - 1</math> : elle est encodée par un nombre dont tous les bits sont à 1.
Le deuxième trivia concerne les nombres de la forme 000...000 1111 1111, à savoir des nombres dont les x bits de poids faible sont à 1 et tous les autres valent 0. Par définition, de tels nombres codent la plus grande valeur possible sur x bits, ce qui fait qu'ils valent <math>2^x - 1</math>.
Le troisième trivia concerne les nombres de la forme 11111...0000. En clair, des nombres où on a une suite consécutive de 1 dans les bits de poids fort et les x bits de poids faibles à 0. De tels nombres valent : <math>2^n - 2^x</math>. La preuve est assez simple : ils s'obtiennent en prenant la valeur maximale <math>2^n - 1</math>, et en soustrayant un nombre de la forme <math>2^x - 1</math>.
[[File:Valeurs particulières en binaire.png|centre|vignette|upright=2|Valeurs particulières en binaire]]
Si on utilise l'arithmétique modulaire, la valeur <math>2^n</math> n'est autre que la valeur de débordement haute pour les nombres stockés sur n bits, et se code comme le zéro (il faudrait 1 bit de plus pour stocker le 1 de poids fort de <math>2^n</math>). Les 1er et 3ème nombres évoqués dans les paragraphes précédents peuvent donc se passer de <math>2^n</math> dans leur expression :
* <math>- 1</math> est codé 1111111111111111...111 (n bits à 1)
* <math>- 2^x</math> est codé 11111...111000..000000 (x bits à 0 précédés de n-x bits à 1)
==Les nombres entiers négatifs==
<noinclude>[[File:Binary Representations.svg|vignette|Représentations signées, exemples sur 4 bits.]]</noinclude>
Passons maintenant aux entiers négatifs en binaire : comment représenter le signe moins ("-") avec des 0 et des 1 ? Eh bien, il existe plusieurs méthodes :
* la représentation en signe-valeur absolue ;
* la représentation en complément à un ;
* la représentation en complément à deux;
* la représentation par excès ;
* la représentation dans une base négative ;
* d'autres représentations encore moins utilisées que les autres.
===La représentation en signe-valeur absolue===
La solution la plus simple pour représenter un entier négatif consiste à coder sa valeur absolue en binaire, et rajouter un bit qui précise si c'est un entier positif ou un entier négatif. Par convention, ce bit de signe vaut 0 si le nombre est positif et 1 s'il est négatif. On parle alors de représentation en signe-valeur absolue, aussi appelée '''représentation en signe-magnitude'''.
Avec cette technique, il y autant de nombres positifs que négatifs. Mieux : pour chaque nombre représentable en représentation signe-valeur absolue, son inverse l'est aussi. Ce qui fait qu'avec cette méthode, le zéro est codé deux fois : on a un -0, et un +0. Cela pose des problèmes lorsqu'on demande à notre ordinateur d'effectuer des calculs ou des comparaisons avec zéro.
[[File:Codage sur 4 bits en signe-valeur absolue.png|centre|vignette|upright=2|Codage sur 4 bits en signe-valeur absolue]]
===La représentation en complément à un===
La représentation en '''complément à un''' peut être vue, en première approximation, comme une variante de la représentation en signe-magnitude. Et je dis bien : "en première approximation", car il y a beaucoup à dire sur la représentation en complément à un, mais nous verrons cela dans la section suivante sur le complément à deux. Conceptuellement, la représentation en signe-magnitude et celle du complément à un sont drastiquement différentes et sont basées sur des concepts mathématiques totalement différents. Mais elles ont des ressemblances de surface, qui font que faire la comparaison entre les deux est assez utile pédagogiquement parlant.
En complément à un, les nombres sont codés en utilisant un bit de signe qui indique si le nombre de positif ou négatif, couplé à une valeur absolue. Par contre, la valeur absolue est codée différemment en binaire. Pour les nombres positifs, la valeur absolue est codée en binaire normal, pas de changement comparé aux autres représentations. Mais pour les valeurs négatives, la valeur absolue est codée en binaire normal, puis tous les bits sont inversés : les 0 deviennent des 1 et réciproquement. En clair, on utilise une opération de complémentation pour les nombres négatifs.
Prenons un exemple avec un nombre codé sur 4 bits, avec un cinquième bit de signe. La valeur 5 est codée comme suit : 0 pour le bit de signe, 5 donne 0101 en binaire, le résultat est 0 0101. Pour la valeur -5, le bit de signe est 1, 5 donne 0101 en binaire, on inverse les bits ce qui donne 1010 : cela donne 11010. Notez qu'on peut passer d'un résultat à l'autre avec une opération de complémentation, à savoir en inversant les bits de l'autre et réciproquement.
Il s'agit là d'une propriété générale avec le complément à 1 : l'opposé d'un nombre, à savoir passer de sa valeur positive à sa valeur négative ou inversement, se calcule avec une opération NOT, une opération de complémentation (d'où son nom). L'avantage est que les ordinateurs gèrent naturellement d'opération de complémentation. Par contre, en signe-magnitude, inverser le bit de signe est une opération spécifique et ne sert qu'à ça. Il faut donc rajouter une opération en plus pour calculer l'opposé d'un nombre.
La représentation en complément à un garde les défauts de la représentation en signe-magnitude. Le zéro est codé deux fois, avec un zéro positif et un zéro négatif. La différence est que les valeurs négatives sont dans l'ordre inverse, il y a une symétrie un peu meilleure. Les deux zéros sont d'ailleurs totalement éloignés, ils correspondent aux valeurs extrêmes encodables : le zéro positif a tous ses bits à 0, le zéro négatif a tous ses bits à 1. Le résultat est que l'implémentation des comparaisons et de certains calculs est plus complexe qu'avec le signe-magnitude, mais de peu.
[[File:Codage sur 4 bits en complément à 1.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 1.]]
===La représentation par excès===
La représentation par excès consiste à ajouter un biais aux nombres à encoder afin de les encoder par un entier positif. Pour encoder tous les nombres compris entre -X et Y en représentation par excès, il suffit de prendre la valeur du nombre à encoder, et de lui ajouter un biais égal à X. Ainsi, la valeur -X sera encodée par zéro, et toutes les autres valeurs le seront par un entier positif, le zéro sera encodé par X, 1, par X+1, etc. Par exemple, prenons des nombres compris entre -127 et 128. On va devoir ajouter un biais égal à 127, ce qui donne :
{|class="wikitable"
|-
!Valeur avant encodage!!Valeur après encodage
|-
||-127||0
|-
||-126||1
|-
||-125||2
|-
||…||…
|-
||0||127
|-
||…||…
|-
||127||254
|-
||128||255
|}
===La représentation en complément à deux===
La représentation en '''complément à deux''' est basée sur les mêmes mathématiques que le complément à un, mais fonctionne très différemment en pratique.
Si on regarde de loin, son principe est assez différent : il n'y a pas de bit de signe ni quoi que ce soit d'autre. À la place, un nombre en complément à deux est encodé comme en binaire normal, à un point près : le bit de poids fort est soustrait, et non additionné aux autres. Il a une valeur négative : on soustrait la puissance de deux associée s'il vaut 1, on ne la tient pas en compte s'il vaut 0.
Par exemple, la valeur du nombre noté 111001 en complément à deux s'obtient comme suit :
{|class="wikitable" style="margin:auto;"
|-
! -32 !! 16 !! 8 !! 4 !! 2 !! 1
|-
| 1 || 1 || 1 || 0 || 0 || 1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(8×1)+(4×0)+(2×1)+(1×1) = −32+16+8+1 = -7.
: ''Avec le complément à deux, comme avec le complément à un, le bit de poids fort vaut 0 pour les nombres positifs et 1 pour les négatifs.''
L'avantage de cette représentation est qu'elle n'a pas de double zéro : le zéro n'est encodé que par une seule valeur. Par contre, la valeur autrefois prise par le zéro négatif est réutilisée pour encoder une valeur négative. La conséquence est que l'on a un nombre négatif en plus d'encodé, il n'y a plus le même nombre de valeurs strictement positives et de valeurs négatives encodées. Le nombre négatif en question est appelé le '''nombre le plus négatif''', ce nom trahit le fait que c'est celui qui a la plus petite valeur (la plus grande valeur absolue). Il est impossible de coder l'entier positif associé, sa valeur absolue.
[[File:Codage sur 4 bits en complément à 2.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 2.]]
====Le calcul du complément à deux : première méthode====
Les représentations en complément à un et en complément à deux sont basées sur le même principe mathématique. Leur idée est de remplacer chaque nombre négatif par un nombre positif équivalent, appelé le ''complément''. Par équivalent, on veut dire que tout calcul donne le même résultat si on remplace un nombre négatif par son complément (idem avec un nombre positif, son complément aura un signe inverse). Et pour faire cela, elles se basent sur les débordements d'entier pour fonctionner, et plus précisément sur l'arithmétique modulaire abordée plus haut.
Si on fait les calculs avec le complément, les résultats du calcul entraînent un débordement d'entier, qui sera résolu par un '''modulo''' : l'ordinateur ne conserve que les bits de poids faible du résultat, les autres bits sont oubliés. Par exemple, prenons l'addition 15 + 2, 1111 + 0010 en binaire : le résultat ne sera pas 17 (10001), vu qu'on n'a pas assez de bits pour encoder le résultat, mais 1 (0001). Et le résultat après modulo sera identique au résultat qu'on aurait obtenu avec le nombre négatif sans modulo. En clair, c'est la gestion des débordements qui permet de corriger le résultat de manière à ce que l'opération avec le complément donne le même résultat qu'avec le nombre négatif voulu. Ainsi, on peut coder un nombre négatif en utilisant son complément positif.
: Cela ressemble beaucoup à la méthode de soustraction basée sur un complément à 9 pour ceux qui connaissent, sauf que c'est une version binaire qui nous intéresse ici.
Prenons un exemple, qui permettra d'introduire la suite. Encore une fois, on utilise un codage sur 4 bits dont la valeur haute de débordement est de 16. Prenons l'addition de 13 + 3 = 16. Avec l'arithmétique modulaire, 16 est équivalent à 0, ce qui donne : 13 + 3 = 0 ! On peut aussi reformuler en disant que 13 = -3, ou encore que 3 = -13. Dit autrement, 3 est le complément de -13 pour ce codage. Et ne croyez pas que ça marche uniquement dans cet exemple : cela se généralise assez rapidement à tout nombre négatif.
Prenons un nombre N dont un veut calculer le complément à deux K. Dans le cas général, on a :
: <math>N + K = 2^n</math>, vu que <math>2^n = 0</math> en utilisant le modulo.
En réorganisant très légèrement les termes pour isoler K, on a :
: <math>K = 2^n - N</math>
La formule précédent permet de calculer la complément à deux assez simplement, en faisant le calcul à la main.
Avant de poursuivre, prenons un exemple très intéressant : le cas où N = -1. Son complément à deux vaut donc :
: <math>K = 2^n - 1</math>
Le terme <math>2^n - 1</math> devrait vous rappeler quelque chose : il s'agit d'un nombre dont tous les bits sont à 1. En clair, le complément à deux de -1 est un nombre de la forme 1111...111. Et cela vaut quel que soit le nombre de bits n. La représentation de -1 est similaire, peu importe que l'on utilise des nombres de 16 bits, 32 bits, 64 bits, etc.
Voyons maintenant le cas des puissance de deux, par exemple 2, 4, 8, 16, etc. Leur complément à deux vaut :
: <math>K = 2^n - 2^x</math>
Nous avions vu précédemment dans le chapitre que ces nombres sont de la forme 11111...0000. En clair, des nombres dont les x bits de poids faible sont à 0, et tous les autres bits sont à 1. Par exemple, le complément à deux de -2 est un nombre dont les bits sont à 1, sauf le bit de poids faible. De même, le complément à deux de -4 a tous ses bits à 1, sauf les deux bits de poids faible. Et le complément à deux de -8 a tous ses bits à 1, sauf les trois bits de poids faible. Pour résumer, tout nombre de la forme <math>- 2^x</math> a tous ses bits à 1, sauf les x bits de poids faible qui sont à 0.
{|class="wikitable"
|+ Exemple avec des nombres de 8 bits
|-
! -1
| 1111 1111
|-
! -2
| 1111 1110
|-
! -4
| 1111 1100
|-
! -8
| 1111 1000
|-
! -16
| 1111 0000
|-
! -32
| 1110 0000
|-
! -64
| 1100 0000
|-
! -128
| 1000 0000
|-
! 0 / - 256
| 0000 0000
|}
====Le calcul du complément à deux : seconde méthode====
Il existe une seconde méthode pour calculer le complément à deux d'un nombre, la voici. Pour les nombres positifs, encodez-les comme en binaire normale. Pour les nombres négatifs, faites pareil, puis inversez tous les bits, avant d'ajouter un. La procédure est identique à celle du complément à un, sauf que l'on incrémente le résultat final. Et ce n'est pas une coïncidence, comme nous allons le voir immédiatement.
Pour comprendre pourquoi la méthode marche, repartons de la formule précédente <math>K = 2^n - N</math>. Elle peut se reformuler comme suit :
: <math>K = (2^n - 1 + 1) - N</math>
: <math>K = (2^n - 1) - N + 1</math>
La valeur <math>(2^n - 1)</math> est par définition un nombre dont tous les bits sont à 1. À cette valeur, on soustrait un nombre <math>|N|</math> dont certains bits sont à 1 et d'autres à 0. En clair, pour chaque colonne, on a deux possibilités : soit on doit faire la soustraction <math>1-1</math>, soit la soustraction <math>1-0</math>. Or, les règles de l'arithmétique binaire disent que <math>1-1=0</math> et <math>1-0=1</math>. En regardant attentivement, on se rend compte que le bit du résultat est l'inverse du bit de départ. De plus, les deux cas ne donnent pas de retenue : le calcul pour chaque bit n'influence pas les bits voisins.
Le terme <math>(2^n - 1) - N</math> est donc le complément à un du nombre N, un nombre égal à sa représentation en binaire dont tous les bits sont inversés. Notons <math>\overline{|N|}</math> le nombre formé en inversant tous les bits de N. On a alors :
: <math>K = \overline{N} + 1</math>
En clair, le complément à deux s'obtient en prenant le complément à un et en ajoutant 1. Dit autrement, il faut prendre le nombre N, en inverser tous les bits et ajouter 1.
Une autre manière équivalente consiste à faire le calcul suivant :
: <math>K = \overline{N - 1}</math>
On prend le nombre dont on veut le complément, on soustrait 1 et on inverse les bits.
: Notons que tout ce qui a été dit plus haut marche aussi pour le complément à un, avec cependant une petite différence : la valeur haute de débordement n'est pas la même, ce qui change les calculs. Pour des nombres codés sur <math>n</math> bits, la valeur haute de débordement est égale à <math>2^n</math> en complément à deux, alors qu'elle est de <math>2^n - 1</math> en complément à un. De ce fait, la gestion des débordements est plus simple en complément à deux.
====L'extension de signe====
Dans les ordinateurs, tous les nombres sont codés sur un nombre fixé et constant de bits. Ainsi, les circuits d'un ordinateur ne peuvent manipuler que des nombres de 4, 8, 12, 16, 32, 48, 64 bits, suivant la machine. Si l'on veut utiliser un entier codé sur 16 bits et que l'ordinateur ne peut manipuler que des nombres de 32 bits, il faut bien trouver un moyen de convertir le nombre de 16 bits en un nombre de 32 bits, sans changer sa valeur et en conservant son signe. Cette conversion d'un entier en un entier plus grand, qui conserve valeur et signe s'appelle l''''extension de signe'''.
L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
L'explication plus simple tient dans la manière de coder le bit de poids fort. Prenons l'exemple de la conversion d'un entier de 5 bits en un entier de 8bits. Les 4 bits de poids faible ont un poids positif (on les additionne), alors que le bit de poids fort a un poids négatif. Le nombre encodé vaut : <math>\text{Bit de poids fort} \times -16 + \text{Valeur encodée sur les 4 bits de poids faible}</math>. La valeur encodée sur 4 bits reste la même après extension de signe, car les poids des bits de poids faible ne changent pas. Par contre, le bit de poids fort change. Sur 8 bits, la valeur -16 est encodée par un nombre de la forme 1111 0000. En remplaçant le bit de poids fort par sa valeur calculée sur plus de bits, on remarque que les bits de poids fort ont été remplacés par des 1.
===La représentation négabinaire===
Enfin, il existe une dernière méthode, assez simple à comprendre, appelée '''représentation négabinaire'''. Dans cette méthode, les nombres sont codés non en base 2, mais en base -2. Oui, vous avez bien lu : la base est un nombre négatif. Dans les faits, la base -2 est similaire à la base 2 : il y a toujours deux chiffres (0 et 1), et la position dans un chiffre indique toujours par quelle puissance de 2 il faut multiplier, sauf qu'il faudra ajouter un signe moins une fois sur 2. Concrètement, les puissances de -2 sont les suivantes : 1, -2, 4, -8, 16, -32, 64, etc. En effet, un nombre négatif multiplié par un nombre négatif donne un nombre positif, ce qui fait qu'une puissance sur deux est négative, alors que les autres sont positives. Ainsi, on peut représenter des nombres négatifs, mais aussi des nombres positifs dans une puissance négative.
Par exemple, la valeur du nombre noté 11011 en base -2 s'obtient comme suit :
{|class="wikitable"
|-
!…
!-32
!16
!-8
!4
!-2
!1
|-
|…
|1
|1
|1
|0
|1
|1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(−8×1)+(4×0)+(−2×1)+(1×1)=−32+16−8−2+1=−25.
==Les nombres à virgule fixe==
On sait donc comment sont stockés nos nombres entiers dans un ordinateur. Néanmoins, les nombres entiers ne sont pas les seuls nombres que l'on utilise au quotidien : il nous arrive d'en utiliser à virgule. Notre ordinateur n'est pas en reste : il est lui aussi capable de manipuler de tels nombres. Dans les grandes lignes, il peut utiliser deux méthodes pour coder des nombres à virgule en binaire : La virgule fixe et la virgule flottante. Parlons d'abord de la virgule fixe, plus simple à comprendre.
===La virgule fixe : des entiers cachés===
La méthode de la virgule fixe consiste à émuler les nombres à virgule à partir de nombres entiers. Un nombre à virgule fixe est codé par un nombre entier proportionnel au nombre à virgule fixe. Pour obtenir la valeur de notre nombre à virgule fixe, il suffit de diviser l'entier servant à le représenter par le facteur de proportionnalité. Par exemple, pour coder 1,23 en virgule fixe, on peut choisir comme « facteur de conversion » 1000, ce qui donne l'entier 1230.
Généralement, les informaticiens utilisent une puissance de deux comme facteur de conversion, pour simplifier les calculs. En faisant cela, on peut écrire les nombres en binaire et les traduire en décimal facilement. Pour l'exemple, cela permet d'écrire des nombres à virgule en binaire comme ceci : 1011101,1011001. Et ces nombres peuvent se traduire en décimal avec la même méthode que des nombres entier, modulo une petite différence. Comme pour les chiffres situés à gauche de la virgule, chaque bit situé à droite de la virgule doit être multiplié par la puissance de deux adéquate. La différence, c'est que les chiffres situés à droite de la virgule sont multipliés par une puissance négative de deux, c'est à dire par <math>1 \over 2</math>, <math>1 \over 4</math>, <math>1 \over 8</math>, <math>1 \over 16</math>, <math>1 \over 32</math>, ...
[[File:Fixed-point binary representation.svg|centre|vignette|upright=2.5|Nombres à virgule fixe.]]
Cette méthode est assez peu utilisée de nos jours, quoiqu'elle puisse avoir quelques rares applications relativement connue. Un bon exemple est celui des banques : les sommes d'argent déposées sur les comptes ou transférées sont codés en virgule fixe. Les sommes manipulées par les ordinateurs ne sont pas exprimées en euros, mais en centimes d'euros. Et c'est une forme de codage en virgule fixe dont le facteur de conversion est égal à 100. La raison de ce choix est que les autres méthodes de codage des nombres à virgule peuvent donner des résultats imprécis : il se peut que les résultats doivent être tronqués ou arrondis, suivant les opérandes. Cela n'arrive jamais en virgule fixe, du moins quand on se limite aux additions et soustractions.
===Les nombres à virgule non-représentables en binaire===
Quel que soit la façon de représenter les nombres à virgules, il existe des nombres qui ne peuvent être représentés de manière exacte, à savoir avec un nombre fini de décimales après la virgule. En soi, ce n'est pas spécifique au binaire, on a la même chose en décimal. Par exemple, la fraction 1/3 en décimal s'écrit 0.3333333..., avec une infinité de 3. La même chose existe en binaire, mais pour des nombres différents.
Déjà, évacuons le cas des nombres irrationnels, à savoir les nombres qui ne peuvent pas s'écrire sous la forme d'une fraction, comme <math>pi</math> ou <math>e</math>. Ils ont une infinité de décimales que ce soit en binaire, en décimal, en hexadécimal, ou autre. Ils ne sont pas représentables avec un nombre fini de décimales quelle que soit la base utilisée. Concentrons-nous sur des nombres qui ne sont pas dans ce cas, et qui ont un nombre fini ou infini de décimales.
Le passage de la base 10 à la base 2 change le nombre de décimales, et peut faire passer d'un nombre fini de décimales à un nombre infini. Par exemple, <math>\frac{1}{5} = 0.2</math> est représenté en binaire avec une séquence infinie de bits : <math>0.001100110011...</math>. Les nombres étant en base binaire et représenté avec un nombre limité de bits, il existe certains nombres décimaux triviaux qui ne sont pas représentables avec un nombre fini de décimales.
Et la réciproque n'est pas vraie : tout nombre binaire avec un nombre fini de décimales en binaire est représentable avec un nombre fini de décimales en base 10. Ceci est lié à la décomposition en facteur premier des bases utilisées :
* <math>2 = 2^1</math>
* <math>10 = 2^1 \times 5^1</math>
Le nombre 10 possède tous les facteurs premiers de 2, mais 2 n'a pas de 5 dans sa décomposition.
==Les nombres flottants==
Les nombres à virgule fixe ont aujourd'hui été remplacés par les '''nombres à virgule flottante''', où le nombre de chiffres après la virgule est variable. Le codage d'un nombre flottant est basée sur son ''écriture scientifique''. Pour rappel, en décimal, l’écriture scientifique d'un nombre consiste à écrire celui-ci comme un produit entre un nombre <math>a</math> et une puissance de 10, avec <math>a</math> étant compris entre 1 et 9. Ce qui donne :
: <math>a \times 10^e</math>, avec <math>a \in [1, 10 [</math>
Le nombre <math>a</math> est appelé le ''significande'' et il est compris entre 1 (inclus) et 10 (exclu). Cette contrainte garantit que l'écriture scientifique d'un nombre est unique, qu'il n'y a qu'une seule façon d'écrire un nombre en notation scientifique. Pour cela, on impose le nombre de chiffre à gauche de la virgule et le plus simple est que celui-ci soit égal à 1. Mais il faut aussi que celui-ci ne soit pas nul. En effet, si on autorise de mettre un 0 à gauche de la virgule, il y a plusieurs manières équivalentes d'écrire un nombre. Ces deux contraintes font que le significande doit être égal ou plus grand que 1, mais strictement inférieur à 10. Par contre, on peut mettre autant de décimales que l'on veut.
En binaire, c'est la même chose, mais avec une puissance de deux. Cela implique de modifier la puissance utilisée : au lieu d'utiliser une puissance de 10, on utilise une puissance de 2.
: <math>a \times 2^e</math>, avec <math>a \in [1, 2 [</math>
Le significande est aussi altéré, au même titre que la puissance, même si les contraintes sont similaires à celles en base 10. En effet, le nombre <math>a</math> ne possède toujours qu'un seul chiffre à gauche de la virgule, comme en base 10. Vu que seuls deux chiffres sont possibles (0 et 1) en binaire, on s'attend à ce que le chiffre situé à gauche de la virgule soit un zéro ou un 1. Mais rappelons que le chiffre à gauche doit être non-nul, pour les mêmes raisons qu'en décimal. En clair, le significande a forcément un bit à 1 à gauche de la virgule. Pour récapituler, l'écriture scientifique binaire d'un nombre consiste à écrire celui-ci sous la forme :
: <math>1,m \times 2^e</math>, avec <math>m \in [0, 1 [</math>
La partie fractionnaire du nombre <math>a</math>, qu'on appelle la '''mantisse'''.
[[File:Mantissa.svg|centre|vignette|Écriture scientifique (anglais).]]
Pour déterminer l'écriture scientifique en binaire d'un nombre quelconque, la procédure dépend de la valeur du nombre en question. Tout dépend s'il est dans l'intervalle <math>[1, 2 [</math>, au-delà de 2 ou en-dessous de 1.
* Pour un nombre entre 1 (inclus) et 2 (exclu), il suffit de le traduire en binaire. Son exposant est 0.
* Pour un nombre au-delà de 2, il faut le diviser par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant est alors le nombre de fois qu'il a fallu diviser par 2.
* Pour un nombre plus petit que 1, il faut le multiplier par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant se calcule en prenant le nombre de fois qu'il a fallu multiplier par 2, et en prenant l'opposé (en mettant un signe - devant le résultat).
===Le codage des nombres flottants et la norme IEEE 754===
La '''virgule flottante''' encode l'écriture scientifique d'un nombre/ Elle utilise pour cela trois données : la mantisse, l'exposant et un bit de signe. Les trois sont concaténés dans un seul "nombre", plus précisément une suite de bits qui est découpée en un bit de signe, un entier qui encode l'exposant, et un entier pour la mantisse.
[[File:General floating point frac.svg|centre|vignette|upright=2|Format Général d'un nombre flottant.]]
[[File:General floating point.svg|centre|vignette|upright=2|Format Général d'un nombre flottant, plus détaillé.]]
L'exposant est codé sur e bits, la mantisse sur f bits. En général, la mantisse est plus large que l'exposant, elle reçoit plus de bits que l'exposant. Le standard actuel pour la virgule flottante est la '''norme IEEE 754'''. Cette norme va (entre autres) définir quatre types de flottants différents, qui utilisent des exposants/mantisses de taille différente. Les deux formats les plus courants sont les flottants '''simple précision''' et '''double précision'''. Les premiers sont codés sur 32 bits, les seconds sur 64 bits.
{|class="wikitable"
|-
!
! Nombre de bits total
! Nombre de bits de l'exposant
! Nombre de bits pour la mantisse
! Décalage
|-
! Simple précision
| 32
| 8
| 23
|-
! Double précision
| 64
| 11
| 52
|}
Voici à peu-près comment les nombres flottants sont répartis sur une ligne numérique. Vous remarquerez que les flottants sont de plus en plus éloignés au fur et à mesure qu'on s'éloigne de zéro. La raison est l'encodage avec un exposant et une mantisse. L'écart entre deux flottants reste stable pour un même exposant, mais il y a une cassure quand on passe d'un exposant au suivant.
[[File:FloatingPointPrecisionAugmented.png|centre|vignette|upright=3|Floating Point Precision.]]
L'exposant peut être aussi bien positif que négatif (pour permettre de coder des nombres très petits). Pour cela, il est encodé en représentation par excès. Pour un exposant de n bits, le biais est égal à <math>2^{n-1} - 1</math>.
L'encodage de la mantisse a lui aussi ses subtilités. En effet, la mantisse est un nombre à virgule, avec une partie entière et une partie fractionnaire. Le tout est encodé en virgule fixe, reste à choisir son encodage exact. Pour simplifier l'encodage, l'exposant est choisir de manière à ce qu'il y ait un seul chiffre avant la virgule, ce qui permet de coder la partie entière sur 1 bit. En clair, il faut encoder le bit à gauche de la virgule, puis ceux à droite. Les anciens PC utilisaient cette solution, du temps de la FPU x87. Ils géraient nativement des nombres flottants de 80 bits, appelés '''flottants étendus x87'''. Ils utilisaient une mantisse codée sur 64 bits, dont un bit pour la partie entière, et 63 pour la partie fractionnaire.
[[File:X86 Extended Floating Point Format.svg|centre|vignette|upright=2|Flottants étendus des anciens CPU X86.]]
Une simplification plus importante choisir l'exposant de manière à ce que la partie entière vaut 1, systématiquement. En faisant cela, on n'a plus à encoder la partie entière, pas besoin de lui réserver un bit. La mantisse encode donc uniquement la partie fractionnaire, la partie entière n'est pas encodée. Une telle méthode est donc plus efficace. A quelques détails près : de nombreuses valeurs ne peuvent pas être encodées facilement. Par exemple, le zéro ne peut pas être encodé avec cette technique. Et c'est là un des gros défaut de cette technique : ne pas pouvoir encoder le zéro est un énorme problème !
Pour éviter ce défaut, les nombres flottants IEE754 utilisent une variante de la technique précédente. Les flottants IEE754 ont une partie entière de la mantisse qui vaut 1, sauf pour quelques rares exceptions. Quelques nombres flottants spécialisés ne sont pas codés en respectant les règles pour le significande et ont un 0 à gauche de la virgule. Ils sont appelés les '''dénormaux''', aussi nommés flottants dénormalisés. Les dénormaux sont des nombres flottants où l'exposant est le plus petit possible. Le '''zéro''' est un dénormal particulier dont la mantisse est nulle. Au fait, remarquez que le zéro est codé deux fois à cause du bit de signe : on se retrouve avec un -0 et un +0.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur minimale (0 en binaire) || Mantisse différente de zéro (dénormal) ou égale à zéro (zéro)
|}
Toujours est-il que le bit à gauche de la virgule n'est pas codé, que ce soit pour les flottants normaux ou les fameux dénormaux qui font exception. On verra que ce bit peut se déduire en fonction de l'exposant utilisé pour encoder le nombre à virgule, ce qui lui vaut le nom de '''bit implicite'''.
Fait étrange, la norme IEEE754 permet de représenter '''l'infini''', aussi bien en positif qu'en négatif. Celui-ci est codé en mettant l'exposant à sa valeur maximale et la mantisse à zéro. Et le pire, c'est qu'on peut effectuer des calculs sur ces flottants infinis. Mais cela a peu d'utilité.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse égale à zéro
|}
Mais malheureusement, l'invention des flottants infinis n'a pas réglé tous les problèmes. Par exemple, quel est le résultat de <math>\infty - \infty</math> ? Ou encore <math>0 - \infty</math> ? Autant prévenir tout de suite : mathématiquement, on ne peut pas savoir quel est le résultat de ces opérations. Pour pouvoir résoudre ces calculs, il a fallu inventer un nombre flottant qui signifie « je ne sais pas quel est le résultat de ton calcul pourri ». Ce nombre, c'est '''NaN'''. NaN est l'abréviation de ''Not A Number'', ce qui signifie : n'est pas un nombre. Ce NaN a un exposant dont la valeur est maximale, mais une mantisse différente de zéro. Pour être plus précis, il existe différents types de NaN, qui diffèrent par la valeur de leur mantisse, ainsi que par les effets qu'ils peuvent avoir. Malgré son nom explicite, on peut faire des opérations avec NaN, mais cela ne sert pas vraiment à grand chose : une opération arithmétique appliquée avec un NaN aura un résultat toujours égal à NaN.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse différente de zéro
|}
===Les arrondis et exceptions===
La norme impose aussi une gestion des arrondis ou erreurs, qui arrivent lors de calculs particuliers. En voici la liste :
{|class="wikitable"
|-
!Nom de l’exception
!Description
|-
!Invalid operation
|Opération qui produit un NAN. Elle est levée dans le cas de calculs ayant un résultat qui est un nombre complexe, ou quand le calcul est une forme indéterminée. Pour ceux qui ne savent pas ce que sont les formes indéterminées, voici en exclusivité la liste des calculs qui retournent NaN : <math>\infty - \infty</math>, <math>0 \times \infty</math>, <math>0^0</math>, <math>1^{\infty}</math>, <math>\infty^0</math>.
|-
!Overflow
|Résultat trop grand pour être stocké dans un flottant. Le plus souvent, on traite l'erreur en arrondissant le résultat en vue de la taille de la mantisse;
|-
!Underflow
|Pareil que le précédent, mais avec un résultat trop petit. Le plus souvent, on traite l'erreur en arrondissant le résultat vers 0.
|-
!Division par zéro
|Le nom parle de lui-même. La réponse la plus courante est de répondre + ou - l'infini.
|-
!'''Inexact'''
|Le résultat ne peut être représenté par un flottant et on doit l'arrondir.
|}
La gestion des arrondis pose souvent problème. Pour donner un exemple, on va prendre le nombre 0,1. En binaire, ce nombre s'écrit comme ceci : 0,1100110011001100... et ainsi de suite jusqu'à l'infini. Notre nombre utilise une infinité de décimales. Bien évidemment, on ne peut pas utiliser une infinité de bits pour stocker notre nombre et on doit impérativement l'arrondir. Comme vous le voyez avec la dernière exception, le codage des nombres flottants peut parfois poser problème : dans un ordinateur, il se peut qu'une opération sur deux nombres flottants donne un résultat qui ne peut être codé par un flottant. On est alors obligé d'arrondir ou de tronquer le résultat de façon à le faire rentrer dans un flottant. Pour éviter que des ordinateurs différents utilisent des méthodes d'arrondis différentes, on a décidé de normaliser les calculs sur les nombres flottants et les méthodes d'arrondis. Pour cela, la norme impose le support de quatre modes d'arrondis :
* Arrondir vers + l'infini ;
* vers - l'infini ;
* vers zéro ;
* vers le nombre flottant le plus proche.
===Les nombres flottants logarithmiques===
Les '''nombres flottants logarithmiques''' sont une spécialisation des nombres flottants IEEE754, ou tout du moins une spécialisation des flottants écrits en écriture scientifique. Un nombre logarithmique est donc composé d'un bit de signe et d'un exposant, sans mantisse. La mantisse est totalement implicite : tous les flottants logarithmiques ont la même mantisse, qui vaut 1.
Pour résumer, il ne reste que l'exposant, qui est tout simplement le logarithme en base 2 du nombre encodé, d'où le nom de codage flottant logarithmique donné à cette méthode. Attention toutefois : l'exposant est ici un nombre fractionnaire, codé en virgule fixe. Le choix d'un exposant fractionnaire permet de représenter pas mal de nombres de taille diverses.
{|class="wikitable"
|-
!
! Bit de signe
! Exposant
|-
! Représentation binaire
| 0
| 01110010101111
|-
! Représentation décimale
| +
| 1040,13245464
|}
L'utilité de cette représentation est de simplifier certains calculs, comme les multiplications, divisions, puissances, etc. En effet, les mathématiques nous disent que le logarithme d'un produit est égal à la somme des logarithmes : <math>\ln{(a \times b)} = \ln{a} + \ln{b}</math>. Or, il se trouve que les ordinateurs sont plus rapides pour faire des additions/soustractions que pour faire des multiplications/divisions. Donc, la représentation logarithmique permet de remplacer les multiplications/divisions par des additions/soustractions, plus simples et plus rapides pour l'ordinateur.
Évidemment, les applications des flottants logarithmiques sont rares, limitées à quelques situations bien précises (traitement d'image, calcul scientifique spécifiques).
==Les encodages binaires alternatifs==
Outre le binaire que nous venons de voir, il existe d'autres manières de coder des nombres avec des bits. Et nous allons les aborder dans cette section. Parmi celle-ci, nous parlerons du code Gray, de la représentation ''one hot'', du unaire et du ternaire. Nous en parlons car elles seront utiles dans la suite du cours, bien que de manière assez limitée. Autant nous passerons notre temps à parler du binaire normal, autant les représentations que nous allons voir sont aujourd'hui utilisées dans des cas assez spécifiques. Et elles sont plus courantes que vous ne le pensez.
===Le code Gray===
Le '''code Gray''' est un encodage binaire qui a une particularité très intéressante : deux nombres consécutifs n'ont qu'un seul bit de différence. Pour exemple, voici ce que donne le codage des 8 premiers entiers sur 3 bits :
{|class="wikitable"
! Décimal
! Binaire naturel
! Codage Gray
|-
|0||000||000
|-
|1||001||001
|-
|2||010||011
|-
|3||011||010
|-
|4||100||110
|-
|5||101||111
|-
|6||110||101
|-
|7||111||100
|}
Les utilisations du code Gray sont assez nombreuses, bien qu'on n'en croise pas tous les jours. Un exemple : le code Gray est très utile dans certains circuits appelés les compteurs, qui mémorisent un nombre et l'incrémentent (+1) ou le décrémentent (-1) suivant les besoins de l'utilisateur. Il est aussi utilisé dans des scénarios difficiles à expliquer ici (des codes correcteurs d'erreur ou des histoires de passages de domaines d'horloge). Mais le point important est que ce code sera absolument nécessaire dans quelques chapitres, quand nous parlerons des tables de Karnaugh, un concept important pour la conception de circuits électroniques. Ne passez pas à côté de cette section.
Pour construire ce code Gray, on peut procéder en suivant plusieurs méthodes, les deux plus connues étant la méthode du miroir et la méthode de l'inversion.
[[File:Binary-reflected Gray code construction.svg|vignette|Construction d'un code gray par la méthode du miroir.]]
La méthode du miroir est relativement simple. Pour connaître le code Gray des nombres codés sur n bits, il faut :
* partir du code Gray sur n-1 bits ;
* symétriser verticalement les nombres déjà obtenus (comme une réflexion dans un miroir) ;
* rajouter un 0 au début des anciens nombres, et un 1 au début des nouveaux nombres.
Il suffit de connaître le code Gray sur 1 bit pour appliquer la méthode : 0 est codé par le bit 0 et 1 par le bit 1.
Une autre méthode pour construire la suite des nombres en code Gray sur n bits est la méthode de l'inversion. Celle-ci permet de connaître le codage du nombre n à partir du codage du nombre n-1, comme la méthode du dessus. On part du nombre 0, systématiquement codé avec uniquement des zéros. Par la suite, on décide quel est le bit à inverser pour obtenir le nombre suivant, avec la règle suivante :
* si le nombre de 1 est pair, il faut inverser le dernier chiffre.
* si le nombre de 1 est impair, il faut localiser le 1 le plus à droite et inverser le chiffre situé à sa gauche.
Pour vous entraîner, essayez par vous-même avec 2, 3, voire 5.
===Les représentations ''one-hot'' et unaire===
La représentation ''one-hot'' et la représentation unaire sont deux représentations assez liées, mais légèrement différentes.
La '''représentation unaire''' est la représentation en base 1. Avec elle, le zéro est codé en mettant tous les bits du nombre à zéro, la valeur 1 est encodée avec la valeur 000...0001, le deux avec 000...0011, le trois avec 000...0111, etc. Pour résumer, le nombre N est encodé en mettant les N bits de poids faible à 1, à l'exception du zéro qui est encodé...par un zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! Unaire
|-
| 0 || 000 || 0000 0000
|-
| 1 || 001 || 0000 0001
|-
| 2 || 010 || 0000 0011
|-
| 3 || 011 || 0000 0111
|-
| 4 || 100 || 0000 1111
|-
| 5 || 101 || 0001 1111
|-
| 6 || 110 || 0011 1111
|-
| 7 || 111 || 0111 1111
|-
| 8 || 1000 || 1111 1111
|}
Pour convertir un nombre codé en unaire vers le binaire, il suffit de compter le nombre de 1. C'est ni plus ni moins ce que fait l'opération dite de ''population count'' dont nous avons parlé plus haut. La représentation unaire permet d'encoder N+1 valeurs sur N bits, car le zéro est encodé à part. Une valeur pour le zéro, plus une par bit.
Avec la '''représentation ''one-hot''''', un nombre est codé sur N bits, dont un seul bit est à 1. La position du 1 dans le nombre indique sa valeur. Précisément, la valeur encodée est égale au poids du bit à 1. En faisant cela, le zéro est codé en mettant le bit de poids faible à 1, la valeur 1 est codée avec les bits 00...00010, la valeur 2 est codée par les bits 00...000100, etc.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Elle permet d'encoder N valeurs différentes avec N bits, zéro inclus. On perd une valeur par rapport à l'unaire, car le zéro n'et pas encodé à part du reste. La traduction du binaire vers le ''one-hot'' est réalisé par un circuit nommé décodeur, que nous verrons d'ici quelques chapitres. De même, la traduction d'un nombre encodé en ''one-hot'' vers le binaire est réalisée par un circuit électronique appelé l'encodeur, qui est en quelque sorte l'inverse de l'encodeur. Les deux circuits sont très utilisés en électronique, ce sont des circuits de base, que nous n'aurons de cesse d'utiliser pour fabriquer d'autres circuits.
L'utilité de ces deux représentations n'est pas évidente. Mais sachez qu'elle le deviendra quand nous parlerons des circuits appelés les "compteurs", tout comme ce sera le cas pour le code Gray. Elles sont très utilisées dans des circuits appelés des machines à état, qui doivent incorporer des circuits compteurs efficients. Et ces représentations permettent d'avoir des circuits pour compter qui sont très simples, efficaces, rapides et économes en circuits électroniques. La représentation unaire sera aussi utile à la toute fin du cours, dans les chapitres liés à l'exécution dans le désordre. Il en sera fait référence quand nous parlerons de fenêtres d'instruction, d'émission dans l'ordre, de ''scoreboarding'', etc.
Il faut aussi noter qu'il a existé des ordinateur qui manipulaient des nombres codés en unaire. Enfin presque, la représentation utilisée était proche de l'unaire, mais ressemblait fortement. De tels ordinateurs étaient appelés des ''ordinateurs stochastiques'' et nous les aborderons à la toute fin de ce wikilivre, dans un chapitre annexe portant sur les ordinateurs ternaires (qui comptent en base 3) et unaires.
==Les encodages hybrides entre décimal et binaire==
Pour terminer, nous allons voir les représentations qui mixent binaire et décimal. Il s'agit en réalité d'encodage qui permettent de manipuler des nombres codés en décimal, mais les chiffres sont codés en utilisant des bits. L'encodage ''Binary Coded Decimal'' est le plus connu de cette catégorie, mais il y en a quelques autres, qui sont moins connus. De telles représentations étaient très utilisées au début de l'informatique, mais sont aujourd'hui tombées en désuétude. Pour désigner ces encodages, nous parlerons d''''encodages bit-décimaux''' : décimaux pour préciser qu'ils encodent des nombres codés en décimal, bit pour préciser que les chiffres sont codés avec des bits.
De tels encodages étaient utilisés sur les tous premiers ordinateurs pour faciliter le travail des programmeurs, mais aussi sur les premières calculettes. Ils sont utiles dans les applications où on doit manipuler chaque chiffre décimal séparément des autres. Un exemple classique est celui d'une horloge digitale. Ils ont aussi l'avantage de bien se marier avec les nombres à virgule. Les représentations des nombres à virgule fixe ou flottante ont le défaut que des arrondis peuvent survenir. Par exemple, la valeur 0,2 est codée comme suit en binaire normal : 0.00110011001100... et ainsi de suite jusqu’à l'infini. Avec les encodages bit-décimaux, on n'a pas ce problème, 0,2 étant codé 0000 , 0010.
Il a existé des ordinateurs qui travaillaient uniquement avec de tels encodages, appelés des '''ordinateurs décimaux'''. Ils étaient assez courants entre les années 60 et 70, même s'ils ne représentent pas la majorité des architectures de l'époque. Avec eux, la mémoire n'était pas organisée en octets, mais elle stockait des chiffres décimaux codés sur 5-6 bits. Le processeur faisait des calculs sur des chiffres bit-décimaux directement.
Leur grand avantage est leur très bonne performance dans les taches de bureautique, de comptabilité, et autres. Les processeurs de l'époque recevaient des entiers codés en bit-décimal de la part des entrées-sorties, et devaient les traiter. Les processeurs binaires devaient faire des conversions décimal-binaire pour communiquer avec les entrées-sorties, mais pas les processeurs décimaux. Le gain en performance pouvait être substantiel dans certaines applications.
Les ordinateurs décimaux se classent en deux sous-types bien précis. Les premiers gèrent des entiers qui ont un nombre de chiffres fixes. Par exemple, l'IBM 7070 gérait des entiers de 10 chiffres, plus un signe +/- pour les entiers signés. Le processeur faisait les calculs directement en bit-décimal, il gérait des entiers faisant environ 10-15 chiffres décimaux et savait faire des calculs avec de tels nombres. Le second sous-type effectue les calculs chiffre par chiffre et géraient des nombres de taille variable, sans limite de chiffres ! Nous reparlerons de ces derniers dans un chapitre ultérieur, quand nous parlerons de la différence entre byte et mot. Pour le moment, ne gardez à l'esprit que les processeurs gérant un nombre fixe de chiffres décimaux, plus simples à comprendre.
===Le ''Binary Coded Decimal''===
Le '''''Binary Coded Decimal''''', abrévié BCD, est une représentation qui mixe binaire et décimal. Avec cette représentation, les nombres sont écrits en décimal, comme nous en avons l'habitude dans la vie courante, sauf que chaque chiffre décimal est directement traduit en binaire sur 4 bits. Prenons l'exemple du nombre 624 : le 6, le 2 et le 4 sont codés en binaire séparément, ce qui donne 0110 0010 0100.
{| class="wikitable"
|+ Codage BCD
|-
! Nombre encodé (décimal)
! scope="col" colspan="4" | BCD
|-
| 0 || 0 || 0 || 0 || 0
|-
| 1 || 0 || 0 || 0 || 1
|-
| 2 || 0 || 0 || 1 || 0
|-
| 3 || 0 || 0 || 1 || 1
|-
| 4 || 0 || 1 || 0 || 0
|-
| 5 || 0 || 1 || 0 || 1
|-
| 6 || 0 || 1 || 1 || 0
|-
| 7 || 0 || 1 || 1 || 1
|-
| 8 || 1 || 0 || 0 || 0
|-
| 9 || 1 || 0 || 0 || 1
|}
On peut remarquer que 4 bits permettent de coder 16 valeurs, là où il n'y a que 10 chiffres. Dans le BCD proprement dit, les combinaisons de bits qui correspondent à 10, 11, 12, 13, 14 ou 15 ne sont tout simplement pas prises en compte. Sur quelques ordinateurs, ces combinaisons codent des chiffres décimaux en double : certains chiffres pouvaient être codés de deux manières différentes. Il est aussi possible d'utiliser ces valeurs pour coder quelque chose d'autre que des chiffres. Par exemple, il est possible de les utiliser pour coder un signe + ou -, afin de gérer les entiers relatifs. Une autre possibilité, complémentaire de la précédente, utilise ces valeurs en trop pour coder une virgule, afin de gérer les nombres non-entiers. Les possibilités sont nombreuses.
Le support du BCD implique souvent que le processeur supporte des '''opérations BCD''', à savoir des opérations capables de travailler sur des opérandes en BCD et de donner un résultat en BCD. Il faut bien faire la différence entre les opérations en binaire et les opérations en BCD. Par exemple, on n'effectue pas une addition de la même manière en binaire et en décimal/BCD, même si les grandes lignes sont presque identiques. Les différences font que les processeurs doivent avoir des opérations différentes pour les deux encodages, de la même manière que les processeurs gèrent les opérations sur les flottants et les entiers séparément.
Le support du codage BCD est abandonné de nos jours, c'est surtout quelque chose qu'on trouve sur les anciens processeurs. Les architectures 8 et 16 bits supportaient à la fois des opérations binaires et des opérations BCD. Mais il a aussi existé une méthode intermédiaire, qui utilisait des additions binaires normales sur des opérandes BCD. L'idée est que le résultat de l'addition est incorrect, car les valeurs 10 à 15 peuvent apparaître comme chiffre dans le résultat. Mais on peut le corriger pour obtenir le résultat exact en BCD. Pour résumé, les processeurs faisaient des additions en binaire, et corrigeaient le résultat avec une opération spécifique pour obtenir un résultat en BCD. Nous en reparlerons dans le chapitre sur le langage machine et l'assembleur, dans lequel nous étudierons les différentes opérations que supporte un processeur.
===Les encodages compacts du BCD===
Des variantes du BCD visent à réduire le nombre de bits utilisés pour encoder un nombre décimal. Nous allons les appeler les '''encodages BCD compacts'''. Avec certaines variantes, on peut utiliser seulement 7 bits pour coder deux chiffres décimaux, au lieu de 8 bits en BCD normal. Idem pour les nombres à 3 chiffres décimaux, qui prennent 10 bits au lieu de 12. Cette économie est réalisée par une variante du BCD assez compliquée, appelée l''''encodage Chen-Ho'''. Une alternative, appelée le ''Densely Packed Decimal'', arrive à une compression identique, mais avec quelques avantages au niveau de l'encodage. Ces encodages sont cependant assez compliqués à expliquer, surtout à ce niveau du cours, aussi je me contente de simplement mentionner leur existence.
Les encodages BCD compacts sont utilisés par les programmeurs pour stocker des données dans un fichier, en mémoire, mais guère plus. Ils ne sont pas gérés par le processeur directement, on ne peut pas faire de calculs avec, le processeur ne gére pas d'opération BCD supportant de tels encodages. C'est théoriquement possible, mais ça n'a jamais été implémenté dans un processeur, le cout en circuit n'en valait pas la chandelle. Pour faire des calculs sur des nombres en BCD compact, on doit les décompresser et les convertir en BCD normal ou en binaire, puis faire les calculs avec des opérations BCD/binaire usuelles.
===L'encodage ''Excess-3''===
La représentation '''''Excess-3''''' (XS-3) est une variante du BCD, qui a autrefois était utilisée sur d'anciens ordinateurs décimaux. Il s'agit d'une sorte d'hybride entre une représentation par excès et BCD. Chaque chiffre décimal est codé sur plusieurs bits en utilisant une représentation binaire par excès. Le biais est de 3 pour la représentation XS-3, il en existe des variantes avec un excès de 4, 5, mais elles sont moins utilisées. Avec elle, la conversion d'un chiffre décimal se fait comme suit : on prend le chiffre décimal, on ajoute 3, puis on traduit en binaire.
{| class="wikitable"
|+ Codage en ''Excess-3''
|-
! Décimal
! scope="col" colspan="4" | Binaire
|-
| -3 || 0 || 0 || 0 || 0
|-
| -2 || 0 || 0 || 0 || 1
|-
| -1 || 0 || 0 || 1 || 0
|-
| 0 || 0 || 0 || 1 || 1
|-
| 1 || 0 || 1 || 0 || 0
|-
| 2 || 0 || 1 || 0 || 1
|-
| 3 || 0 || 1 || 1 || 0
|-
| 4 || 0 || 1 || 1 || 1
|-
| 5 || 1 || 0 || 0 || 0
|-
| 6 || 1 || 0 || 0 || 1
|-
| 7 || 1 || 0 || 1 || 0
|-
| 8 || 1 || 0 || 1 || 1
|-
| 9 || 1 || 1 || 0 || 0
|-
| 10 || 1 || 1 || 0 || 1
|-
| 11 || 1 || 1 || 1 || 0
|-
| 12 || 1 || 1 || 1 || 1
|}
L'avantage de cette représentation est que l'on peut facilement calculer les soustractions en utilisant une méthode bien précise (celle du complément à 10, je ne détaille pas plus). Le défaut est que le calcul des additions est légèrement plus complexe.
L'XS-3 a été utilisé sur quelques ordinateurs décimaux assez anciens, notamment sur l'UNIVAC I et II.
===Les codes bi-quinaires===
Les '''codes bi-quinaires''' utilisent un mélange entre encodage binaire, encodage ''one-hot'' et encodage décimal. L'idée de base derrière ces codes est d'utiliser la représentation ''one-hot'' pour coder un chiffre BCD. Le problème est que cela demande d'utiliser 10 bits pour encoder un chiffre BCD, ce qui est beaucoup. L'encodage bi-quinaire optimise le tout de manière à diviser par deux le nombre de bits requis pour l'encodage.
Pour simplifier, un chiffre encodé en bi-quinaire est composé de deux parties : une partie binaire de un bit, suivie par d'une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits pour encoder les valeurs 0, 1, 2, 3 et 4. La partie binaire indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9 : 0 à 4 si la partie binaire vaut 0, 5 à 9 si elle vaut 1. La valeur du chiffre se calcule comme suit :
* si la partie binaire vaut 0, elle est égale à la partie quinaire ;
* si la partie binaire vaut 1, on ajoute 5 à la partie quinaire.
L'originalité de ce codage est qu'il permet de facilement compter sur ses doigts : on a deux mains, cinq doigts. De nombreuses langues utilisent ce système pour coder des nombres, comme le Wolof. Et ce système est utilisé dans certains pays pour compter sur ses doigts, voire dans la vie de tous les jours.
[[File:Code Biquinaer.svg|vignette|Code bi-quinaire où partie binaire et quinaire sont encodées en représentation ''one-hot''.]]
L'encodage abordé au-dessus est une version simplifiée de l'encodage bi-quinaire, il en existe d'autres variantes. Le bi-quinaire vu juste au-dessus encode la partie binaire en binaire, et la partie quinaire en ''one-hot''. Mais il est aussi possible d'inverser l'encodage, à savoir encoder la partie binaire avec une représentation ''one-hot'', alors que la partie bi-quinaire est encodée en binaire normal.
Cependant, la version la plus intéressante utilise l'encodage ''one-hot'' pour les deux ! Elle est illustrée ci-contre. Avec cette dernière, un chiffre BCD est encodé sur 7 bits, dont 2 sont obligatoirement à 1 et les 5 restants sont à 0. Cette propriété permet de détecter qu'un bit a été inversé suite à une erreur de transmission ou un problème de stockage. Si un bit est inversé, le résultat aura 1 bit à 1 et 6 à 0, ou alors 3 bits à 1 et 4 à 0, ce qui ne colle pas. Le résultat après inversion ne donnera pas un chiffre bi-quinaire valide.
Une autre variante du bi-quinaire a été utilisée sur l'IBM 1401. Avec elle, la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre encodé par la partie quinaire. L'avantage de ce système est que la gestion des retenues lors des additions était grandement simplifiée.
Le bi-quinaire et ses variantes ont été utilisés sur d'anciens ordinateurs, dont l'IBM 650, l'UNIVAC Solid State et le UNIVAC LARC. La plupart utilisaient des variantes de l'encodage bi-quinaire proprement dit. Pour rentrer vraiment dans le détail, voici les encodages utilisés sur ces machines. La lecture est facultative, il est possible de passer directement à la section suivante :
{| class="wikitable"
|-
! !! IBM 650 !! Remington Rand 409 !! UNIVAC Solid State !! UNIVAC LARC
|-
| Chiffre || 1357-9 bits || 05-01234 || p-5-421 bits || p-5-qqq bits
|-
| 0 || 10-10000 || 0000-0 || 1-0-000 || 1-0-000
|-
| 1 || 10-01000 || 1000-0 || 0-0-001 || 0-0-001
|-
| 2 || 10-00100 || 1000-1 || 0-0-010 || 1-0-011
|-
| 3 || 10-00010 || 0100-0 || 1-0-011 || 0-0-111
|-
| 4 || 10-00001 || 0100-1 || 0-0-100 || 1-0-110
|-
| 5 || 01-10000 || 0010-0 || 0-1-000 || 0-1-000
|-
| 6 || 01-01000 || 0010-1 || 1-1-001 || 1-1-001
|-
| 7 || 01-00100 || 0001-0 || 1-1-010|| 0-1-011
|-
| 8 || 01-00010 || 0001-1 || 0-1-011 || 1-1-111
|-
| 9 || 01-00001 || 0000-1 || 1-1-100|| 0-1-110
|}
==Annexe : pourquoi l'octet existe==
Nous avons vu plus haut que les nombres encodés en binaire sont découpés en octets, des paquets de 8 bits. Et les octets sont présents partout dans la conception d'un ordinateur. Par exemple, la mémoire RAM d'un ordinateur moderne est organisée en octets. Elle contient des cases mémoire, chacune contenant un octet. Les octets sont numérotés, ce qui permet de sélectionner un octet parmi tous les autres. La capacité de la mémoire est le nombre d'octets qu'elle contient, elle s'exprime en kilo-octets (1024 octets), mébi-octets (1024 kilo-octets), gibi-octets (1024 mébi-octets). Les octets sont donc présents partout dans l'informatique moderne.
{|class="wikitable"
|+ Contenu de la RAM, simplifié
|-
! Numéro
! Octet
|-
! 0
| 0000 1111
|-
! 1
| 0101 0101
|-
! 2
| 0111 0110
|-
! 3
| 0011 1010
|-
! 4
| 1001 1001
|-
! ...
| ...
|-
! 1023
| 0110 1010
|-
! 1024
| 0001 1110
|}
Mais pourquoi est-ce que les nombres encodés en binaire sont souvent découpés en octets ? Pourquoi ne pas utiliser des groupes de 3, 5, 6, 7, 9, 10 bits ? La question est d'autant plus légitime que d'anciens ordinateurs faisaient ainsi. Leurs mémoires étaient découpé en cases mémoire, mais chacune faisait 7 ou 9 bits, rien d'impossible à cela. À vrai dire, l'usage des octets au tout début de l'informatique était assez rare. Il y a eu un basculement progressif vers l'octet pendant les années 80-90. Reste à expliquer pourquoi. Si on devait résumer, les deux raisons principales sont le support du BCD et le support des caractères pour le texte. Voyons d'abord l'encodage des caractères en binaire.
===L'encodage du texte : ASCII et autres encodages===
Pour les caractères, les encodages se sont concentrés sur l'encodage des alphabets occidentaux. Ils demandaient d'encoder au minimum toutes les lettres, en majuscules et minuscules, plus les chiffres, une dizaine de caractères pour la ponctuation. À cela, il faut ajouter des symboles utiles comme %, µ, $, £, #, {} et bien d'autres. Au minimum, cela fait au minimum 52 lettres (26 lettres doublées pour les majuscules) + 10 chiffres + une dizaine de caractères pour la ponctuation : cela fait plus de 72 valeurs à encoder. Pour encoder cela en binaire, le minimum est de 7 bits, ce qui permet d'encoder 128 caractères : les 72 caractères minimum, plus quelques autres. L'encodage ASCII utilisait d'ailleurs 7 bits pour encoder des nombres.
On pourrait croire que c'était une raison suffisante pour utiliser des groupes de 7 bits. Mais il y a quelques problèmes avec cela. Premièrement, les encodages 7 bits pour le texte marchent bien pour gérer la langue anglaise et la plupart des langues occidentales, mais d'autres langues ont besoin d'encoder des caractères locaux. Les 128 caractères de l'ASCII ne sont pas suffisant, ce qui demande de passer de 7 à 8 bits. On retombe donc sur un octet ! D'ailleurs, les encodages ASCII améliorés utilisaient les 127 premières valeurs pour l'ASCII normal, mais les 128 suivantes encodaient d'autres caractères. Les encodages en question varient d'un pays ou d'une langue à l'autre, mais ils sont terriblement importants, ce qui est un argument pour l'usage des octets.
===Le support des données encodées sur un ''nibble''===
Une autre raison pour l'usage des octets est qu'il est facile de les découper en deux ''nibbles''. Pour rappel, les ''nibbles'' sont des groupes de 4 bits. Et il se trouve que de nombreuses données sont encodées sur 4 bits, ni plus ni moins. Les ''nibbles'' sont très important pour le support du BCD et de l’hexadécimal. Et c'était un avantage important au début de l'informatique, moins maintenant. Un octet regroupe deux ''nibbles'' : un ''nibble'' de poids faible, un ''nibble'' de poids fort.
[[File:Nibbles in a byte.PNG|centre|vignette|upright=2|Nibbles dans un octet.]]
Un chiffre BCD occupe un ''nibble'', ce qui fait que la taille idéale de l'unité mémoire doit être un multiple de 4 bits : 4 bits, 8 bits, 12 bits, 16 bits, etc. Un ''nibble'' de 4 bits est insuffisant pour encoder des caractères, 8 bits fonctionne parfaitement, pas besoin d'aller plus loin. De plus, l'usage des ''nibbles'' permet de gérer la représentation hexadécimale très facilement. Un chiffre hexadécimal est encodé sur exactement un ''nibble'', un octet regroupe deux chiffres hexadécimaux, deux octets 4 chiffres hexadécimaux, etc. Enfin, 8 bits est une puissance de deux, ce qui simplifie grandement l'encodage des entiers en binaire est est une source de nombreuses simplifications. Les circuits de l'ordinateur sont plus simple à concevoir quand ils manipulent des opérandes de 2, 4, 8, 16, 32, 64, 128 bits.
===L'influence sur la taille des opérandes===
Les anciens ordinateurs étaient conçus pour le calcul scientifique, ou du moins des applications similaires. Ils n'avaient pas besoin de supporter le BCD, ni même de gérer du texte. Ils ne faisaient que des calculs et utilisaient des opérandes flottantes. La taille des opérandes était dictée par des considérations calculatoires. La norme à l'époque était que les calculs scientifiques devaient gérer des opérandes pouvant aller jusqu'à 10 chiffres décimaux. Et cela demande d'utiliser des opérandes entiers de 35 bits, avec un bit en plus pour encoder des opérandes positives ou négatives. Les 36 bits étaient parfois découpés en 6 groupes de 6 bits chacun, au lieu des octets de 8 bits.
Mais ces groupes de 6 bits étaient souvent peu utilisés. Prenons l'exemple des mémoires RAM. Plus haut, j'ai dit qu'une mémoire RAM normale est composée de plusieurs octets, tous numérotés. Intuitivement, vous vous dites que les anciens ''mainframes'' remplaçaient ces octets par des groupes de 6 bits dans la RAM. Mais en réalité, l'équivalent des RAM de l'époque utilisait des groupes de 36 bits. les groupes de 36 bits étaient aussi numérotés, les numéros étant appelés des adresses mémoire. Par contre, les adresses/numéros étaient codés sur 12 ou 18 bits, c'est à dire sur 2 ou 3 groupes de 6 bits. La raison est que ces numéros sont manipulés par le processeur, qui gère des opérandes de 36 bits. L'idée est que le numéro ne prend que la moitié ou un tiers des 36 bits gérés par le processeur.
Avec l'apparition de l'informatique commerciale, les ordinateurs ont du s'adapter aux besoins des entreprises, pour gérer la comptabilité, des applications financières, des documents texte, etc. Et cela demandait de supporter à la fois le BCD et les caractères/lettres. Il a fallu trouver une unité mémoire capable de gérer à la fois nombres entiers, chiffres BCD et caractères. L'octet était parfait pour ça. Les ''mainframes'' sont adaptés en supportant des caractères codés sur 6, 7 ou 8 bits. Mais le support demandait d'utiliser des circuits qui découpaient un groupe de 36 bits en plusieurs caractères ou en plusieurs chiffres BCD. Le tout demandait des circuits de décalage et de masquage assez complexes. Les ordinateurs 8 et 16 bits manipulaient des octets nativement : leur mémoire RAM gérait des octets, le processeur traitait des opérandes de 8, 16 ou 32 bits. Ils n'avaient pas besoin de matériel complexe pour extraire un caractère ou un chiffre BCD d'un octet/doublet/multiplet. C'était un avantage que les anciens ''mainframes'' n'avaient pas.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'encodage des données
| prevText=L'encodage des données
| next=Les codes de détection/correction d'erreur
| nextText=Les codes de détection/correction d'erreur
}}
</noinclude>
fwu1ivwvedm2q124o7kfjul08nw67hx
775644
775643
2026-11-02T19:40:28Z
Mewtow
31375
/* Le codage des nombres flottants et la norme IEEE 754 */
775644
wikitext
text/x-wiki
Dans le chapitre précédent, nous avons vu que les ordinateurs actuels utilisent un codage binaire. Ce codage binaire ne vous est peut-être pas familier. Aussi, dans ce chapitre, nous allons apprendre comment coder des nombres en binaire. Nous allons commencer par le cas le plus simple : les nombres positifs. Par la suite, nous aborderons les nombres négatifs. Et nous terminerons par les nombres à virgules, appelés aussi nombres flottants.
==Le codage des nombres entiers positifs==
Pour coder des nombres entiers positifs, il existe plusieurs méthodes : le binaire, l’hexadécimal, le code Gray, le décimal codé binaire et bien d'autres encore. La plus connue est certainement le binaire, secondée par l'hexadécimal, les autres étant plus anecdotiques. Pour comprendre ce qu'est le binaire, il nous faut faire un rappel sur les nombres entiers tel que vous les avez appris en primaire, à savoir les entiers écrits en décimal. Prenons un nombre écrit en décimal : le chiffre le plus à droite est le chiffre des unités, celui à côté est pour les dizaines, suivi du chiffre des centaines, et ainsi de suite. Dans un tel nombre :
* on utilise une dizaine de chiffres, de 0 à 9 ;
* chaque chiffre est multiplié par une puissance de 10 : 1, 10, 100, 1000, etc. ;
* la position d'un chiffre dans le nombre indique par quelle puissance de 10 il faut le multiplier : le chiffre des unités doit être multiplié par 1, celui des dizaines par 10, celui des centaines par 100, et ainsi de suite.
Exemple avec le nombre 1337 :
<math>1337 = 1 \times 1000 + 3 \times 100 + 3 \times 10 + 7 \times 1</math>
Pour résumer, un nombre en décimal s'écrit comme la somme de produits, chaque produit multipliant un chiffre par une puissance de 10. On dit alors que le nombre est en base 10.
<math>1337 = 1 \times 10^3 + 3 \times 10^2 + 3 \times 10^1 + 7 \times 10^0</math>
Ce qui peut être fait avec des puissances de 10 peut être fait avec des puissances de 2, 3, 4, 125, etc : n'importe quel nombre entier strictement positif peut servir de '''base'''. En informatique, on utilise rarement la base 10 à laquelle nous sommes tant habitués. On utilise à la place deux autres bases :
* La base 2 (système binaire) : les chiffres utilisés sont 0 et 1 ;
* La base 16 (système hexadécimal) : les chiffres utilisés sont 0, 1, 2, 3, 4, 5, 6, 7, 8 et 9 ; auxquels s'ajoutent les six premières lettres de notre alphabet : A, B, C, D, E et F.
===Le système binaire===
En binaire, on compte en base 2. Cela veut dire qu'au lieu d'utiliser des puissances de 10 comme en décimal, on utilise des puissances de deux : n'importe quel nombre entier peut être écrit sous la forme d'une somme de puissances de 2. Par exemple 6 s'écrira donc 0110 en binaire : <math>0 \times 2^3 + 1 \times 2^2 + 1 \times 2^1 + 0 \times 2^0 = 6</math>. On peut remarquer que le binaire n'autorise que deux chiffres, à savoir 0 ou 1 : ces chiffres binaires sont appelés des '''bits''' (abréviation de Binary Digit). Pour simplifier, on peut dire qu'un bit est un truc qui vaut 0 ou 1. Pour résumer, tout nombre en binaire s'écrit sous la forme d'un produit entre bits et puissances de deux de la forme :
<math>a_0 \times 2^0 + a_1 \times 2^1 + a_2 \times 2^2 + a_3 \times 2^3 + a_4 \times 2^4 +...+ a_n \times 2^n</math>
Les coefficients <math>a_0, a_1, a_2</math> sont les bits, l'exposant n qui correspond à un bit <math>a_n</math> est appelé le '''poids''' du bit.
====La terminologie du binaire====
En informatique, il est rare que l'on code une information sur un seul bit. Dans la plupart des cas, l'ordinateur manipule des nombres codés sur plusieurs bits. Les informaticiens ont donné des noms aux groupes de bits suivant leur taille. Le plus connu est certainement l''''octet''', qui désigne un groupe de 8 bits. Moins connu, on parle de '''''nibble''''' pour un groupe de 4 bits (un demi-octet), de '''doublet''' pour un groupe de 16 bits (deux octets) et de '''quadruplet''' pour un groupe de 32 bits (quatre octets).
Précisons qu'en anglais, le terme ''byte'' n'est pas synonyme d'octet. En réalité, le terme ''octet'' marche aussi bien en français qu'en anglais. Quant au terme ''byte'', il désigne un concept complètement différent, que nous aborderons plus tard (c'est la plus petite unité de mémoire que le processeur peut adresser). Il a existé dans le passé des ordinateurs où le ''byte'' faisait 4, 7, 9, 16, voire 48 bits, par exemple. Il a même existé des ordinateurs où le ''byte'' faisait exactement 1 bit ! Mais sur presque tous les ordinateurs modernes, un ''byte'' fait effectivement 8 bits, ce qui fait que le terme ''byte'' est parfois utilisé en lieu et place d'''octet''. Mais c'est un abus de langage, attention aux confusions ! Dans ce cours, nous parlerons d'octet pour désigner un groupe de 8 bits, en réservant le terme ''byte'' pour sa véritable signification.
À l'intérieur d'un nombre, le '''bit de poids faible''' est celui qui est le plus à droite du nombre, alors que le '''bit de poids fort''' est celui non nul qui est placé le plus à gauche, comme illustré dans le schéma ci-dessous.
C'est le même principe que l'écriture des nombres en base décimale : le chiffre le plus significatif à gauche, et le moins significatif à droite (l'unité ou les décimales après la virgule).
[[File:Msb.svg|centre|vignette|upright=2|Bit de poids fort.]]
[[File:Lsb.svg|centre|vignette|upright=2|Bit de poids faible.]]
Cette terminologie s'applique aussi pour les bits à l'intérieur d'un octet, d'un ''nibble'', d'un doublet ou d'un quadruplet. Pour un nombre codés sur plusieurs octets, on peut aussi parler de l'octet de poids fort et de l'octet de poids faible, du doublet de poids fort ou de poids faible, etc.
====La traduction binaire→décimal====
Pour traduire un nombre binaire en décimal, il faut juste se rappeler que la position d'un bit indique par quelle puissance il faut le multiplier. Ainsi, le chiffre le plus à droite est le chiffre des unités : il doit être multiplié par 1 (<math>2^0</math>). Le chiffre situé immédiatement à gauche du chiffre des unités doit être multiplié par 2 (<math>2^1</math>). Le chiffre encore à gauche doit être multiplié par 4 (<math>2^2</math>), et ainsi de suite. Mathématiquement, on peut dire que le énième bit en partant de la droite doit être multiplié par <math>2^{n-1}</math>. Par exemple, la valeur du nombre noté 1011 en binaire est de <math>8 \times 1 + 4 \times 0 + 2 \times 1 + 1 \times 1 = 11</math>.
[[File:Value of digits in the Binary numeral system.svg|centre|vignette|upright=2|Valeur des chffres dans le système de numération binaire.]]
====La traduction décimal→binaire====
La traduction inverse, du décimal au binaire, demande d'effectuer des divisions successives par deux. Les divisions en question sont des divisions euclidiennes, avec un reste et un quotient. En lisant les restes des divisions dans un certain sens, on obtient le nombre en binaire. Voici comment il faut procéder, pour traduire le nombre 34 :
[[File:Exemple d'illustration de la méthode de conversion décimal vers binaire.png|centre|vignette|upright=2|Exemple d'illustration de la méthode de conversion décimal vers binaire.]]
====Quelques opérations en binaire====
Maintenant que l'on sait coder des nombres en binaire normal, il est utile de savoir comment faire quelques opérations usuelles en binaire. Nous utiliserons les acquis de cette section dans la suite du chapitre, bien que de manière assez marginale.
La première opération est assez spécifique au binaire. Il s'agit d'une opération qui inverse les bits d'un nombre : les 0 deviennent des 1 et réciproquement. Par exemple, le nombre 0001 1001 devient 1110 0110. Elle porte plusieurs noms : opération NOT, opération NON, complémentation, etc. Nous parlerons de '''complémentation''' ou d'opération NOT dans ce qui suit. Beaucoup d'ordinateurs gèrent cette opération, ils savent la faire en un seul calcul. Il faut dire que c'est une opération assez utile, bien que nous ne pouvons pas encore expliquer pourquoi.
[[File:Binary Addition Demonstration.svg|vignette|Exemple d'addition en binaire.]]
La seconde opération à aborder est l'addition. Elle se fait en binaire de la même manière qu'en décimal : Pour faire une addition en binaire, on additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. Sauf que l'on additionne des bits. Heureusement, la table d'addition est très simple en binaire :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
La troisième opération est une variante de l'addition appelée l''''opération XOR''', notée <math>\oplus</math>. Il s'agit d'une addition dans laquelle on ne propage pas les retenues. L'addition des deux bits des opérandes se fait normalement, mais les retenues sont simplement oubliées, on n'en tient pas compte. Le résultat est que l'addition se résume à appliquer la table d'addition précédente :
* 0 <math>\oplus</math> 0 = 0 ;
* 0 <math>\oplus</math> 1 = 1 ;
* 1 <math>\oplus</math> 0 = 1 ;
* 1 <math>\oplus</math> 1 = 0.
Pour résumer, le résultat vaut 1 si les deux bits sont différents, 0 s'ils sont identiques. L'opération XOR sera utilisée rapidement dans le chapitre suivant, quand nous parlerons rapidement du mot de parité. Et elle sera beaucoup utilisée dans la suite du cours, nous en feront fortement usage. Pour le moment, mémorisez juste cette opération, elle n'a rien de compliqué.
Une dernière opération est l'opération de '''''population count'''''. Il s'agit ni plus ni moins que de compter le nombre de bits qui sont à 1 dans un nombre. Par exemple, pour le nombre 0110 0010 1101 1110, elle donne pour résultat 9. Elle est utilisée dans certaines applications, comme le calcul de certains codes correcteurs d'erreur, comme on le verra dans le chapitre suivant. Elle est supportée sur de nombreux ordinateurs, encore que cela dépende du processeur considéré. Il s'agit cependant d'une opération assez courante, supportée par les processeurs ARM, les processeurs x86 modernes (ceux qui gèrent le SSE), et quelques autres.
===L'hexadécimal===
L’hexadécimal est basé sur le même principe que le binaire, sauf qu'il utilise les 16 chiffres suivants :
{| class="wikitable"
|- style="color:#8080ffff; text-align:center;"
! Chiffre hexadécimal
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || A || B || C || D || E || F
|- style="text-align:center;"
! Nombre décimal correspondant
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 10 || 11 || 12 || 13 || 14 || 15
|- style="text-align:center;"
! Notation binaire
| 0000 || 0001 || 0010 || 0011 || 0100 || 0101 || 0110 || 0111 || 1000 || 1001 || 1010 || 1011 || 1100 || 1101 || 1110 || 1111
|}
Dans les textes, afin de différencier les nombres décimaux des nombres hexadécimaux, les nombres hexadécimaux sont suivis par un petit h, indiqué en indice. Si cette notation n'existait pas, des nombres comme 2546 seraient ambigus : on ne saurait pas dire sans autre indication s'ils sont écrits en décimal ou en hexadécimal. Avec la notation, on sait de suite que 2546 est en décimal et que 2546h est en hexadécimal.
Dans les codes sources des programmes, la notation diffère selon le langage de programmation.
Certains supportent le suffixe <code>h</code> pour les nombres hexadécimaux, d'autres utilisent un préfixe <code>0x</code> ou <code>0h</code>.
Pour convertir un nombre hexadécimal en décimal, il suffit de multiplier chaque chiffre par la puissance de 16 qui lui est attribuée. Là encore, la position d'un chiffre indique par quelle puissance celui-ci doit être multiplié : le chiffre le plus à droite est celui des unités, le second chiffre le plus à droite doit être multiplié par 16, le troisième chiffre en partant de la droite doit être multiplié par 256 (16 * 16) et ainsi de suite. La technique pour convertir un nombre décimal vers de l’hexadécimal est similaire à celle utilisée pour traduire un nombre du décimal vers le binaire. On retrouve une suite de divisions successives, mais cette fois-ci les divisions ne sont pas des divisions par 2 : ce sont des divisions par 16.
La conversion inverse, de l'hexadécimal vers le binaire est très simple, nettement plus simple que les autres conversions. Pour passer de l'hexadécimal au binaire, il suffit de traduire chaque chiffre en sa valeur binaire, celle indiquée dans le tableau au tout début du paragraphe nommé « Hexadécimal ». Une fois cela fait, il suffit de faire le remplacement. La traduction inverse est tout aussi simple : il suffit de grouper les bits du nombre par 4, en commençant par la droite (si un groupe est incomplet, on le remplit avec des zéros). Il suffit alors de remplacer le groupe de 4 bits par le chiffre hexadécimal qui correspond.
==Interlude propédeutique : la capacité d'un entier et les débordements d'entiers==
Dans la section précédente, nous avons vu comment coder des entiers positifs en binaire ou dans des représentations proches. La logique voudrait que l'on aborde ensuite le codage des entiers négatifs. Mais nous allons déroger à cette logique simple, pour des raisons pédagogiques. Nous allons faire un interlude qui introduira des notions utiles pour la suite du chapitre. De plus, ces concepts seront abordés de nombreuses fois dans ce wikilivre et l'introduire ici est de loin la solution idéale.
===Les ordinateurs manipulent des nombres codés sur un nombre fixe de bits===
Vous avez certainement déjà entendu parler de processeurs 32 ou 64 bits. Et si vous avez joué aux jeux vidéos durant votre jeunesse et êtes assez agé, vous avez entendu parler de consoles de jeu 8 bits, 16 bits, 32 bits, voire 64 bits (pour la Jaguar, et c'était un peu trompeur). Derrière cette appellation qu'on retrouvait autrefois comme argument commercial dans la presse se cache un concept simple. Tout ordinateur manipule des nombres entiers dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Une console 16 bits manipulait des entiers codés en binaire sur 16 bits, pas un de plus, pas un de moins. Pareil pour les anciens ordinateurs 32 bits, qui manipulaient des nombres entiers codés sur 32 bits.
Aujourd'hui, les ordinateurs modernes utilisent presque un nombre de bits qui est une puissance de 2 : 8, 16, 32, 64, 128, 256, voire 512 bits. Mais cette règle souffre évidemment d'exceptions. Aux tout débuts de l'informatique, certaines machines utilisaient 3, 7, 13, 17, 23, 36 et 48 bits ; mais elles sont aujourd'hui tombées en désuétude. De nos jours, il ne reste que les processeurs dédiés au traitement de signal audio, que l'on trouve dans les chaînes HIFI, les décodeurs TNT, les lecteurs DVD, etc. Ceux-ci utilisent des nombres entiers de 24 bits, car l'information audio est souvent codée par des nombres de 24 bits.
Anecdote amusante, il a existé des ordinateurs de 1 bit, qui sont capables de manipuler des nombres codés sur 1 bit, pas plus. Un exemple est le Motorola MC14500B, commercialisé de 1976.
====Le lien entre nombre de bits et valeurs codables====
Évidemment, on ne peut pas coder tous les entiers possibles et imaginables avec seulement 8 bits, ou 16 bits. Et il parait intuitif que l'on ait plus de valeurs codables sur 16 bits qu'avec 8 bits, par exemple. Plus le nombre de bits est important, plus on pourra coder de valeurs entières différentes. Mais combien de plus ? Par exemple, si je passe de 8 bits à 16 bits, est-ce que le nombre de valeurs que l'on peut coder double, quadruple, pentuple ? De même, combien de valeurs différentes on peut coder avec <math>n</math> bits. Par exemple, combien de nombres différents peut-on coder avec 4, 8 ou 16 bits ? La section précédente vous l'expliquer.
Avec <math>n</math> bits, on peut coder <math>2^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>2^n - 1</math>. N'oubliez pas cette formule : elle sera assez utile dans la suite de ce tutoriel. Pour exemple, on peut coder 16 valeurs avec 4 bits, qui vont de 0 à 15. De même, on peut coder 256 valeurs avec un octet, qui vont de 0 à 255. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de bits
! Nombre de valeurs codables
|-
! 4
| 16
|-
! 8
| 256
|-
! 16
| 65 536
|-
! 32
| 4 294 967 296
|-
! 64
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien de bits il faut pour coder une valeur quelconque, que nous noterons N. Pour cela, il faut utiliser la formule précédente, mais à l'envers. On cherche alors <math>n</math> tel que <math>N = 2^n</math>. L'opération qui donne <math>n</math> est appelée le logarithme, et plus précisément un logarithme en base 2, noté <math>\log_2{(X)}</math>. Le problème est que le résultat du logarithme ne tombe juste que si le nombre X est une puissance de 2. Si ce n'est pas le cas, le résultat est un nombre à virgule, ce qui n'a pas de sens pratique. Par exemple, la formule nous dit que pour coder le nombre 13, on a besoin de 3,70043971814 bits, ce qui est impossible. Pour que le résultat ait un sens, il faut arrondir à l'entier supérieur. Pour l'exemple précédent, les 3,70043971814 bits s'arrondissent en 4 bits.
====Le lien entre nombre de chiffres hexadécimaux et valeurs codables====
Maintenant, voyons combien de valeurs peut-on coder avec <math>n</math> chiffres hexadécimaux. La réponse n'est pas très différente de celle obtenue en binaire, si ce n'est qu'il faut remplacer le 2 par un 16 dans la formule précédente. Avec <math>n</math> chiffres hexadécimaux, on peut coder <math>16^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>16^n - 1</math>. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de chiffres héxadécimaux
! Nombre de valeurs codables
|-
! 1 (4 bits)
| 16
|-
! 2 (8 bits)
| 256
|-
! 4 (16 bits)
| 65 536
|-
! 8 (32 bits)
| 4 294 967 296
|-
! 16 (64 bits)
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien faut-il de chiffres hexadécimaux pour coder une valeur quelconque en hexadécimal. La formule est là encore la même qu'en binaire, sauf qu'on remplace le 2 par un 16. Pour trouver le nombre de chiffres hexadécimaux pour encoder un nombre X, il faut calculer <math>\log_{16}{(X)}</math>. Notons que le logarithme utilisé est un logarithme en base 16, et non un logarithme en base 2, comme pour le binaire. Là encore, le résultat ne tombe juste que si le nombre X est une puissance de 16 et il faut arrondir à l'entier supérieur si ce n'est pas le cas.
Une propriété mathématique des logarithmes nous dit que l'on peut passer d'un logarithme en base X et à un logarithme en base Y avec une simple division, en utilisant la formule suivante :
: <math>\log_X{(N)} = {\log_{Y}{(N)} \over \log_Y(X)}</math>
Dans le cas qui nous intéresse, on a Y = 2 et X = 16, ce qui donne :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over \log_2(16)}</math>
Or, <math>\log_2(16)</math> est tout simplement égal à 4, car il faut 4 bits pour coder la valeur 16. On a donc :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over 4}</math>
En clair, il faut quatre fois moins de chiffres hexadécimaux que de bits, ce qui est assez intuitif vu qu'il faut 4 bits pour coder un chiffre hexadécimal.
===Les débordements d'entier===
On vient de voir que tout ordinateur manipule des nombres dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Et cela limite les valeurs qu'il peut encoder, qui sont comprises dans un intervalle bien précis. Mais que ce passe-t-il si jamais le résultat d'un calcul ne rentre pas dans cet intervalle ? Par exemple, pour du binaire normal, que faire si le résultat d'un calcul atteint ou dépasse <math>2^n</math> ? Dans ce cas, le résultat ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
On peut imaginer d'autres codages pour lesquels les entiers ne commencent pas à zéro ou ne terminent pas à <math>2^n - 1</math>. On peut prendre le cas où l'ordinateur gère les nombres négatifs, par exemple. Dans le cas général, l'ordinateur peut coder les valeurs comprises dans un intervalle, qui va de la valeur la plus basse <math>N_\text{min}</math> à la valeur la plus grande <math>N_\text{max}</math>. Et encore une fois, si un résultat de calcul sort de cet intervalle, on fait face à un débordement d'entier.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math>, avec <math>N_\text{max}</math> la plus grande valeur codable par l'ordinateur.
On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur basse de débordement vaut <math>N_\text{min} - 1</math>, avec <math>N_\text{min}</math> la plus petite valeur codable par l'ordinateur.
===La gestion des débordements d'entiers===
Face à un débordement d'entier, l'ordinateur peut utiliser deux méthodes : l'arithmétique saturée ou l'arithmétique modulaire.
L''''arithmétique saturée''' consiste à arrondir le résultat pour prendre la plus grande ou la plus petite valeur. Si le résultat d'un calcul dépasse la valeur haute de débordement, le résultat est remplacé par le plus grand entier supporté par l'ordinateur. La même chose est possible quand le résultat est inférieur à la plus petite valeur possible, par exemple lors d'une soustraction : l'ordinateur arrondit au plus petit entier possible.
Pour donner un exemple, voici ce que cela donne avec des entiers codés sur 4 bits, qui codent des nombres de 0 à 15. Si je fais le calcul 8 + 9, le résultat normal vaut 17, ce qui ne rentre pas dans l'intervalle. Le résultat est alors arrondi à 15. Inversement, si je fais le calcul 8 - 9, le résultat sera de -1, ce qui ne rentre pas dans l'intervalle : le résultat est alors arrondi à 0.
[[File:Odometer rollover.jpg|vignette|Exemple de débordement d'entier sur un compteur mécanique quelconque. L'image montre clairement que le compteur revient à zéro une fois la valeur maximale dépassée.]]
L''''arithmétique modulaire''' est plus compliquée et c'est elle qui va nous intéresser dans ce qui suit. Pour simplifier, imaginons que l'on décompte à partir de zéro. Quand on arrive à la valeur haute de débordement, on recommence à compter à partir de zéro. L'arithmétique modulaire n'est pas si contre-intuitive et vous l'utilisez sans doute au quotidien. Après tout, c'est comme cela que l'on compte les heures, les minutes et les secondes. Quand on compte les minutes, on revient à 0 au bout de 60 minutes. Pareil pour les heures : on revient à zéro quand on arrive à 24 heures. Divers compteurs mécaniques fonctionnent sur le même principe et reviennent à zéro quand ils dépassent la plus grande valeur possible, l'image ci-contre en montrant un exemple.
Mathématiquement, l'arithmétique modulaire implique des divisions euclidiennes, celles qui donnent un quotient et un reste. Lors d'un débordement, le résultat s'obtient comme suit : on divise le nombre qui déborde par la valeur haute de débordement, et que l'on conserve le reste de la division. Au passage, l'opération qui consiste à faire une division et à garder le reste au lieu du quotient est appelée le ''modulo''. Prenons l'exemple où l'ordinateur peut coder tous les nombres entre 0 et 1023, soit une valeur haute de débordement de 1024. Pour coder le nombre 4563, on fait le calcul 4563 / 1024. On obtient : <math>4563 = 4 \times 1024 + 467</math>. Le reste de la division est de 467 et c'est lui qui sera utilisé pour coder la valeur de départ, 4563. Le nombre 4563 est donc codé par la valeur 467 dans un tel ordinateur en arithmétique modulaire. Au passage, la valeur haute de débordement est toujours codée par un zéro dans ce genre d'arithmétique.
Les ordinateurs utilisent le plus souvent une valeur haute de débordement de <math>2^n</math>, avec n le nombre de bits utilisé pour coder les nombres entiers positifs. En faisant cela, l'opération modulo devient très simple et revient à éliminer les bits de poids forts au-delà du énième bit. Par exemple, reprenons l'exemple d'un ordinateur qui code ses nombres sur 4 bits. Imaginons qu'il fasse le calcul <math>13 + 3</math>, soit 1101 + 0011 = 1 0000 en binaire. Le résultat entraîne un débordement d'entier et l'ordinateur ne conserve que les 4 bits de poids faible. Cela donne : 1101 + 0011 = 0000. Comme autre exemple l'addition 1111 + 0010 ne donnera pas 17 (1 0001), mais 1 (0001).
L'avantage est que les calculs sont beaucoup plus simples avec cette méthode qu'avec les autres. L'ordinateur a juste à ne pas calculer les bits de poids fort. Pas besoin de faire une division pour calculer un modulo, pas besoin de corriger le résultat pour faire de l'arithmétique saturée.
===Aparté : quelques valeurs particulières en binaire===
Plus haut, on a dit qu'avec n bits, on peut encoder toutes les valeurs allant de <math>0</math> à <math>2^n - 1</math>. Ce simple fait permet de déterminer quelle est la valeur de certains entiers à vue d’œil. Dans ce qui va suivre, nous allons poser quelques bases que nous réutiliserons dans la suite du chapitre. Il s'agit de quelques trivias qui sont cependant assez utiles.
Le premier trivia concerne la valeur maximale <math>2^n - 1</math> : elle est encodée par un nombre dont tous les bits sont à 1.
Le deuxième trivia concerne les nombres de la forme 000...000 1111 1111, à savoir des nombres dont les x bits de poids faible sont à 1 et tous les autres valent 0. Par définition, de tels nombres codent la plus grande valeur possible sur x bits, ce qui fait qu'ils valent <math>2^x - 1</math>.
Le troisième trivia concerne les nombres de la forme 11111...0000. En clair, des nombres où on a une suite consécutive de 1 dans les bits de poids fort et les x bits de poids faibles à 0. De tels nombres valent : <math>2^n - 2^x</math>. La preuve est assez simple : ils s'obtiennent en prenant la valeur maximale <math>2^n - 1</math>, et en soustrayant un nombre de la forme <math>2^x - 1</math>.
[[File:Valeurs particulières en binaire.png|centre|vignette|upright=2|Valeurs particulières en binaire]]
Si on utilise l'arithmétique modulaire, la valeur <math>2^n</math> n'est autre que la valeur de débordement haute pour les nombres stockés sur n bits, et se code comme le zéro (il faudrait 1 bit de plus pour stocker le 1 de poids fort de <math>2^n</math>). Les 1er et 3ème nombres évoqués dans les paragraphes précédents peuvent donc se passer de <math>2^n</math> dans leur expression :
* <math>- 1</math> est codé 1111111111111111...111 (n bits à 1)
* <math>- 2^x</math> est codé 11111...111000..000000 (x bits à 0 précédés de n-x bits à 1)
==Les nombres entiers négatifs==
<noinclude>[[File:Binary Representations.svg|vignette|Représentations signées, exemples sur 4 bits.]]</noinclude>
Passons maintenant aux entiers négatifs en binaire : comment représenter le signe moins ("-") avec des 0 et des 1 ? Eh bien, il existe plusieurs méthodes :
* la représentation en signe-valeur absolue ;
* la représentation en complément à un ;
* la représentation en complément à deux;
* la représentation par excès ;
* la représentation dans une base négative ;
* d'autres représentations encore moins utilisées que les autres.
===La représentation en signe-valeur absolue===
La solution la plus simple pour représenter un entier négatif consiste à coder sa valeur absolue en binaire, et rajouter un bit qui précise si c'est un entier positif ou un entier négatif. Par convention, ce bit de signe vaut 0 si le nombre est positif et 1 s'il est négatif. On parle alors de représentation en signe-valeur absolue, aussi appelée '''représentation en signe-magnitude'''.
Avec cette technique, il y autant de nombres positifs que négatifs. Mieux : pour chaque nombre représentable en représentation signe-valeur absolue, son inverse l'est aussi. Ce qui fait qu'avec cette méthode, le zéro est codé deux fois : on a un -0, et un +0. Cela pose des problèmes lorsqu'on demande à notre ordinateur d'effectuer des calculs ou des comparaisons avec zéro.
[[File:Codage sur 4 bits en signe-valeur absolue.png|centre|vignette|upright=2|Codage sur 4 bits en signe-valeur absolue]]
===La représentation en complément à un===
La représentation en '''complément à un''' peut être vue, en première approximation, comme une variante de la représentation en signe-magnitude. Et je dis bien : "en première approximation", car il y a beaucoup à dire sur la représentation en complément à un, mais nous verrons cela dans la section suivante sur le complément à deux. Conceptuellement, la représentation en signe-magnitude et celle du complément à un sont drastiquement différentes et sont basées sur des concepts mathématiques totalement différents. Mais elles ont des ressemblances de surface, qui font que faire la comparaison entre les deux est assez utile pédagogiquement parlant.
En complément à un, les nombres sont codés en utilisant un bit de signe qui indique si le nombre de positif ou négatif, couplé à une valeur absolue. Par contre, la valeur absolue est codée différemment en binaire. Pour les nombres positifs, la valeur absolue est codée en binaire normal, pas de changement comparé aux autres représentations. Mais pour les valeurs négatives, la valeur absolue est codée en binaire normal, puis tous les bits sont inversés : les 0 deviennent des 1 et réciproquement. En clair, on utilise une opération de complémentation pour les nombres négatifs.
Prenons un exemple avec un nombre codé sur 4 bits, avec un cinquième bit de signe. La valeur 5 est codée comme suit : 0 pour le bit de signe, 5 donne 0101 en binaire, le résultat est 0 0101. Pour la valeur -5, le bit de signe est 1, 5 donne 0101 en binaire, on inverse les bits ce qui donne 1010 : cela donne 11010. Notez qu'on peut passer d'un résultat à l'autre avec une opération de complémentation, à savoir en inversant les bits de l'autre et réciproquement.
Il s'agit là d'une propriété générale avec le complément à 1 : l'opposé d'un nombre, à savoir passer de sa valeur positive à sa valeur négative ou inversement, se calcule avec une opération NOT, une opération de complémentation (d'où son nom). L'avantage est que les ordinateurs gèrent naturellement d'opération de complémentation. Par contre, en signe-magnitude, inverser le bit de signe est une opération spécifique et ne sert qu'à ça. Il faut donc rajouter une opération en plus pour calculer l'opposé d'un nombre.
La représentation en complément à un garde les défauts de la représentation en signe-magnitude. Le zéro est codé deux fois, avec un zéro positif et un zéro négatif. La différence est que les valeurs négatives sont dans l'ordre inverse, il y a une symétrie un peu meilleure. Les deux zéros sont d'ailleurs totalement éloignés, ils correspondent aux valeurs extrêmes encodables : le zéro positif a tous ses bits à 0, le zéro négatif a tous ses bits à 1. Le résultat est que l'implémentation des comparaisons et de certains calculs est plus complexe qu'avec le signe-magnitude, mais de peu.
[[File:Codage sur 4 bits en complément à 1.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 1.]]
===La représentation par excès===
La représentation par excès consiste à ajouter un biais aux nombres à encoder afin de les encoder par un entier positif. Pour encoder tous les nombres compris entre -X et Y en représentation par excès, il suffit de prendre la valeur du nombre à encoder, et de lui ajouter un biais égal à X. Ainsi, la valeur -X sera encodée par zéro, et toutes les autres valeurs le seront par un entier positif, le zéro sera encodé par X, 1, par X+1, etc. Par exemple, prenons des nombres compris entre -127 et 128. On va devoir ajouter un biais égal à 127, ce qui donne :
{|class="wikitable"
|-
!Valeur avant encodage!!Valeur après encodage
|-
||-127||0
|-
||-126||1
|-
||-125||2
|-
||…||…
|-
||0||127
|-
||…||…
|-
||127||254
|-
||128||255
|}
===La représentation en complément à deux===
La représentation en '''complément à deux''' est basée sur les mêmes mathématiques que le complément à un, mais fonctionne très différemment en pratique.
Si on regarde de loin, son principe est assez différent : il n'y a pas de bit de signe ni quoi que ce soit d'autre. À la place, un nombre en complément à deux est encodé comme en binaire normal, à un point près : le bit de poids fort est soustrait, et non additionné aux autres. Il a une valeur négative : on soustrait la puissance de deux associée s'il vaut 1, on ne la tient pas en compte s'il vaut 0.
Par exemple, la valeur du nombre noté 111001 en complément à deux s'obtient comme suit :
{|class="wikitable" style="margin:auto;"
|-
! -32 !! 16 !! 8 !! 4 !! 2 !! 1
|-
| 1 || 1 || 1 || 0 || 0 || 1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(8×1)+(4×0)+(2×1)+(1×1) = −32+16+8+1 = -7.
: ''Avec le complément à deux, comme avec le complément à un, le bit de poids fort vaut 0 pour les nombres positifs et 1 pour les négatifs.''
L'avantage de cette représentation est qu'elle n'a pas de double zéro : le zéro n'est encodé que par une seule valeur. Par contre, la valeur autrefois prise par le zéro négatif est réutilisée pour encoder une valeur négative. La conséquence est que l'on a un nombre négatif en plus d'encodé, il n'y a plus le même nombre de valeurs strictement positives et de valeurs négatives encodées. Le nombre négatif en question est appelé le '''nombre le plus négatif''', ce nom trahit le fait que c'est celui qui a la plus petite valeur (la plus grande valeur absolue). Il est impossible de coder l'entier positif associé, sa valeur absolue.
[[File:Codage sur 4 bits en complément à 2.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 2.]]
====Le calcul du complément à deux : première méthode====
Les représentations en complément à un et en complément à deux sont basées sur le même principe mathématique. Leur idée est de remplacer chaque nombre négatif par un nombre positif équivalent, appelé le ''complément''. Par équivalent, on veut dire que tout calcul donne le même résultat si on remplace un nombre négatif par son complément (idem avec un nombre positif, son complément aura un signe inverse). Et pour faire cela, elles se basent sur les débordements d'entier pour fonctionner, et plus précisément sur l'arithmétique modulaire abordée plus haut.
Si on fait les calculs avec le complément, les résultats du calcul entraînent un débordement d'entier, qui sera résolu par un '''modulo''' : l'ordinateur ne conserve que les bits de poids faible du résultat, les autres bits sont oubliés. Par exemple, prenons l'addition 15 + 2, 1111 + 0010 en binaire : le résultat ne sera pas 17 (10001), vu qu'on n'a pas assez de bits pour encoder le résultat, mais 1 (0001). Et le résultat après modulo sera identique au résultat qu'on aurait obtenu avec le nombre négatif sans modulo. En clair, c'est la gestion des débordements qui permet de corriger le résultat de manière à ce que l'opération avec le complément donne le même résultat qu'avec le nombre négatif voulu. Ainsi, on peut coder un nombre négatif en utilisant son complément positif.
: Cela ressemble beaucoup à la méthode de soustraction basée sur un complément à 9 pour ceux qui connaissent, sauf que c'est une version binaire qui nous intéresse ici.
Prenons un exemple, qui permettra d'introduire la suite. Encore une fois, on utilise un codage sur 4 bits dont la valeur haute de débordement est de 16. Prenons l'addition de 13 + 3 = 16. Avec l'arithmétique modulaire, 16 est équivalent à 0, ce qui donne : 13 + 3 = 0 ! On peut aussi reformuler en disant que 13 = -3, ou encore que 3 = -13. Dit autrement, 3 est le complément de -13 pour ce codage. Et ne croyez pas que ça marche uniquement dans cet exemple : cela se généralise assez rapidement à tout nombre négatif.
Prenons un nombre N dont un veut calculer le complément à deux K. Dans le cas général, on a :
: <math>N + K = 2^n</math>, vu que <math>2^n = 0</math> en utilisant le modulo.
En réorganisant très légèrement les termes pour isoler K, on a :
: <math>K = 2^n - N</math>
La formule précédent permet de calculer la complément à deux assez simplement, en faisant le calcul à la main.
Avant de poursuivre, prenons un exemple très intéressant : le cas où N = -1. Son complément à deux vaut donc :
: <math>K = 2^n - 1</math>
Le terme <math>2^n - 1</math> devrait vous rappeler quelque chose : il s'agit d'un nombre dont tous les bits sont à 1. En clair, le complément à deux de -1 est un nombre de la forme 1111...111. Et cela vaut quel que soit le nombre de bits n. La représentation de -1 est similaire, peu importe que l'on utilise des nombres de 16 bits, 32 bits, 64 bits, etc.
Voyons maintenant le cas des puissance de deux, par exemple 2, 4, 8, 16, etc. Leur complément à deux vaut :
: <math>K = 2^n - 2^x</math>
Nous avions vu précédemment dans le chapitre que ces nombres sont de la forme 11111...0000. En clair, des nombres dont les x bits de poids faible sont à 0, et tous les autres bits sont à 1. Par exemple, le complément à deux de -2 est un nombre dont les bits sont à 1, sauf le bit de poids faible. De même, le complément à deux de -4 a tous ses bits à 1, sauf les deux bits de poids faible. Et le complément à deux de -8 a tous ses bits à 1, sauf les trois bits de poids faible. Pour résumer, tout nombre de la forme <math>- 2^x</math> a tous ses bits à 1, sauf les x bits de poids faible qui sont à 0.
{|class="wikitable"
|+ Exemple avec des nombres de 8 bits
|-
! -1
| 1111 1111
|-
! -2
| 1111 1110
|-
! -4
| 1111 1100
|-
! -8
| 1111 1000
|-
! -16
| 1111 0000
|-
! -32
| 1110 0000
|-
! -64
| 1100 0000
|-
! -128
| 1000 0000
|-
! 0 / - 256
| 0000 0000
|}
====Le calcul du complément à deux : seconde méthode====
Il existe une seconde méthode pour calculer le complément à deux d'un nombre, la voici. Pour les nombres positifs, encodez-les comme en binaire normale. Pour les nombres négatifs, faites pareil, puis inversez tous les bits, avant d'ajouter un. La procédure est identique à celle du complément à un, sauf que l'on incrémente le résultat final. Et ce n'est pas une coïncidence, comme nous allons le voir immédiatement.
Pour comprendre pourquoi la méthode marche, repartons de la formule précédente <math>K = 2^n - N</math>. Elle peut se reformuler comme suit :
: <math>K = (2^n - 1 + 1) - N</math>
: <math>K = (2^n - 1) - N + 1</math>
La valeur <math>(2^n - 1)</math> est par définition un nombre dont tous les bits sont à 1. À cette valeur, on soustrait un nombre <math>|N|</math> dont certains bits sont à 1 et d'autres à 0. En clair, pour chaque colonne, on a deux possibilités : soit on doit faire la soustraction <math>1-1</math>, soit la soustraction <math>1-0</math>. Or, les règles de l'arithmétique binaire disent que <math>1-1=0</math> et <math>1-0=1</math>. En regardant attentivement, on se rend compte que le bit du résultat est l'inverse du bit de départ. De plus, les deux cas ne donnent pas de retenue : le calcul pour chaque bit n'influence pas les bits voisins.
Le terme <math>(2^n - 1) - N</math> est donc le complément à un du nombre N, un nombre égal à sa représentation en binaire dont tous les bits sont inversés. Notons <math>\overline{|N|}</math> le nombre formé en inversant tous les bits de N. On a alors :
: <math>K = \overline{N} + 1</math>
En clair, le complément à deux s'obtient en prenant le complément à un et en ajoutant 1. Dit autrement, il faut prendre le nombre N, en inverser tous les bits et ajouter 1.
Une autre manière équivalente consiste à faire le calcul suivant :
: <math>K = \overline{N - 1}</math>
On prend le nombre dont on veut le complément, on soustrait 1 et on inverse les bits.
: Notons que tout ce qui a été dit plus haut marche aussi pour le complément à un, avec cependant une petite différence : la valeur haute de débordement n'est pas la même, ce qui change les calculs. Pour des nombres codés sur <math>n</math> bits, la valeur haute de débordement est égale à <math>2^n</math> en complément à deux, alors qu'elle est de <math>2^n - 1</math> en complément à un. De ce fait, la gestion des débordements est plus simple en complément à deux.
====L'extension de signe====
Dans les ordinateurs, tous les nombres sont codés sur un nombre fixé et constant de bits. Ainsi, les circuits d'un ordinateur ne peuvent manipuler que des nombres de 4, 8, 12, 16, 32, 48, 64 bits, suivant la machine. Si l'on veut utiliser un entier codé sur 16 bits et que l'ordinateur ne peut manipuler que des nombres de 32 bits, il faut bien trouver un moyen de convertir le nombre de 16 bits en un nombre de 32 bits, sans changer sa valeur et en conservant son signe. Cette conversion d'un entier en un entier plus grand, qui conserve valeur et signe s'appelle l''''extension de signe'''.
L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
L'explication plus simple tient dans la manière de coder le bit de poids fort. Prenons l'exemple de la conversion d'un entier de 5 bits en un entier de 8bits. Les 4 bits de poids faible ont un poids positif (on les additionne), alors que le bit de poids fort a un poids négatif. Le nombre encodé vaut : <math>\text{Bit de poids fort} \times -16 + \text{Valeur encodée sur les 4 bits de poids faible}</math>. La valeur encodée sur 4 bits reste la même après extension de signe, car les poids des bits de poids faible ne changent pas. Par contre, le bit de poids fort change. Sur 8 bits, la valeur -16 est encodée par un nombre de la forme 1111 0000. En remplaçant le bit de poids fort par sa valeur calculée sur plus de bits, on remarque que les bits de poids fort ont été remplacés par des 1.
===La représentation négabinaire===
Enfin, il existe une dernière méthode, assez simple à comprendre, appelée '''représentation négabinaire'''. Dans cette méthode, les nombres sont codés non en base 2, mais en base -2. Oui, vous avez bien lu : la base est un nombre négatif. Dans les faits, la base -2 est similaire à la base 2 : il y a toujours deux chiffres (0 et 1), et la position dans un chiffre indique toujours par quelle puissance de 2 il faut multiplier, sauf qu'il faudra ajouter un signe moins une fois sur 2. Concrètement, les puissances de -2 sont les suivantes : 1, -2, 4, -8, 16, -32, 64, etc. En effet, un nombre négatif multiplié par un nombre négatif donne un nombre positif, ce qui fait qu'une puissance sur deux est négative, alors que les autres sont positives. Ainsi, on peut représenter des nombres négatifs, mais aussi des nombres positifs dans une puissance négative.
Par exemple, la valeur du nombre noté 11011 en base -2 s'obtient comme suit :
{|class="wikitable"
|-
!…
!-32
!16
!-8
!4
!-2
!1
|-
|…
|1
|1
|1
|0
|1
|1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(−8×1)+(4×0)+(−2×1)+(1×1)=−32+16−8−2+1=−25.
==Les nombres à virgule fixe==
On sait donc comment sont stockés nos nombres entiers dans un ordinateur. Néanmoins, les nombres entiers ne sont pas les seuls nombres que l'on utilise au quotidien : il nous arrive d'en utiliser à virgule. Notre ordinateur n'est pas en reste : il est lui aussi capable de manipuler de tels nombres. Dans les grandes lignes, il peut utiliser deux méthodes pour coder des nombres à virgule en binaire : La virgule fixe et la virgule flottante. Parlons d'abord de la virgule fixe, plus simple à comprendre.
===La virgule fixe : des entiers cachés===
La méthode de la virgule fixe consiste à émuler les nombres à virgule à partir de nombres entiers. Un nombre à virgule fixe est codé par un nombre entier proportionnel au nombre à virgule fixe. Pour obtenir la valeur de notre nombre à virgule fixe, il suffit de diviser l'entier servant à le représenter par le facteur de proportionnalité. Par exemple, pour coder 1,23 en virgule fixe, on peut choisir comme « facteur de conversion » 1000, ce qui donne l'entier 1230.
Généralement, les informaticiens utilisent une puissance de deux comme facteur de conversion, pour simplifier les calculs. En faisant cela, on peut écrire les nombres en binaire et les traduire en décimal facilement. Pour l'exemple, cela permet d'écrire des nombres à virgule en binaire comme ceci : 1011101,1011001. Et ces nombres peuvent se traduire en décimal avec la même méthode que des nombres entier, modulo une petite différence. Comme pour les chiffres situés à gauche de la virgule, chaque bit situé à droite de la virgule doit être multiplié par la puissance de deux adéquate. La différence, c'est que les chiffres situés à droite de la virgule sont multipliés par une puissance négative de deux, c'est à dire par <math>1 \over 2</math>, <math>1 \over 4</math>, <math>1 \over 8</math>, <math>1 \over 16</math>, <math>1 \over 32</math>, ...
[[File:Fixed-point binary representation.svg|centre|vignette|upright=2.5|Nombres à virgule fixe.]]
Cette méthode est assez peu utilisée de nos jours, quoiqu'elle puisse avoir quelques rares applications relativement connue. Un bon exemple est celui des banques : les sommes d'argent déposées sur les comptes ou transférées sont codés en virgule fixe. Les sommes manipulées par les ordinateurs ne sont pas exprimées en euros, mais en centimes d'euros. Et c'est une forme de codage en virgule fixe dont le facteur de conversion est égal à 100. La raison de ce choix est que les autres méthodes de codage des nombres à virgule peuvent donner des résultats imprécis : il se peut que les résultats doivent être tronqués ou arrondis, suivant les opérandes. Cela n'arrive jamais en virgule fixe, du moins quand on se limite aux additions et soustractions.
===Les nombres à virgule non-représentables en binaire===
Quel que soit la façon de représenter les nombres à virgules, il existe des nombres qui ne peuvent être représentés de manière exacte, à savoir avec un nombre fini de décimales après la virgule. En soi, ce n'est pas spécifique au binaire, on a la même chose en décimal. Par exemple, la fraction 1/3 en décimal s'écrit 0.3333333..., avec une infinité de 3. La même chose existe en binaire, mais pour des nombres différents.
Déjà, évacuons le cas des nombres irrationnels, à savoir les nombres qui ne peuvent pas s'écrire sous la forme d'une fraction, comme <math>pi</math> ou <math>e</math>. Ils ont une infinité de décimales que ce soit en binaire, en décimal, en hexadécimal, ou autre. Ils ne sont pas représentables avec un nombre fini de décimales quelle que soit la base utilisée. Concentrons-nous sur des nombres qui ne sont pas dans ce cas, et qui ont un nombre fini ou infini de décimales.
Le passage de la base 10 à la base 2 change le nombre de décimales, et peut faire passer d'un nombre fini de décimales à un nombre infini. Par exemple, <math>\frac{1}{5} = 0.2</math> est représenté en binaire avec une séquence infinie de bits : <math>0.001100110011...</math>. Les nombres étant en base binaire et représenté avec un nombre limité de bits, il existe certains nombres décimaux triviaux qui ne sont pas représentables avec un nombre fini de décimales.
Et la réciproque n'est pas vraie : tout nombre binaire avec un nombre fini de décimales en binaire est représentable avec un nombre fini de décimales en base 10. Ceci est lié à la décomposition en facteur premier des bases utilisées :
* <math>2 = 2^1</math>
* <math>10 = 2^1 \times 5^1</math>
Le nombre 10 possède tous les facteurs premiers de 2, mais 2 n'a pas de 5 dans sa décomposition.
==Les nombres flottants==
Les nombres à virgule fixe ont aujourd'hui été remplacés par les '''nombres à virgule flottante''', où le nombre de chiffres après la virgule est variable. Le codage d'un nombre flottant est basée sur son ''écriture scientifique''. Pour rappel, en décimal, l’écriture scientifique d'un nombre consiste à écrire celui-ci comme un produit entre un nombre <math>a</math> et une puissance de 10, avec <math>a</math> étant compris entre 1 et 9. Ce qui donne :
: <math>a \times 10^e</math>, avec <math>a \in [1, 10 [</math>
Le nombre <math>a</math> est appelé le ''significande'' et il est compris entre 1 (inclus) et 10 (exclu). Cette contrainte garantit que l'écriture scientifique d'un nombre est unique, qu'il n'y a qu'une seule façon d'écrire un nombre en notation scientifique. Pour cela, on impose le nombre de chiffre à gauche de la virgule et le plus simple est que celui-ci soit égal à 1. Mais il faut aussi que celui-ci ne soit pas nul. En effet, si on autorise de mettre un 0 à gauche de la virgule, il y a plusieurs manières équivalentes d'écrire un nombre. Ces deux contraintes font que le significande doit être égal ou plus grand que 1, mais strictement inférieur à 10. Par contre, on peut mettre autant de décimales que l'on veut.
En binaire, c'est la même chose, mais avec une puissance de deux. Cela implique de modifier la puissance utilisée : au lieu d'utiliser une puissance de 10, on utilise une puissance de 2.
: <math>a \times 2^e</math>, avec <math>a \in [1, 2 [</math>
Le significande est aussi altéré, au même titre que la puissance, même si les contraintes sont similaires à celles en base 10. En effet, le nombre <math>a</math> ne possède toujours qu'un seul chiffre à gauche de la virgule, comme en base 10. Vu que seuls deux chiffres sont possibles (0 et 1) en binaire, on s'attend à ce que le chiffre situé à gauche de la virgule soit un zéro ou un 1. Mais rappelons que le chiffre à gauche doit être non-nul, pour les mêmes raisons qu'en décimal. En clair, le significande a forcément un bit à 1 à gauche de la virgule. Pour récapituler, l'écriture scientifique binaire d'un nombre consiste à écrire celui-ci sous la forme :
: <math>1,m \times 2^e</math>, avec <math>m \in [0, 1 [</math>
La partie fractionnaire du nombre <math>a</math>, qu'on appelle la '''mantisse'''.
[[File:Mantissa.svg|centre|vignette|Écriture scientifique (anglais).]]
Pour déterminer l'écriture scientifique en binaire d'un nombre quelconque, la procédure dépend de la valeur du nombre en question. Tout dépend s'il est dans l'intervalle <math>[1, 2 [</math>, au-delà de 2 ou en-dessous de 1.
* Pour un nombre entre 1 (inclus) et 2 (exclu), il suffit de le traduire en binaire. Son exposant est 0.
* Pour un nombre au-delà de 2, il faut le diviser par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant est alors le nombre de fois qu'il a fallu diviser par 2.
* Pour un nombre plus petit que 1, il faut le multiplier par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant se calcule en prenant le nombre de fois qu'il a fallu multiplier par 2, et en prenant l'opposé (en mettant un signe - devant le résultat).
===Le codage des nombres flottants et la norme IEEE 754===
La '''virgule flottante''' encode l'écriture scientifique d'un nombre/ Elle utilise pour cela trois données : la mantisse, l'exposant et un bit de signe. Les trois sont concaténés dans un seul "nombre", plus précisément une suite de bits qui est découpée en un bit de signe, un entier qui encode l'exposant, et un entier pour la mantisse.
[[File:General floating point frac.svg|centre|vignette|upright=2|Format Général d'un nombre flottant.]]
[[File:General floating point.svg|centre|vignette|upright=2|Format Général d'un nombre flottant, plus détaillé.]]
Voici à peu-près comment les nombres flottants sont répartis sur une ligne numérique. Vous remarquerez que les flottants sont de plus en plus éloignés au fur et à mesure qu'on s'éloigne de zéro. La raison est l'encodage avec un exposant et une mantisse. L'écart entre deux flottants reste stable pour un même exposant, mais il y a une cassure quand on passe d'un exposant au suivant.
[[File:FloatingPointPrecisionAugmented.png|centre|vignette|upright=3|Floating Point Precision.]]
Le standard actuel pour la virgule flottante est la '''norme IEEE 754'''. Cette norme va (entre autres) définir quatre types de flottants différents, qui utilisent des exposants/mantisses de taille différente. Les deux formats les plus courants sont les flottants '''simple précision''' et '''double précision'''. Les premiers sont codés sur 32 bits, les seconds sur 64 bits. Il y a aussi des formats de '''précision étendus''', codés sur 80 bits ou plus.
[[File:Single double extended2.gif|centre|vignette|upright=2.5|Formats des nombres flottants IEEE754.]]
===L'encodage de la mantisse et de l'exposant===
L'exposant peut être aussi bien positif que négatif (pour permettre de coder des nombres très petits). Pour cela, il est encodé en représentation par excès. Pour un exposant de n bits, le biais est égal à <math>2^{n-1} - 1</math>.
L'encodage de la mantisse a lui aussi ses subtilités. En effet, la mantisse est un nombre à virgule, avec une partie entière et une partie fractionnaire. Le tout est encodé en virgule fixe, reste à choisir son encodage exact. Pour simplifier l'encodage, l'exposant est choisir de manière à ce qu'il y ait un seul chiffre avant la virgule, ce qui permet de coder la partie entière sur 1 bit. En clair, il faut encoder le bit à gauche de la virgule, puis ceux à droite. Les anciens PC utilisaient cette solution, du temps de la FPU x87. Ils géraient nativement des nombres flottants de 80 bits, appelés '''flottants étendus x87'''. Ils utilisaient une mantisse codée sur 64 bits, dont un bit pour la partie entière, et 63 pour la partie fractionnaire.
[[File:X86 Extended Floating Point Format.svg|centre|vignette|upright=2|Flottants étendus des anciens CPU X86.]]
Une simplification plus importante choisir l'exposant de manière à ce que la partie entière vaut 1, systématiquement. En faisant cela, on n'a plus à encoder la partie entière, pas besoin de lui réserver un bit. La mantisse encode donc uniquement la partie fractionnaire, la partie entière n'est pas encodée. Une telle méthode est donc plus efficace. A quelques détails près : de nombreuses valeurs ne peuvent pas être encodées facilement. Par exemple, le zéro ne peut pas être encodé avec cette technique. Et c'est là un des gros défaut de cette technique : ne pas pouvoir encoder le zéro est un énorme problème !
Pour éviter ce défaut, les nombres flottants IEE754 utilisent une variante de la technique précédente. Les flottants IEE754 ont une partie entière de la mantisse qui vaut 1, sauf pour quelques rares exceptions. Quelques nombres flottants spécialisés ne sont pas codés en respectant les règles pour le significande et ont un 0 à gauche de la virgule. Ils sont appelés les '''dénormaux''', aussi nommés flottants dénormalisés. Les dénormaux sont des nombres flottants où l'exposant est le plus petit possible. Le '''zéro''' est un dénormal particulier dont la mantisse est nulle. Au fait, remarquez que le zéro est codé deux fois à cause du bit de signe : on se retrouve avec un -0 et un +0.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur minimale (0 en binaire) || Mantisse différente de zéro (dénormal) ou égale à zéro (zéro)
|}
Toujours est-il que le bit à gauche de la virgule n'est pas codé, que ce soit pour les flottants normaux ou les fameux dénormaux qui font exception. On verra que ce bit peut se déduire en fonction de l'exposant utilisé pour encoder le nombre à virgule, ce qui lui vaut le nom de '''bit implicite'''.
===Les flottants spéciaux : infinis et NaN===
Fait étrange, la norme IEEE754 permet de représenter '''l'infini''', aussi bien en positif qu'en négatif. Celui-ci est codé en mettant l'exposant à sa valeur maximale et la mantisse à zéro. Et le pire, c'est qu'on peut effectuer des calculs sur ces flottants infinis. Mais cela a peu d'utilité.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse égale à zéro
|}
Mais malheureusement, l'invention des flottants infinis n'a pas réglé tous les problèmes. Par exemple, quel est le résultat de <math>\infty - \infty</math> ? Ou encore <math>0 - \infty</math> ? Autant prévenir tout de suite : mathématiquement, on ne peut pas savoir quel est le résultat de ces opérations. Pour pouvoir résoudre ces calculs, il a fallu inventer un nombre flottant qui signifie « je ne sais pas quel est le résultat de ton calcul pourri ». Ce nombre, c'est '''NaN'''. NaN est l'abréviation de ''Not A Number'', ce qui signifie : n'est pas un nombre. Ce NaN a un exposant dont la valeur est maximale, mais une mantisse différente de zéro. Pour être plus précis, il existe différents types de NaN, qui diffèrent par la valeur de leur mantisse, ainsi que par les effets qu'ils peuvent avoir. Malgré son nom explicite, on peut faire des opérations avec NaN, mais cela ne sert pas vraiment à grand chose : une opération arithmétique appliquée avec un NaN aura un résultat toujours égal à NaN.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse différente de zéro
|}
===Les arrondis et exceptions===
La norme impose aussi une gestion des arrondis ou erreurs, qui arrivent lors de calculs particuliers. En voici la liste :
{|class="wikitable"
|-
!Nom de l’exception
!Description
|-
!Invalid operation
|Opération qui produit un NAN. Elle est levée dans le cas de calculs ayant un résultat qui est un nombre complexe, ou quand le calcul est une forme indéterminée. Pour ceux qui ne savent pas ce que sont les formes indéterminées, voici en exclusivité la liste des calculs qui retournent NaN : <math>\infty - \infty</math>, <math>0 \times \infty</math>, <math>0^0</math>, <math>1^{\infty}</math>, <math>\infty^0</math>.
|-
!Overflow
|Résultat trop grand pour être stocké dans un flottant. Le plus souvent, on traite l'erreur en arrondissant le résultat en vue de la taille de la mantisse;
|-
!Underflow
|Pareil que le précédent, mais avec un résultat trop petit. Le plus souvent, on traite l'erreur en arrondissant le résultat vers 0.
|-
!Division par zéro
|Le nom parle de lui-même. La réponse la plus courante est de répondre + ou - l'infini.
|-
!'''Inexact'''
|Le résultat ne peut être représenté par un flottant et on doit l'arrondir.
|}
La gestion des arrondis pose souvent problème. Pour donner un exemple, on va prendre le nombre 0,1. En binaire, ce nombre s'écrit comme ceci : 0,1100110011001100... et ainsi de suite jusqu'à l'infini. Notre nombre utilise une infinité de décimales. Bien évidemment, on ne peut pas utiliser une infinité de bits pour stocker notre nombre et on doit impérativement l'arrondir. Comme vous le voyez avec la dernière exception, le codage des nombres flottants peut parfois poser problème : dans un ordinateur, il se peut qu'une opération sur deux nombres flottants donne un résultat qui ne peut être codé par un flottant. On est alors obligé d'arrondir ou de tronquer le résultat de façon à le faire rentrer dans un flottant. Pour éviter que des ordinateurs différents utilisent des méthodes d'arrondis différentes, on a décidé de normaliser les calculs sur les nombres flottants et les méthodes d'arrondis. Pour cela, la norme impose le support de quatre modes d'arrondis :
* Arrondir vers + l'infini ;
* vers - l'infini ;
* vers zéro ;
* vers le nombre flottant le plus proche.
===Les nombres flottants logarithmiques===
Les '''nombres flottants logarithmiques''' sont une spécialisation des nombres flottants IEEE754, ou tout du moins une spécialisation des flottants écrits en écriture scientifique. Un nombre logarithmique est donc composé d'un bit de signe et d'un exposant, sans mantisse. La mantisse est totalement implicite : tous les flottants logarithmiques ont la même mantisse, qui vaut 1.
Pour résumer, il ne reste que l'exposant, qui est tout simplement le logarithme en base 2 du nombre encodé, d'où le nom de codage flottant logarithmique donné à cette méthode. Attention toutefois : l'exposant est ici un nombre fractionnaire, codé en virgule fixe. Le choix d'un exposant fractionnaire permet de représenter pas mal de nombres de taille diverses.
{|class="wikitable"
|-
!
! Bit de signe
! Exposant
|-
! Représentation binaire
| 0
| 01110010101111
|-
! Représentation décimale
| +
| 1040,13245464
|}
L'utilité de cette représentation est de simplifier certains calculs, comme les multiplications, divisions, puissances, etc. En effet, les mathématiques nous disent que le logarithme d'un produit est égal à la somme des logarithmes : <math>\ln{(a \times b)} = \ln{a} + \ln{b}</math>. Or, il se trouve que les ordinateurs sont plus rapides pour faire des additions/soustractions que pour faire des multiplications/divisions. Donc, la représentation logarithmique permet de remplacer les multiplications/divisions par des additions/soustractions, plus simples et plus rapides pour l'ordinateur.
Évidemment, les applications des flottants logarithmiques sont rares, limitées à quelques situations bien précises (traitement d'image, calcul scientifique spécifiques).
==Les encodages binaires alternatifs==
Outre le binaire que nous venons de voir, il existe d'autres manières de coder des nombres avec des bits. Et nous allons les aborder dans cette section. Parmi celle-ci, nous parlerons du code Gray, de la représentation ''one hot'', du unaire et du ternaire. Nous en parlons car elles seront utiles dans la suite du cours, bien que de manière assez limitée. Autant nous passerons notre temps à parler du binaire normal, autant les représentations que nous allons voir sont aujourd'hui utilisées dans des cas assez spécifiques. Et elles sont plus courantes que vous ne le pensez.
===Le code Gray===
Le '''code Gray''' est un encodage binaire qui a une particularité très intéressante : deux nombres consécutifs n'ont qu'un seul bit de différence. Pour exemple, voici ce que donne le codage des 8 premiers entiers sur 3 bits :
{|class="wikitable"
! Décimal
! Binaire naturel
! Codage Gray
|-
|0||000||000
|-
|1||001||001
|-
|2||010||011
|-
|3||011||010
|-
|4||100||110
|-
|5||101||111
|-
|6||110||101
|-
|7||111||100
|}
Les utilisations du code Gray sont assez nombreuses, bien qu'on n'en croise pas tous les jours. Un exemple : le code Gray est très utile dans certains circuits appelés les compteurs, qui mémorisent un nombre et l'incrémentent (+1) ou le décrémentent (-1) suivant les besoins de l'utilisateur. Il est aussi utilisé dans des scénarios difficiles à expliquer ici (des codes correcteurs d'erreur ou des histoires de passages de domaines d'horloge). Mais le point important est que ce code sera absolument nécessaire dans quelques chapitres, quand nous parlerons des tables de Karnaugh, un concept important pour la conception de circuits électroniques. Ne passez pas à côté de cette section.
Pour construire ce code Gray, on peut procéder en suivant plusieurs méthodes, les deux plus connues étant la méthode du miroir et la méthode de l'inversion.
[[File:Binary-reflected Gray code construction.svg|vignette|Construction d'un code gray par la méthode du miroir.]]
La méthode du miroir est relativement simple. Pour connaître le code Gray des nombres codés sur n bits, il faut :
* partir du code Gray sur n-1 bits ;
* symétriser verticalement les nombres déjà obtenus (comme une réflexion dans un miroir) ;
* rajouter un 0 au début des anciens nombres, et un 1 au début des nouveaux nombres.
Il suffit de connaître le code Gray sur 1 bit pour appliquer la méthode : 0 est codé par le bit 0 et 1 par le bit 1.
Une autre méthode pour construire la suite des nombres en code Gray sur n bits est la méthode de l'inversion. Celle-ci permet de connaître le codage du nombre n à partir du codage du nombre n-1, comme la méthode du dessus. On part du nombre 0, systématiquement codé avec uniquement des zéros. Par la suite, on décide quel est le bit à inverser pour obtenir le nombre suivant, avec la règle suivante :
* si le nombre de 1 est pair, il faut inverser le dernier chiffre.
* si le nombre de 1 est impair, il faut localiser le 1 le plus à droite et inverser le chiffre situé à sa gauche.
Pour vous entraîner, essayez par vous-même avec 2, 3, voire 5.
===Les représentations ''one-hot'' et unaire===
La représentation ''one-hot'' et la représentation unaire sont deux représentations assez liées, mais légèrement différentes.
La '''représentation unaire''' est la représentation en base 1. Avec elle, le zéro est codé en mettant tous les bits du nombre à zéro, la valeur 1 est encodée avec la valeur 000...0001, le deux avec 000...0011, le trois avec 000...0111, etc. Pour résumer, le nombre N est encodé en mettant les N bits de poids faible à 1, à l'exception du zéro qui est encodé...par un zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! Unaire
|-
| 0 || 000 || 0000 0000
|-
| 1 || 001 || 0000 0001
|-
| 2 || 010 || 0000 0011
|-
| 3 || 011 || 0000 0111
|-
| 4 || 100 || 0000 1111
|-
| 5 || 101 || 0001 1111
|-
| 6 || 110 || 0011 1111
|-
| 7 || 111 || 0111 1111
|-
| 8 || 1000 || 1111 1111
|}
Pour convertir un nombre codé en unaire vers le binaire, il suffit de compter le nombre de 1. C'est ni plus ni moins ce que fait l'opération dite de ''population count'' dont nous avons parlé plus haut. La représentation unaire permet d'encoder N+1 valeurs sur N bits, car le zéro est encodé à part. Une valeur pour le zéro, plus une par bit.
Avec la '''représentation ''one-hot''''', un nombre est codé sur N bits, dont un seul bit est à 1. La position du 1 dans le nombre indique sa valeur. Précisément, la valeur encodée est égale au poids du bit à 1. En faisant cela, le zéro est codé en mettant le bit de poids faible à 1, la valeur 1 est codée avec les bits 00...00010, la valeur 2 est codée par les bits 00...000100, etc.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Elle permet d'encoder N valeurs différentes avec N bits, zéro inclus. On perd une valeur par rapport à l'unaire, car le zéro n'et pas encodé à part du reste. La traduction du binaire vers le ''one-hot'' est réalisé par un circuit nommé décodeur, que nous verrons d'ici quelques chapitres. De même, la traduction d'un nombre encodé en ''one-hot'' vers le binaire est réalisée par un circuit électronique appelé l'encodeur, qui est en quelque sorte l'inverse de l'encodeur. Les deux circuits sont très utilisés en électronique, ce sont des circuits de base, que nous n'aurons de cesse d'utiliser pour fabriquer d'autres circuits.
L'utilité de ces deux représentations n'est pas évidente. Mais sachez qu'elle le deviendra quand nous parlerons des circuits appelés les "compteurs", tout comme ce sera le cas pour le code Gray. Elles sont très utilisées dans des circuits appelés des machines à état, qui doivent incorporer des circuits compteurs efficients. Et ces représentations permettent d'avoir des circuits pour compter qui sont très simples, efficaces, rapides et économes en circuits électroniques. La représentation unaire sera aussi utile à la toute fin du cours, dans les chapitres liés à l'exécution dans le désordre. Il en sera fait référence quand nous parlerons de fenêtres d'instruction, d'émission dans l'ordre, de ''scoreboarding'', etc.
Il faut aussi noter qu'il a existé des ordinateur qui manipulaient des nombres codés en unaire. Enfin presque, la représentation utilisée était proche de l'unaire, mais ressemblait fortement. De tels ordinateurs étaient appelés des ''ordinateurs stochastiques'' et nous les aborderons à la toute fin de ce wikilivre, dans un chapitre annexe portant sur les ordinateurs ternaires (qui comptent en base 3) et unaires.
==Les encodages hybrides entre décimal et binaire==
Pour terminer, nous allons voir les représentations qui mixent binaire et décimal. Il s'agit en réalité d'encodage qui permettent de manipuler des nombres codés en décimal, mais les chiffres sont codés en utilisant des bits. L'encodage ''Binary Coded Decimal'' est le plus connu de cette catégorie, mais il y en a quelques autres, qui sont moins connus. De telles représentations étaient très utilisées au début de l'informatique, mais sont aujourd'hui tombées en désuétude. Pour désigner ces encodages, nous parlerons d''''encodages bit-décimaux''' : décimaux pour préciser qu'ils encodent des nombres codés en décimal, bit pour préciser que les chiffres sont codés avec des bits.
De tels encodages étaient utilisés sur les tous premiers ordinateurs pour faciliter le travail des programmeurs, mais aussi sur les premières calculettes. Ils sont utiles dans les applications où on doit manipuler chaque chiffre décimal séparément des autres. Un exemple classique est celui d'une horloge digitale. Ils ont aussi l'avantage de bien se marier avec les nombres à virgule. Les représentations des nombres à virgule fixe ou flottante ont le défaut que des arrondis peuvent survenir. Par exemple, la valeur 0,2 est codée comme suit en binaire normal : 0.00110011001100... et ainsi de suite jusqu’à l'infini. Avec les encodages bit-décimaux, on n'a pas ce problème, 0,2 étant codé 0000 , 0010.
Il a existé des ordinateurs qui travaillaient uniquement avec de tels encodages, appelés des '''ordinateurs décimaux'''. Ils étaient assez courants entre les années 60 et 70, même s'ils ne représentent pas la majorité des architectures de l'époque. Avec eux, la mémoire n'était pas organisée en octets, mais elle stockait des chiffres décimaux codés sur 5-6 bits. Le processeur faisait des calculs sur des chiffres bit-décimaux directement.
Leur grand avantage est leur très bonne performance dans les taches de bureautique, de comptabilité, et autres. Les processeurs de l'époque recevaient des entiers codés en bit-décimal de la part des entrées-sorties, et devaient les traiter. Les processeurs binaires devaient faire des conversions décimal-binaire pour communiquer avec les entrées-sorties, mais pas les processeurs décimaux. Le gain en performance pouvait être substantiel dans certaines applications.
Les ordinateurs décimaux se classent en deux sous-types bien précis. Les premiers gèrent des entiers qui ont un nombre de chiffres fixes. Par exemple, l'IBM 7070 gérait des entiers de 10 chiffres, plus un signe +/- pour les entiers signés. Le processeur faisait les calculs directement en bit-décimal, il gérait des entiers faisant environ 10-15 chiffres décimaux et savait faire des calculs avec de tels nombres. Le second sous-type effectue les calculs chiffre par chiffre et géraient des nombres de taille variable, sans limite de chiffres ! Nous reparlerons de ces derniers dans un chapitre ultérieur, quand nous parlerons de la différence entre byte et mot. Pour le moment, ne gardez à l'esprit que les processeurs gérant un nombre fixe de chiffres décimaux, plus simples à comprendre.
===Le ''Binary Coded Decimal''===
Le '''''Binary Coded Decimal''''', abrévié BCD, est une représentation qui mixe binaire et décimal. Avec cette représentation, les nombres sont écrits en décimal, comme nous en avons l'habitude dans la vie courante, sauf que chaque chiffre décimal est directement traduit en binaire sur 4 bits. Prenons l'exemple du nombre 624 : le 6, le 2 et le 4 sont codés en binaire séparément, ce qui donne 0110 0010 0100.
{| class="wikitable"
|+ Codage BCD
|-
! Nombre encodé (décimal)
! scope="col" colspan="4" | BCD
|-
| 0 || 0 || 0 || 0 || 0
|-
| 1 || 0 || 0 || 0 || 1
|-
| 2 || 0 || 0 || 1 || 0
|-
| 3 || 0 || 0 || 1 || 1
|-
| 4 || 0 || 1 || 0 || 0
|-
| 5 || 0 || 1 || 0 || 1
|-
| 6 || 0 || 1 || 1 || 0
|-
| 7 || 0 || 1 || 1 || 1
|-
| 8 || 1 || 0 || 0 || 0
|-
| 9 || 1 || 0 || 0 || 1
|}
On peut remarquer que 4 bits permettent de coder 16 valeurs, là où il n'y a que 10 chiffres. Dans le BCD proprement dit, les combinaisons de bits qui correspondent à 10, 11, 12, 13, 14 ou 15 ne sont tout simplement pas prises en compte. Sur quelques ordinateurs, ces combinaisons codent des chiffres décimaux en double : certains chiffres pouvaient être codés de deux manières différentes. Il est aussi possible d'utiliser ces valeurs pour coder quelque chose d'autre que des chiffres. Par exemple, il est possible de les utiliser pour coder un signe + ou -, afin de gérer les entiers relatifs. Une autre possibilité, complémentaire de la précédente, utilise ces valeurs en trop pour coder une virgule, afin de gérer les nombres non-entiers. Les possibilités sont nombreuses.
Le support du BCD implique souvent que le processeur supporte des '''opérations BCD''', à savoir des opérations capables de travailler sur des opérandes en BCD et de donner un résultat en BCD. Il faut bien faire la différence entre les opérations en binaire et les opérations en BCD. Par exemple, on n'effectue pas une addition de la même manière en binaire et en décimal/BCD, même si les grandes lignes sont presque identiques. Les différences font que les processeurs doivent avoir des opérations différentes pour les deux encodages, de la même manière que les processeurs gèrent les opérations sur les flottants et les entiers séparément.
Le support du codage BCD est abandonné de nos jours, c'est surtout quelque chose qu'on trouve sur les anciens processeurs. Les architectures 8 et 16 bits supportaient à la fois des opérations binaires et des opérations BCD. Mais il a aussi existé une méthode intermédiaire, qui utilisait des additions binaires normales sur des opérandes BCD. L'idée est que le résultat de l'addition est incorrect, car les valeurs 10 à 15 peuvent apparaître comme chiffre dans le résultat. Mais on peut le corriger pour obtenir le résultat exact en BCD. Pour résumé, les processeurs faisaient des additions en binaire, et corrigeaient le résultat avec une opération spécifique pour obtenir un résultat en BCD. Nous en reparlerons dans le chapitre sur le langage machine et l'assembleur, dans lequel nous étudierons les différentes opérations que supporte un processeur.
===Les encodages compacts du BCD===
Des variantes du BCD visent à réduire le nombre de bits utilisés pour encoder un nombre décimal. Nous allons les appeler les '''encodages BCD compacts'''. Avec certaines variantes, on peut utiliser seulement 7 bits pour coder deux chiffres décimaux, au lieu de 8 bits en BCD normal. Idem pour les nombres à 3 chiffres décimaux, qui prennent 10 bits au lieu de 12. Cette économie est réalisée par une variante du BCD assez compliquée, appelée l''''encodage Chen-Ho'''. Une alternative, appelée le ''Densely Packed Decimal'', arrive à une compression identique, mais avec quelques avantages au niveau de l'encodage. Ces encodages sont cependant assez compliqués à expliquer, surtout à ce niveau du cours, aussi je me contente de simplement mentionner leur existence.
Les encodages BCD compacts sont utilisés par les programmeurs pour stocker des données dans un fichier, en mémoire, mais guère plus. Ils ne sont pas gérés par le processeur directement, on ne peut pas faire de calculs avec, le processeur ne gére pas d'opération BCD supportant de tels encodages. C'est théoriquement possible, mais ça n'a jamais été implémenté dans un processeur, le cout en circuit n'en valait pas la chandelle. Pour faire des calculs sur des nombres en BCD compact, on doit les décompresser et les convertir en BCD normal ou en binaire, puis faire les calculs avec des opérations BCD/binaire usuelles.
===L'encodage ''Excess-3''===
La représentation '''''Excess-3''''' (XS-3) est une variante du BCD, qui a autrefois était utilisée sur d'anciens ordinateurs décimaux. Il s'agit d'une sorte d'hybride entre une représentation par excès et BCD. Chaque chiffre décimal est codé sur plusieurs bits en utilisant une représentation binaire par excès. Le biais est de 3 pour la représentation XS-3, il en existe des variantes avec un excès de 4, 5, mais elles sont moins utilisées. Avec elle, la conversion d'un chiffre décimal se fait comme suit : on prend le chiffre décimal, on ajoute 3, puis on traduit en binaire.
{| class="wikitable"
|+ Codage en ''Excess-3''
|-
! Décimal
! scope="col" colspan="4" | Binaire
|-
| -3 || 0 || 0 || 0 || 0
|-
| -2 || 0 || 0 || 0 || 1
|-
| -1 || 0 || 0 || 1 || 0
|-
| 0 || 0 || 0 || 1 || 1
|-
| 1 || 0 || 1 || 0 || 0
|-
| 2 || 0 || 1 || 0 || 1
|-
| 3 || 0 || 1 || 1 || 0
|-
| 4 || 0 || 1 || 1 || 1
|-
| 5 || 1 || 0 || 0 || 0
|-
| 6 || 1 || 0 || 0 || 1
|-
| 7 || 1 || 0 || 1 || 0
|-
| 8 || 1 || 0 || 1 || 1
|-
| 9 || 1 || 1 || 0 || 0
|-
| 10 || 1 || 1 || 0 || 1
|-
| 11 || 1 || 1 || 1 || 0
|-
| 12 || 1 || 1 || 1 || 1
|}
L'avantage de cette représentation est que l'on peut facilement calculer les soustractions en utilisant une méthode bien précise (celle du complément à 10, je ne détaille pas plus). Le défaut est que le calcul des additions est légèrement plus complexe.
L'XS-3 a été utilisé sur quelques ordinateurs décimaux assez anciens, notamment sur l'UNIVAC I et II.
===Les codes bi-quinaires===
Les '''codes bi-quinaires''' utilisent un mélange entre encodage binaire, encodage ''one-hot'' et encodage décimal. L'idée de base derrière ces codes est d'utiliser la représentation ''one-hot'' pour coder un chiffre BCD. Le problème est que cela demande d'utiliser 10 bits pour encoder un chiffre BCD, ce qui est beaucoup. L'encodage bi-quinaire optimise le tout de manière à diviser par deux le nombre de bits requis pour l'encodage.
Pour simplifier, un chiffre encodé en bi-quinaire est composé de deux parties : une partie binaire de un bit, suivie par d'une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits pour encoder les valeurs 0, 1, 2, 3 et 4. La partie binaire indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9 : 0 à 4 si la partie binaire vaut 0, 5 à 9 si elle vaut 1. La valeur du chiffre se calcule comme suit :
* si la partie binaire vaut 0, elle est égale à la partie quinaire ;
* si la partie binaire vaut 1, on ajoute 5 à la partie quinaire.
L'originalité de ce codage est qu'il permet de facilement compter sur ses doigts : on a deux mains, cinq doigts. De nombreuses langues utilisent ce système pour coder des nombres, comme le Wolof. Et ce système est utilisé dans certains pays pour compter sur ses doigts, voire dans la vie de tous les jours.
[[File:Code Biquinaer.svg|vignette|Code bi-quinaire où partie binaire et quinaire sont encodées en représentation ''one-hot''.]]
L'encodage abordé au-dessus est une version simplifiée de l'encodage bi-quinaire, il en existe d'autres variantes. Le bi-quinaire vu juste au-dessus encode la partie binaire en binaire, et la partie quinaire en ''one-hot''. Mais il est aussi possible d'inverser l'encodage, à savoir encoder la partie binaire avec une représentation ''one-hot'', alors que la partie bi-quinaire est encodée en binaire normal.
Cependant, la version la plus intéressante utilise l'encodage ''one-hot'' pour les deux ! Elle est illustrée ci-contre. Avec cette dernière, un chiffre BCD est encodé sur 7 bits, dont 2 sont obligatoirement à 1 et les 5 restants sont à 0. Cette propriété permet de détecter qu'un bit a été inversé suite à une erreur de transmission ou un problème de stockage. Si un bit est inversé, le résultat aura 1 bit à 1 et 6 à 0, ou alors 3 bits à 1 et 4 à 0, ce qui ne colle pas. Le résultat après inversion ne donnera pas un chiffre bi-quinaire valide.
Une autre variante du bi-quinaire a été utilisée sur l'IBM 1401. Avec elle, la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre encodé par la partie quinaire. L'avantage de ce système est que la gestion des retenues lors des additions était grandement simplifiée.
Le bi-quinaire et ses variantes ont été utilisés sur d'anciens ordinateurs, dont l'IBM 650, l'UNIVAC Solid State et le UNIVAC LARC. La plupart utilisaient des variantes de l'encodage bi-quinaire proprement dit. Pour rentrer vraiment dans le détail, voici les encodages utilisés sur ces machines. La lecture est facultative, il est possible de passer directement à la section suivante :
{| class="wikitable"
|-
! !! IBM 650 !! Remington Rand 409 !! UNIVAC Solid State !! UNIVAC LARC
|-
| Chiffre || 1357-9 bits || 05-01234 || p-5-421 bits || p-5-qqq bits
|-
| 0 || 10-10000 || 0000-0 || 1-0-000 || 1-0-000
|-
| 1 || 10-01000 || 1000-0 || 0-0-001 || 0-0-001
|-
| 2 || 10-00100 || 1000-1 || 0-0-010 || 1-0-011
|-
| 3 || 10-00010 || 0100-0 || 1-0-011 || 0-0-111
|-
| 4 || 10-00001 || 0100-1 || 0-0-100 || 1-0-110
|-
| 5 || 01-10000 || 0010-0 || 0-1-000 || 0-1-000
|-
| 6 || 01-01000 || 0010-1 || 1-1-001 || 1-1-001
|-
| 7 || 01-00100 || 0001-0 || 1-1-010|| 0-1-011
|-
| 8 || 01-00010 || 0001-1 || 0-1-011 || 1-1-111
|-
| 9 || 01-00001 || 0000-1 || 1-1-100|| 0-1-110
|}
==Annexe : pourquoi l'octet existe==
Nous avons vu plus haut que les nombres encodés en binaire sont découpés en octets, des paquets de 8 bits. Et les octets sont présents partout dans la conception d'un ordinateur. Par exemple, la mémoire RAM d'un ordinateur moderne est organisée en octets. Elle contient des cases mémoire, chacune contenant un octet. Les octets sont numérotés, ce qui permet de sélectionner un octet parmi tous les autres. La capacité de la mémoire est le nombre d'octets qu'elle contient, elle s'exprime en kilo-octets (1024 octets), mébi-octets (1024 kilo-octets), gibi-octets (1024 mébi-octets). Les octets sont donc présents partout dans l'informatique moderne.
{|class="wikitable"
|+ Contenu de la RAM, simplifié
|-
! Numéro
! Octet
|-
! 0
| 0000 1111
|-
! 1
| 0101 0101
|-
! 2
| 0111 0110
|-
! 3
| 0011 1010
|-
! 4
| 1001 1001
|-
! ...
| ...
|-
! 1023
| 0110 1010
|-
! 1024
| 0001 1110
|}
Mais pourquoi est-ce que les nombres encodés en binaire sont souvent découpés en octets ? Pourquoi ne pas utiliser des groupes de 3, 5, 6, 7, 9, 10 bits ? La question est d'autant plus légitime que d'anciens ordinateurs faisaient ainsi. Leurs mémoires étaient découpé en cases mémoire, mais chacune faisait 7 ou 9 bits, rien d'impossible à cela. À vrai dire, l'usage des octets au tout début de l'informatique était assez rare. Il y a eu un basculement progressif vers l'octet pendant les années 80-90. Reste à expliquer pourquoi. Si on devait résumer, les deux raisons principales sont le support du BCD et le support des caractères pour le texte. Voyons d'abord l'encodage des caractères en binaire.
===L'encodage du texte : ASCII et autres encodages===
Pour les caractères, les encodages se sont concentrés sur l'encodage des alphabets occidentaux. Ils demandaient d'encoder au minimum toutes les lettres, en majuscules et minuscules, plus les chiffres, une dizaine de caractères pour la ponctuation. À cela, il faut ajouter des symboles utiles comme %, µ, $, £, #, {} et bien d'autres. Au minimum, cela fait au minimum 52 lettres (26 lettres doublées pour les majuscules) + 10 chiffres + une dizaine de caractères pour la ponctuation : cela fait plus de 72 valeurs à encoder. Pour encoder cela en binaire, le minimum est de 7 bits, ce qui permet d'encoder 128 caractères : les 72 caractères minimum, plus quelques autres. L'encodage ASCII utilisait d'ailleurs 7 bits pour encoder des nombres.
On pourrait croire que c'était une raison suffisante pour utiliser des groupes de 7 bits. Mais il y a quelques problèmes avec cela. Premièrement, les encodages 7 bits pour le texte marchent bien pour gérer la langue anglaise et la plupart des langues occidentales, mais d'autres langues ont besoin d'encoder des caractères locaux. Les 128 caractères de l'ASCII ne sont pas suffisant, ce qui demande de passer de 7 à 8 bits. On retombe donc sur un octet ! D'ailleurs, les encodages ASCII améliorés utilisaient les 127 premières valeurs pour l'ASCII normal, mais les 128 suivantes encodaient d'autres caractères. Les encodages en question varient d'un pays ou d'une langue à l'autre, mais ils sont terriblement importants, ce qui est un argument pour l'usage des octets.
===Le support des données encodées sur un ''nibble''===
Une autre raison pour l'usage des octets est qu'il est facile de les découper en deux ''nibbles''. Pour rappel, les ''nibbles'' sont des groupes de 4 bits. Et il se trouve que de nombreuses données sont encodées sur 4 bits, ni plus ni moins. Les ''nibbles'' sont très important pour le support du BCD et de l’hexadécimal. Et c'était un avantage important au début de l'informatique, moins maintenant. Un octet regroupe deux ''nibbles'' : un ''nibble'' de poids faible, un ''nibble'' de poids fort.
[[File:Nibbles in a byte.PNG|centre|vignette|upright=2|Nibbles dans un octet.]]
Un chiffre BCD occupe un ''nibble'', ce qui fait que la taille idéale de l'unité mémoire doit être un multiple de 4 bits : 4 bits, 8 bits, 12 bits, 16 bits, etc. Un ''nibble'' de 4 bits est insuffisant pour encoder des caractères, 8 bits fonctionne parfaitement, pas besoin d'aller plus loin. De plus, l'usage des ''nibbles'' permet de gérer la représentation hexadécimale très facilement. Un chiffre hexadécimal est encodé sur exactement un ''nibble'', un octet regroupe deux chiffres hexadécimaux, deux octets 4 chiffres hexadécimaux, etc. Enfin, 8 bits est une puissance de deux, ce qui simplifie grandement l'encodage des entiers en binaire est est une source de nombreuses simplifications. Les circuits de l'ordinateur sont plus simple à concevoir quand ils manipulent des opérandes de 2, 4, 8, 16, 32, 64, 128 bits.
===L'influence sur la taille des opérandes===
Les anciens ordinateurs étaient conçus pour le calcul scientifique, ou du moins des applications similaires. Ils n'avaient pas besoin de supporter le BCD, ni même de gérer du texte. Ils ne faisaient que des calculs et utilisaient des opérandes flottantes. La taille des opérandes était dictée par des considérations calculatoires. La norme à l'époque était que les calculs scientifiques devaient gérer des opérandes pouvant aller jusqu'à 10 chiffres décimaux. Et cela demande d'utiliser des opérandes entiers de 35 bits, avec un bit en plus pour encoder des opérandes positives ou négatives. Les 36 bits étaient parfois découpés en 6 groupes de 6 bits chacun, au lieu des octets de 8 bits.
Mais ces groupes de 6 bits étaient souvent peu utilisés. Prenons l'exemple des mémoires RAM. Plus haut, j'ai dit qu'une mémoire RAM normale est composée de plusieurs octets, tous numérotés. Intuitivement, vous vous dites que les anciens ''mainframes'' remplaçaient ces octets par des groupes de 6 bits dans la RAM. Mais en réalité, l'équivalent des RAM de l'époque utilisait des groupes de 36 bits. les groupes de 36 bits étaient aussi numérotés, les numéros étant appelés des adresses mémoire. Par contre, les adresses/numéros étaient codés sur 12 ou 18 bits, c'est à dire sur 2 ou 3 groupes de 6 bits. La raison est que ces numéros sont manipulés par le processeur, qui gère des opérandes de 36 bits. L'idée est que le numéro ne prend que la moitié ou un tiers des 36 bits gérés par le processeur.
Avec l'apparition de l'informatique commerciale, les ordinateurs ont du s'adapter aux besoins des entreprises, pour gérer la comptabilité, des applications financières, des documents texte, etc. Et cela demandait de supporter à la fois le BCD et les caractères/lettres. Il a fallu trouver une unité mémoire capable de gérer à la fois nombres entiers, chiffres BCD et caractères. L'octet était parfait pour ça. Les ''mainframes'' sont adaptés en supportant des caractères codés sur 6, 7 ou 8 bits. Mais le support demandait d'utiliser des circuits qui découpaient un groupe de 36 bits en plusieurs caractères ou en plusieurs chiffres BCD. Le tout demandait des circuits de décalage et de masquage assez complexes. Les ordinateurs 8 et 16 bits manipulaient des octets nativement : leur mémoire RAM gérait des octets, le processeur traitait des opérandes de 8, 16 ou 32 bits. Ils n'avaient pas besoin de matériel complexe pour extraire un caractère ou un chiffre BCD d'un octet/doublet/multiplet. C'était un avantage que les anciens ''mainframes'' n'avaient pas.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'encodage des données
| prevText=L'encodage des données
| next=Les codes de détection/correction d'erreur
| nextText=Les codes de détection/correction d'erreur
}}
</noinclude>
gfa8s1zb8fij00nqsnc3fl23iywvu5s
775645
775644
2026-11-02T19:41:19Z
Mewtow
31375
/* Les nombres flottants logarithmiques */
775645
wikitext
text/x-wiki
Dans le chapitre précédent, nous avons vu que les ordinateurs actuels utilisent un codage binaire. Ce codage binaire ne vous est peut-être pas familier. Aussi, dans ce chapitre, nous allons apprendre comment coder des nombres en binaire. Nous allons commencer par le cas le plus simple : les nombres positifs. Par la suite, nous aborderons les nombres négatifs. Et nous terminerons par les nombres à virgules, appelés aussi nombres flottants.
==Le codage des nombres entiers positifs==
Pour coder des nombres entiers positifs, il existe plusieurs méthodes : le binaire, l’hexadécimal, le code Gray, le décimal codé binaire et bien d'autres encore. La plus connue est certainement le binaire, secondée par l'hexadécimal, les autres étant plus anecdotiques. Pour comprendre ce qu'est le binaire, il nous faut faire un rappel sur les nombres entiers tel que vous les avez appris en primaire, à savoir les entiers écrits en décimal. Prenons un nombre écrit en décimal : le chiffre le plus à droite est le chiffre des unités, celui à côté est pour les dizaines, suivi du chiffre des centaines, et ainsi de suite. Dans un tel nombre :
* on utilise une dizaine de chiffres, de 0 à 9 ;
* chaque chiffre est multiplié par une puissance de 10 : 1, 10, 100, 1000, etc. ;
* la position d'un chiffre dans le nombre indique par quelle puissance de 10 il faut le multiplier : le chiffre des unités doit être multiplié par 1, celui des dizaines par 10, celui des centaines par 100, et ainsi de suite.
Exemple avec le nombre 1337 :
<math>1337 = 1 \times 1000 + 3 \times 100 + 3 \times 10 + 7 \times 1</math>
Pour résumer, un nombre en décimal s'écrit comme la somme de produits, chaque produit multipliant un chiffre par une puissance de 10. On dit alors que le nombre est en base 10.
<math>1337 = 1 \times 10^3 + 3 \times 10^2 + 3 \times 10^1 + 7 \times 10^0</math>
Ce qui peut être fait avec des puissances de 10 peut être fait avec des puissances de 2, 3, 4, 125, etc : n'importe quel nombre entier strictement positif peut servir de '''base'''. En informatique, on utilise rarement la base 10 à laquelle nous sommes tant habitués. On utilise à la place deux autres bases :
* La base 2 (système binaire) : les chiffres utilisés sont 0 et 1 ;
* La base 16 (système hexadécimal) : les chiffres utilisés sont 0, 1, 2, 3, 4, 5, 6, 7, 8 et 9 ; auxquels s'ajoutent les six premières lettres de notre alphabet : A, B, C, D, E et F.
===Le système binaire===
En binaire, on compte en base 2. Cela veut dire qu'au lieu d'utiliser des puissances de 10 comme en décimal, on utilise des puissances de deux : n'importe quel nombre entier peut être écrit sous la forme d'une somme de puissances de 2. Par exemple 6 s'écrira donc 0110 en binaire : <math>0 \times 2^3 + 1 \times 2^2 + 1 \times 2^1 + 0 \times 2^0 = 6</math>. On peut remarquer que le binaire n'autorise que deux chiffres, à savoir 0 ou 1 : ces chiffres binaires sont appelés des '''bits''' (abréviation de Binary Digit). Pour simplifier, on peut dire qu'un bit est un truc qui vaut 0 ou 1. Pour résumer, tout nombre en binaire s'écrit sous la forme d'un produit entre bits et puissances de deux de la forme :
<math>a_0 \times 2^0 + a_1 \times 2^1 + a_2 \times 2^2 + a_3 \times 2^3 + a_4 \times 2^4 +...+ a_n \times 2^n</math>
Les coefficients <math>a_0, a_1, a_2</math> sont les bits, l'exposant n qui correspond à un bit <math>a_n</math> est appelé le '''poids''' du bit.
====La terminologie du binaire====
En informatique, il est rare que l'on code une information sur un seul bit. Dans la plupart des cas, l'ordinateur manipule des nombres codés sur plusieurs bits. Les informaticiens ont donné des noms aux groupes de bits suivant leur taille. Le plus connu est certainement l''''octet''', qui désigne un groupe de 8 bits. Moins connu, on parle de '''''nibble''''' pour un groupe de 4 bits (un demi-octet), de '''doublet''' pour un groupe de 16 bits (deux octets) et de '''quadruplet''' pour un groupe de 32 bits (quatre octets).
Précisons qu'en anglais, le terme ''byte'' n'est pas synonyme d'octet. En réalité, le terme ''octet'' marche aussi bien en français qu'en anglais. Quant au terme ''byte'', il désigne un concept complètement différent, que nous aborderons plus tard (c'est la plus petite unité de mémoire que le processeur peut adresser). Il a existé dans le passé des ordinateurs où le ''byte'' faisait 4, 7, 9, 16, voire 48 bits, par exemple. Il a même existé des ordinateurs où le ''byte'' faisait exactement 1 bit ! Mais sur presque tous les ordinateurs modernes, un ''byte'' fait effectivement 8 bits, ce qui fait que le terme ''byte'' est parfois utilisé en lieu et place d'''octet''. Mais c'est un abus de langage, attention aux confusions ! Dans ce cours, nous parlerons d'octet pour désigner un groupe de 8 bits, en réservant le terme ''byte'' pour sa véritable signification.
À l'intérieur d'un nombre, le '''bit de poids faible''' est celui qui est le plus à droite du nombre, alors que le '''bit de poids fort''' est celui non nul qui est placé le plus à gauche, comme illustré dans le schéma ci-dessous.
C'est le même principe que l'écriture des nombres en base décimale : le chiffre le plus significatif à gauche, et le moins significatif à droite (l'unité ou les décimales après la virgule).
[[File:Msb.svg|centre|vignette|upright=2|Bit de poids fort.]]
[[File:Lsb.svg|centre|vignette|upright=2|Bit de poids faible.]]
Cette terminologie s'applique aussi pour les bits à l'intérieur d'un octet, d'un ''nibble'', d'un doublet ou d'un quadruplet. Pour un nombre codés sur plusieurs octets, on peut aussi parler de l'octet de poids fort et de l'octet de poids faible, du doublet de poids fort ou de poids faible, etc.
====La traduction binaire→décimal====
Pour traduire un nombre binaire en décimal, il faut juste se rappeler que la position d'un bit indique par quelle puissance il faut le multiplier. Ainsi, le chiffre le plus à droite est le chiffre des unités : il doit être multiplié par 1 (<math>2^0</math>). Le chiffre situé immédiatement à gauche du chiffre des unités doit être multiplié par 2 (<math>2^1</math>). Le chiffre encore à gauche doit être multiplié par 4 (<math>2^2</math>), et ainsi de suite. Mathématiquement, on peut dire que le énième bit en partant de la droite doit être multiplié par <math>2^{n-1}</math>. Par exemple, la valeur du nombre noté 1011 en binaire est de <math>8 \times 1 + 4 \times 0 + 2 \times 1 + 1 \times 1 = 11</math>.
[[File:Value of digits in the Binary numeral system.svg|centre|vignette|upright=2|Valeur des chffres dans le système de numération binaire.]]
====La traduction décimal→binaire====
La traduction inverse, du décimal au binaire, demande d'effectuer des divisions successives par deux. Les divisions en question sont des divisions euclidiennes, avec un reste et un quotient. En lisant les restes des divisions dans un certain sens, on obtient le nombre en binaire. Voici comment il faut procéder, pour traduire le nombre 34 :
[[File:Exemple d'illustration de la méthode de conversion décimal vers binaire.png|centre|vignette|upright=2|Exemple d'illustration de la méthode de conversion décimal vers binaire.]]
====Quelques opérations en binaire====
Maintenant que l'on sait coder des nombres en binaire normal, il est utile de savoir comment faire quelques opérations usuelles en binaire. Nous utiliserons les acquis de cette section dans la suite du chapitre, bien que de manière assez marginale.
La première opération est assez spécifique au binaire. Il s'agit d'une opération qui inverse les bits d'un nombre : les 0 deviennent des 1 et réciproquement. Par exemple, le nombre 0001 1001 devient 1110 0110. Elle porte plusieurs noms : opération NOT, opération NON, complémentation, etc. Nous parlerons de '''complémentation''' ou d'opération NOT dans ce qui suit. Beaucoup d'ordinateurs gèrent cette opération, ils savent la faire en un seul calcul. Il faut dire que c'est une opération assez utile, bien que nous ne pouvons pas encore expliquer pourquoi.
[[File:Binary Addition Demonstration.svg|vignette|Exemple d'addition en binaire.]]
La seconde opération à aborder est l'addition. Elle se fait en binaire de la même manière qu'en décimal : Pour faire une addition en binaire, on additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. Sauf que l'on additionne des bits. Heureusement, la table d'addition est très simple en binaire :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
La troisième opération est une variante de l'addition appelée l''''opération XOR''', notée <math>\oplus</math>. Il s'agit d'une addition dans laquelle on ne propage pas les retenues. L'addition des deux bits des opérandes se fait normalement, mais les retenues sont simplement oubliées, on n'en tient pas compte. Le résultat est que l'addition se résume à appliquer la table d'addition précédente :
* 0 <math>\oplus</math> 0 = 0 ;
* 0 <math>\oplus</math> 1 = 1 ;
* 1 <math>\oplus</math> 0 = 1 ;
* 1 <math>\oplus</math> 1 = 0.
Pour résumer, le résultat vaut 1 si les deux bits sont différents, 0 s'ils sont identiques. L'opération XOR sera utilisée rapidement dans le chapitre suivant, quand nous parlerons rapidement du mot de parité. Et elle sera beaucoup utilisée dans la suite du cours, nous en feront fortement usage. Pour le moment, mémorisez juste cette opération, elle n'a rien de compliqué.
Une dernière opération est l'opération de '''''population count'''''. Il s'agit ni plus ni moins que de compter le nombre de bits qui sont à 1 dans un nombre. Par exemple, pour le nombre 0110 0010 1101 1110, elle donne pour résultat 9. Elle est utilisée dans certaines applications, comme le calcul de certains codes correcteurs d'erreur, comme on le verra dans le chapitre suivant. Elle est supportée sur de nombreux ordinateurs, encore que cela dépende du processeur considéré. Il s'agit cependant d'une opération assez courante, supportée par les processeurs ARM, les processeurs x86 modernes (ceux qui gèrent le SSE), et quelques autres.
===L'hexadécimal===
L’hexadécimal est basé sur le même principe que le binaire, sauf qu'il utilise les 16 chiffres suivants :
{| class="wikitable"
|- style="color:#8080ffff; text-align:center;"
! Chiffre hexadécimal
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || A || B || C || D || E || F
|- style="text-align:center;"
! Nombre décimal correspondant
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 10 || 11 || 12 || 13 || 14 || 15
|- style="text-align:center;"
! Notation binaire
| 0000 || 0001 || 0010 || 0011 || 0100 || 0101 || 0110 || 0111 || 1000 || 1001 || 1010 || 1011 || 1100 || 1101 || 1110 || 1111
|}
Dans les textes, afin de différencier les nombres décimaux des nombres hexadécimaux, les nombres hexadécimaux sont suivis par un petit h, indiqué en indice. Si cette notation n'existait pas, des nombres comme 2546 seraient ambigus : on ne saurait pas dire sans autre indication s'ils sont écrits en décimal ou en hexadécimal. Avec la notation, on sait de suite que 2546 est en décimal et que 2546h est en hexadécimal.
Dans les codes sources des programmes, la notation diffère selon le langage de programmation.
Certains supportent le suffixe <code>h</code> pour les nombres hexadécimaux, d'autres utilisent un préfixe <code>0x</code> ou <code>0h</code>.
Pour convertir un nombre hexadécimal en décimal, il suffit de multiplier chaque chiffre par la puissance de 16 qui lui est attribuée. Là encore, la position d'un chiffre indique par quelle puissance celui-ci doit être multiplié : le chiffre le plus à droite est celui des unités, le second chiffre le plus à droite doit être multiplié par 16, le troisième chiffre en partant de la droite doit être multiplié par 256 (16 * 16) et ainsi de suite. La technique pour convertir un nombre décimal vers de l’hexadécimal est similaire à celle utilisée pour traduire un nombre du décimal vers le binaire. On retrouve une suite de divisions successives, mais cette fois-ci les divisions ne sont pas des divisions par 2 : ce sont des divisions par 16.
La conversion inverse, de l'hexadécimal vers le binaire est très simple, nettement plus simple que les autres conversions. Pour passer de l'hexadécimal au binaire, il suffit de traduire chaque chiffre en sa valeur binaire, celle indiquée dans le tableau au tout début du paragraphe nommé « Hexadécimal ». Une fois cela fait, il suffit de faire le remplacement. La traduction inverse est tout aussi simple : il suffit de grouper les bits du nombre par 4, en commençant par la droite (si un groupe est incomplet, on le remplit avec des zéros). Il suffit alors de remplacer le groupe de 4 bits par le chiffre hexadécimal qui correspond.
==Interlude propédeutique : la capacité d'un entier et les débordements d'entiers==
Dans la section précédente, nous avons vu comment coder des entiers positifs en binaire ou dans des représentations proches. La logique voudrait que l'on aborde ensuite le codage des entiers négatifs. Mais nous allons déroger à cette logique simple, pour des raisons pédagogiques. Nous allons faire un interlude qui introduira des notions utiles pour la suite du chapitre. De plus, ces concepts seront abordés de nombreuses fois dans ce wikilivre et l'introduire ici est de loin la solution idéale.
===Les ordinateurs manipulent des nombres codés sur un nombre fixe de bits===
Vous avez certainement déjà entendu parler de processeurs 32 ou 64 bits. Et si vous avez joué aux jeux vidéos durant votre jeunesse et êtes assez agé, vous avez entendu parler de consoles de jeu 8 bits, 16 bits, 32 bits, voire 64 bits (pour la Jaguar, et c'était un peu trompeur). Derrière cette appellation qu'on retrouvait autrefois comme argument commercial dans la presse se cache un concept simple. Tout ordinateur manipule des nombres entiers dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Une console 16 bits manipulait des entiers codés en binaire sur 16 bits, pas un de plus, pas un de moins. Pareil pour les anciens ordinateurs 32 bits, qui manipulaient des nombres entiers codés sur 32 bits.
Aujourd'hui, les ordinateurs modernes utilisent presque un nombre de bits qui est une puissance de 2 : 8, 16, 32, 64, 128, 256, voire 512 bits. Mais cette règle souffre évidemment d'exceptions. Aux tout débuts de l'informatique, certaines machines utilisaient 3, 7, 13, 17, 23, 36 et 48 bits ; mais elles sont aujourd'hui tombées en désuétude. De nos jours, il ne reste que les processeurs dédiés au traitement de signal audio, que l'on trouve dans les chaînes HIFI, les décodeurs TNT, les lecteurs DVD, etc. Ceux-ci utilisent des nombres entiers de 24 bits, car l'information audio est souvent codée par des nombres de 24 bits.
Anecdote amusante, il a existé des ordinateurs de 1 bit, qui sont capables de manipuler des nombres codés sur 1 bit, pas plus. Un exemple est le Motorola MC14500B, commercialisé de 1976.
====Le lien entre nombre de bits et valeurs codables====
Évidemment, on ne peut pas coder tous les entiers possibles et imaginables avec seulement 8 bits, ou 16 bits. Et il parait intuitif que l'on ait plus de valeurs codables sur 16 bits qu'avec 8 bits, par exemple. Plus le nombre de bits est important, plus on pourra coder de valeurs entières différentes. Mais combien de plus ? Par exemple, si je passe de 8 bits à 16 bits, est-ce que le nombre de valeurs que l'on peut coder double, quadruple, pentuple ? De même, combien de valeurs différentes on peut coder avec <math>n</math> bits. Par exemple, combien de nombres différents peut-on coder avec 4, 8 ou 16 bits ? La section précédente vous l'expliquer.
Avec <math>n</math> bits, on peut coder <math>2^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>2^n - 1</math>. N'oubliez pas cette formule : elle sera assez utile dans la suite de ce tutoriel. Pour exemple, on peut coder 16 valeurs avec 4 bits, qui vont de 0 à 15. De même, on peut coder 256 valeurs avec un octet, qui vont de 0 à 255. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de bits
! Nombre de valeurs codables
|-
! 4
| 16
|-
! 8
| 256
|-
! 16
| 65 536
|-
! 32
| 4 294 967 296
|-
! 64
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien de bits il faut pour coder une valeur quelconque, que nous noterons N. Pour cela, il faut utiliser la formule précédente, mais à l'envers. On cherche alors <math>n</math> tel que <math>N = 2^n</math>. L'opération qui donne <math>n</math> est appelée le logarithme, et plus précisément un logarithme en base 2, noté <math>\log_2{(X)}</math>. Le problème est que le résultat du logarithme ne tombe juste que si le nombre X est une puissance de 2. Si ce n'est pas le cas, le résultat est un nombre à virgule, ce qui n'a pas de sens pratique. Par exemple, la formule nous dit que pour coder le nombre 13, on a besoin de 3,70043971814 bits, ce qui est impossible. Pour que le résultat ait un sens, il faut arrondir à l'entier supérieur. Pour l'exemple précédent, les 3,70043971814 bits s'arrondissent en 4 bits.
====Le lien entre nombre de chiffres hexadécimaux et valeurs codables====
Maintenant, voyons combien de valeurs peut-on coder avec <math>n</math> chiffres hexadécimaux. La réponse n'est pas très différente de celle obtenue en binaire, si ce n'est qu'il faut remplacer le 2 par un 16 dans la formule précédente. Avec <math>n</math> chiffres hexadécimaux, on peut coder <math>16^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>16^n - 1</math>. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de chiffres héxadécimaux
! Nombre de valeurs codables
|-
! 1 (4 bits)
| 16
|-
! 2 (8 bits)
| 256
|-
! 4 (16 bits)
| 65 536
|-
! 8 (32 bits)
| 4 294 967 296
|-
! 16 (64 bits)
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien faut-il de chiffres hexadécimaux pour coder une valeur quelconque en hexadécimal. La formule est là encore la même qu'en binaire, sauf qu'on remplace le 2 par un 16. Pour trouver le nombre de chiffres hexadécimaux pour encoder un nombre X, il faut calculer <math>\log_{16}{(X)}</math>. Notons que le logarithme utilisé est un logarithme en base 16, et non un logarithme en base 2, comme pour le binaire. Là encore, le résultat ne tombe juste que si le nombre X est une puissance de 16 et il faut arrondir à l'entier supérieur si ce n'est pas le cas.
Une propriété mathématique des logarithmes nous dit que l'on peut passer d'un logarithme en base X et à un logarithme en base Y avec une simple division, en utilisant la formule suivante :
: <math>\log_X{(N)} = {\log_{Y}{(N)} \over \log_Y(X)}</math>
Dans le cas qui nous intéresse, on a Y = 2 et X = 16, ce qui donne :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over \log_2(16)}</math>
Or, <math>\log_2(16)</math> est tout simplement égal à 4, car il faut 4 bits pour coder la valeur 16. On a donc :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over 4}</math>
En clair, il faut quatre fois moins de chiffres hexadécimaux que de bits, ce qui est assez intuitif vu qu'il faut 4 bits pour coder un chiffre hexadécimal.
===Les débordements d'entier===
On vient de voir que tout ordinateur manipule des nombres dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Et cela limite les valeurs qu'il peut encoder, qui sont comprises dans un intervalle bien précis. Mais que ce passe-t-il si jamais le résultat d'un calcul ne rentre pas dans cet intervalle ? Par exemple, pour du binaire normal, que faire si le résultat d'un calcul atteint ou dépasse <math>2^n</math> ? Dans ce cas, le résultat ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
On peut imaginer d'autres codages pour lesquels les entiers ne commencent pas à zéro ou ne terminent pas à <math>2^n - 1</math>. On peut prendre le cas où l'ordinateur gère les nombres négatifs, par exemple. Dans le cas général, l'ordinateur peut coder les valeurs comprises dans un intervalle, qui va de la valeur la plus basse <math>N_\text{min}</math> à la valeur la plus grande <math>N_\text{max}</math>. Et encore une fois, si un résultat de calcul sort de cet intervalle, on fait face à un débordement d'entier.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math>, avec <math>N_\text{max}</math> la plus grande valeur codable par l'ordinateur.
On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur basse de débordement vaut <math>N_\text{min} - 1</math>, avec <math>N_\text{min}</math> la plus petite valeur codable par l'ordinateur.
===La gestion des débordements d'entiers===
Face à un débordement d'entier, l'ordinateur peut utiliser deux méthodes : l'arithmétique saturée ou l'arithmétique modulaire.
L''''arithmétique saturée''' consiste à arrondir le résultat pour prendre la plus grande ou la plus petite valeur. Si le résultat d'un calcul dépasse la valeur haute de débordement, le résultat est remplacé par le plus grand entier supporté par l'ordinateur. La même chose est possible quand le résultat est inférieur à la plus petite valeur possible, par exemple lors d'une soustraction : l'ordinateur arrondit au plus petit entier possible.
Pour donner un exemple, voici ce que cela donne avec des entiers codés sur 4 bits, qui codent des nombres de 0 à 15. Si je fais le calcul 8 + 9, le résultat normal vaut 17, ce qui ne rentre pas dans l'intervalle. Le résultat est alors arrondi à 15. Inversement, si je fais le calcul 8 - 9, le résultat sera de -1, ce qui ne rentre pas dans l'intervalle : le résultat est alors arrondi à 0.
[[File:Odometer rollover.jpg|vignette|Exemple de débordement d'entier sur un compteur mécanique quelconque. L'image montre clairement que le compteur revient à zéro une fois la valeur maximale dépassée.]]
L''''arithmétique modulaire''' est plus compliquée et c'est elle qui va nous intéresser dans ce qui suit. Pour simplifier, imaginons que l'on décompte à partir de zéro. Quand on arrive à la valeur haute de débordement, on recommence à compter à partir de zéro. L'arithmétique modulaire n'est pas si contre-intuitive et vous l'utilisez sans doute au quotidien. Après tout, c'est comme cela que l'on compte les heures, les minutes et les secondes. Quand on compte les minutes, on revient à 0 au bout de 60 minutes. Pareil pour les heures : on revient à zéro quand on arrive à 24 heures. Divers compteurs mécaniques fonctionnent sur le même principe et reviennent à zéro quand ils dépassent la plus grande valeur possible, l'image ci-contre en montrant un exemple.
Mathématiquement, l'arithmétique modulaire implique des divisions euclidiennes, celles qui donnent un quotient et un reste. Lors d'un débordement, le résultat s'obtient comme suit : on divise le nombre qui déborde par la valeur haute de débordement, et que l'on conserve le reste de la division. Au passage, l'opération qui consiste à faire une division et à garder le reste au lieu du quotient est appelée le ''modulo''. Prenons l'exemple où l'ordinateur peut coder tous les nombres entre 0 et 1023, soit une valeur haute de débordement de 1024. Pour coder le nombre 4563, on fait le calcul 4563 / 1024. On obtient : <math>4563 = 4 \times 1024 + 467</math>. Le reste de la division est de 467 et c'est lui qui sera utilisé pour coder la valeur de départ, 4563. Le nombre 4563 est donc codé par la valeur 467 dans un tel ordinateur en arithmétique modulaire. Au passage, la valeur haute de débordement est toujours codée par un zéro dans ce genre d'arithmétique.
Les ordinateurs utilisent le plus souvent une valeur haute de débordement de <math>2^n</math>, avec n le nombre de bits utilisé pour coder les nombres entiers positifs. En faisant cela, l'opération modulo devient très simple et revient à éliminer les bits de poids forts au-delà du énième bit. Par exemple, reprenons l'exemple d'un ordinateur qui code ses nombres sur 4 bits. Imaginons qu'il fasse le calcul <math>13 + 3</math>, soit 1101 + 0011 = 1 0000 en binaire. Le résultat entraîne un débordement d'entier et l'ordinateur ne conserve que les 4 bits de poids faible. Cela donne : 1101 + 0011 = 0000. Comme autre exemple l'addition 1111 + 0010 ne donnera pas 17 (1 0001), mais 1 (0001).
L'avantage est que les calculs sont beaucoup plus simples avec cette méthode qu'avec les autres. L'ordinateur a juste à ne pas calculer les bits de poids fort. Pas besoin de faire une division pour calculer un modulo, pas besoin de corriger le résultat pour faire de l'arithmétique saturée.
===Aparté : quelques valeurs particulières en binaire===
Plus haut, on a dit qu'avec n bits, on peut encoder toutes les valeurs allant de <math>0</math> à <math>2^n - 1</math>. Ce simple fait permet de déterminer quelle est la valeur de certains entiers à vue d’œil. Dans ce qui va suivre, nous allons poser quelques bases que nous réutiliserons dans la suite du chapitre. Il s'agit de quelques trivias qui sont cependant assez utiles.
Le premier trivia concerne la valeur maximale <math>2^n - 1</math> : elle est encodée par un nombre dont tous les bits sont à 1.
Le deuxième trivia concerne les nombres de la forme 000...000 1111 1111, à savoir des nombres dont les x bits de poids faible sont à 1 et tous les autres valent 0. Par définition, de tels nombres codent la plus grande valeur possible sur x bits, ce qui fait qu'ils valent <math>2^x - 1</math>.
Le troisième trivia concerne les nombres de la forme 11111...0000. En clair, des nombres où on a une suite consécutive de 1 dans les bits de poids fort et les x bits de poids faibles à 0. De tels nombres valent : <math>2^n - 2^x</math>. La preuve est assez simple : ils s'obtiennent en prenant la valeur maximale <math>2^n - 1</math>, et en soustrayant un nombre de la forme <math>2^x - 1</math>.
[[File:Valeurs particulières en binaire.png|centre|vignette|upright=2|Valeurs particulières en binaire]]
Si on utilise l'arithmétique modulaire, la valeur <math>2^n</math> n'est autre que la valeur de débordement haute pour les nombres stockés sur n bits, et se code comme le zéro (il faudrait 1 bit de plus pour stocker le 1 de poids fort de <math>2^n</math>). Les 1er et 3ème nombres évoqués dans les paragraphes précédents peuvent donc se passer de <math>2^n</math> dans leur expression :
* <math>- 1</math> est codé 1111111111111111...111 (n bits à 1)
* <math>- 2^x</math> est codé 11111...111000..000000 (x bits à 0 précédés de n-x bits à 1)
==Les nombres entiers négatifs==
<noinclude>[[File:Binary Representations.svg|vignette|Représentations signées, exemples sur 4 bits.]]</noinclude>
Passons maintenant aux entiers négatifs en binaire : comment représenter le signe moins ("-") avec des 0 et des 1 ? Eh bien, il existe plusieurs méthodes :
* la représentation en signe-valeur absolue ;
* la représentation en complément à un ;
* la représentation en complément à deux;
* la représentation par excès ;
* la représentation dans une base négative ;
* d'autres représentations encore moins utilisées que les autres.
===La représentation en signe-valeur absolue===
La solution la plus simple pour représenter un entier négatif consiste à coder sa valeur absolue en binaire, et rajouter un bit qui précise si c'est un entier positif ou un entier négatif. Par convention, ce bit de signe vaut 0 si le nombre est positif et 1 s'il est négatif. On parle alors de représentation en signe-valeur absolue, aussi appelée '''représentation en signe-magnitude'''.
Avec cette technique, il y autant de nombres positifs que négatifs. Mieux : pour chaque nombre représentable en représentation signe-valeur absolue, son inverse l'est aussi. Ce qui fait qu'avec cette méthode, le zéro est codé deux fois : on a un -0, et un +0. Cela pose des problèmes lorsqu'on demande à notre ordinateur d'effectuer des calculs ou des comparaisons avec zéro.
[[File:Codage sur 4 bits en signe-valeur absolue.png|centre|vignette|upright=2|Codage sur 4 bits en signe-valeur absolue]]
===La représentation en complément à un===
La représentation en '''complément à un''' peut être vue, en première approximation, comme une variante de la représentation en signe-magnitude. Et je dis bien : "en première approximation", car il y a beaucoup à dire sur la représentation en complément à un, mais nous verrons cela dans la section suivante sur le complément à deux. Conceptuellement, la représentation en signe-magnitude et celle du complément à un sont drastiquement différentes et sont basées sur des concepts mathématiques totalement différents. Mais elles ont des ressemblances de surface, qui font que faire la comparaison entre les deux est assez utile pédagogiquement parlant.
En complément à un, les nombres sont codés en utilisant un bit de signe qui indique si le nombre de positif ou négatif, couplé à une valeur absolue. Par contre, la valeur absolue est codée différemment en binaire. Pour les nombres positifs, la valeur absolue est codée en binaire normal, pas de changement comparé aux autres représentations. Mais pour les valeurs négatives, la valeur absolue est codée en binaire normal, puis tous les bits sont inversés : les 0 deviennent des 1 et réciproquement. En clair, on utilise une opération de complémentation pour les nombres négatifs.
Prenons un exemple avec un nombre codé sur 4 bits, avec un cinquième bit de signe. La valeur 5 est codée comme suit : 0 pour le bit de signe, 5 donne 0101 en binaire, le résultat est 0 0101. Pour la valeur -5, le bit de signe est 1, 5 donne 0101 en binaire, on inverse les bits ce qui donne 1010 : cela donne 11010. Notez qu'on peut passer d'un résultat à l'autre avec une opération de complémentation, à savoir en inversant les bits de l'autre et réciproquement.
Il s'agit là d'une propriété générale avec le complément à 1 : l'opposé d'un nombre, à savoir passer de sa valeur positive à sa valeur négative ou inversement, se calcule avec une opération NOT, une opération de complémentation (d'où son nom). L'avantage est que les ordinateurs gèrent naturellement d'opération de complémentation. Par contre, en signe-magnitude, inverser le bit de signe est une opération spécifique et ne sert qu'à ça. Il faut donc rajouter une opération en plus pour calculer l'opposé d'un nombre.
La représentation en complément à un garde les défauts de la représentation en signe-magnitude. Le zéro est codé deux fois, avec un zéro positif et un zéro négatif. La différence est que les valeurs négatives sont dans l'ordre inverse, il y a une symétrie un peu meilleure. Les deux zéros sont d'ailleurs totalement éloignés, ils correspondent aux valeurs extrêmes encodables : le zéro positif a tous ses bits à 0, le zéro négatif a tous ses bits à 1. Le résultat est que l'implémentation des comparaisons et de certains calculs est plus complexe qu'avec le signe-magnitude, mais de peu.
[[File:Codage sur 4 bits en complément à 1.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 1.]]
===La représentation par excès===
La représentation par excès consiste à ajouter un biais aux nombres à encoder afin de les encoder par un entier positif. Pour encoder tous les nombres compris entre -X et Y en représentation par excès, il suffit de prendre la valeur du nombre à encoder, et de lui ajouter un biais égal à X. Ainsi, la valeur -X sera encodée par zéro, et toutes les autres valeurs le seront par un entier positif, le zéro sera encodé par X, 1, par X+1, etc. Par exemple, prenons des nombres compris entre -127 et 128. On va devoir ajouter un biais égal à 127, ce qui donne :
{|class="wikitable"
|-
!Valeur avant encodage!!Valeur après encodage
|-
||-127||0
|-
||-126||1
|-
||-125||2
|-
||…||…
|-
||0||127
|-
||…||…
|-
||127||254
|-
||128||255
|}
===La représentation en complément à deux===
La représentation en '''complément à deux''' est basée sur les mêmes mathématiques que le complément à un, mais fonctionne très différemment en pratique.
Si on regarde de loin, son principe est assez différent : il n'y a pas de bit de signe ni quoi que ce soit d'autre. À la place, un nombre en complément à deux est encodé comme en binaire normal, à un point près : le bit de poids fort est soustrait, et non additionné aux autres. Il a une valeur négative : on soustrait la puissance de deux associée s'il vaut 1, on ne la tient pas en compte s'il vaut 0.
Par exemple, la valeur du nombre noté 111001 en complément à deux s'obtient comme suit :
{|class="wikitable" style="margin:auto;"
|-
! -32 !! 16 !! 8 !! 4 !! 2 !! 1
|-
| 1 || 1 || 1 || 0 || 0 || 1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(8×1)+(4×0)+(2×1)+(1×1) = −32+16+8+1 = -7.
: ''Avec le complément à deux, comme avec le complément à un, le bit de poids fort vaut 0 pour les nombres positifs et 1 pour les négatifs.''
L'avantage de cette représentation est qu'elle n'a pas de double zéro : le zéro n'est encodé que par une seule valeur. Par contre, la valeur autrefois prise par le zéro négatif est réutilisée pour encoder une valeur négative. La conséquence est que l'on a un nombre négatif en plus d'encodé, il n'y a plus le même nombre de valeurs strictement positives et de valeurs négatives encodées. Le nombre négatif en question est appelé le '''nombre le plus négatif''', ce nom trahit le fait que c'est celui qui a la plus petite valeur (la plus grande valeur absolue). Il est impossible de coder l'entier positif associé, sa valeur absolue.
[[File:Codage sur 4 bits en complément à 2.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 2.]]
====Le calcul du complément à deux : première méthode====
Les représentations en complément à un et en complément à deux sont basées sur le même principe mathématique. Leur idée est de remplacer chaque nombre négatif par un nombre positif équivalent, appelé le ''complément''. Par équivalent, on veut dire que tout calcul donne le même résultat si on remplace un nombre négatif par son complément (idem avec un nombre positif, son complément aura un signe inverse). Et pour faire cela, elles se basent sur les débordements d'entier pour fonctionner, et plus précisément sur l'arithmétique modulaire abordée plus haut.
Si on fait les calculs avec le complément, les résultats du calcul entraînent un débordement d'entier, qui sera résolu par un '''modulo''' : l'ordinateur ne conserve que les bits de poids faible du résultat, les autres bits sont oubliés. Par exemple, prenons l'addition 15 + 2, 1111 + 0010 en binaire : le résultat ne sera pas 17 (10001), vu qu'on n'a pas assez de bits pour encoder le résultat, mais 1 (0001). Et le résultat après modulo sera identique au résultat qu'on aurait obtenu avec le nombre négatif sans modulo. En clair, c'est la gestion des débordements qui permet de corriger le résultat de manière à ce que l'opération avec le complément donne le même résultat qu'avec le nombre négatif voulu. Ainsi, on peut coder un nombre négatif en utilisant son complément positif.
: Cela ressemble beaucoup à la méthode de soustraction basée sur un complément à 9 pour ceux qui connaissent, sauf que c'est une version binaire qui nous intéresse ici.
Prenons un exemple, qui permettra d'introduire la suite. Encore une fois, on utilise un codage sur 4 bits dont la valeur haute de débordement est de 16. Prenons l'addition de 13 + 3 = 16. Avec l'arithmétique modulaire, 16 est équivalent à 0, ce qui donne : 13 + 3 = 0 ! On peut aussi reformuler en disant que 13 = -3, ou encore que 3 = -13. Dit autrement, 3 est le complément de -13 pour ce codage. Et ne croyez pas que ça marche uniquement dans cet exemple : cela se généralise assez rapidement à tout nombre négatif.
Prenons un nombre N dont un veut calculer le complément à deux K. Dans le cas général, on a :
: <math>N + K = 2^n</math>, vu que <math>2^n = 0</math> en utilisant le modulo.
En réorganisant très légèrement les termes pour isoler K, on a :
: <math>K = 2^n - N</math>
La formule précédent permet de calculer la complément à deux assez simplement, en faisant le calcul à la main.
Avant de poursuivre, prenons un exemple très intéressant : le cas où N = -1. Son complément à deux vaut donc :
: <math>K = 2^n - 1</math>
Le terme <math>2^n - 1</math> devrait vous rappeler quelque chose : il s'agit d'un nombre dont tous les bits sont à 1. En clair, le complément à deux de -1 est un nombre de la forme 1111...111. Et cela vaut quel que soit le nombre de bits n. La représentation de -1 est similaire, peu importe que l'on utilise des nombres de 16 bits, 32 bits, 64 bits, etc.
Voyons maintenant le cas des puissance de deux, par exemple 2, 4, 8, 16, etc. Leur complément à deux vaut :
: <math>K = 2^n - 2^x</math>
Nous avions vu précédemment dans le chapitre que ces nombres sont de la forme 11111...0000. En clair, des nombres dont les x bits de poids faible sont à 0, et tous les autres bits sont à 1. Par exemple, le complément à deux de -2 est un nombre dont les bits sont à 1, sauf le bit de poids faible. De même, le complément à deux de -4 a tous ses bits à 1, sauf les deux bits de poids faible. Et le complément à deux de -8 a tous ses bits à 1, sauf les trois bits de poids faible. Pour résumer, tout nombre de la forme <math>- 2^x</math> a tous ses bits à 1, sauf les x bits de poids faible qui sont à 0.
{|class="wikitable"
|+ Exemple avec des nombres de 8 bits
|-
! -1
| 1111 1111
|-
! -2
| 1111 1110
|-
! -4
| 1111 1100
|-
! -8
| 1111 1000
|-
! -16
| 1111 0000
|-
! -32
| 1110 0000
|-
! -64
| 1100 0000
|-
! -128
| 1000 0000
|-
! 0 / - 256
| 0000 0000
|}
====Le calcul du complément à deux : seconde méthode====
Il existe une seconde méthode pour calculer le complément à deux d'un nombre, la voici. Pour les nombres positifs, encodez-les comme en binaire normale. Pour les nombres négatifs, faites pareil, puis inversez tous les bits, avant d'ajouter un. La procédure est identique à celle du complément à un, sauf que l'on incrémente le résultat final. Et ce n'est pas une coïncidence, comme nous allons le voir immédiatement.
Pour comprendre pourquoi la méthode marche, repartons de la formule précédente <math>K = 2^n - N</math>. Elle peut se reformuler comme suit :
: <math>K = (2^n - 1 + 1) - N</math>
: <math>K = (2^n - 1) - N + 1</math>
La valeur <math>(2^n - 1)</math> est par définition un nombre dont tous les bits sont à 1. À cette valeur, on soustrait un nombre <math>|N|</math> dont certains bits sont à 1 et d'autres à 0. En clair, pour chaque colonne, on a deux possibilités : soit on doit faire la soustraction <math>1-1</math>, soit la soustraction <math>1-0</math>. Or, les règles de l'arithmétique binaire disent que <math>1-1=0</math> et <math>1-0=1</math>. En regardant attentivement, on se rend compte que le bit du résultat est l'inverse du bit de départ. De plus, les deux cas ne donnent pas de retenue : le calcul pour chaque bit n'influence pas les bits voisins.
Le terme <math>(2^n - 1) - N</math> est donc le complément à un du nombre N, un nombre égal à sa représentation en binaire dont tous les bits sont inversés. Notons <math>\overline{|N|}</math> le nombre formé en inversant tous les bits de N. On a alors :
: <math>K = \overline{N} + 1</math>
En clair, le complément à deux s'obtient en prenant le complément à un et en ajoutant 1. Dit autrement, il faut prendre le nombre N, en inverser tous les bits et ajouter 1.
Une autre manière équivalente consiste à faire le calcul suivant :
: <math>K = \overline{N - 1}</math>
On prend le nombre dont on veut le complément, on soustrait 1 et on inverse les bits.
: Notons que tout ce qui a été dit plus haut marche aussi pour le complément à un, avec cependant une petite différence : la valeur haute de débordement n'est pas la même, ce qui change les calculs. Pour des nombres codés sur <math>n</math> bits, la valeur haute de débordement est égale à <math>2^n</math> en complément à deux, alors qu'elle est de <math>2^n - 1</math> en complément à un. De ce fait, la gestion des débordements est plus simple en complément à deux.
====L'extension de signe====
Dans les ordinateurs, tous les nombres sont codés sur un nombre fixé et constant de bits. Ainsi, les circuits d'un ordinateur ne peuvent manipuler que des nombres de 4, 8, 12, 16, 32, 48, 64 bits, suivant la machine. Si l'on veut utiliser un entier codé sur 16 bits et que l'ordinateur ne peut manipuler que des nombres de 32 bits, il faut bien trouver un moyen de convertir le nombre de 16 bits en un nombre de 32 bits, sans changer sa valeur et en conservant son signe. Cette conversion d'un entier en un entier plus grand, qui conserve valeur et signe s'appelle l''''extension de signe'''.
L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
L'explication plus simple tient dans la manière de coder le bit de poids fort. Prenons l'exemple de la conversion d'un entier de 5 bits en un entier de 8bits. Les 4 bits de poids faible ont un poids positif (on les additionne), alors que le bit de poids fort a un poids négatif. Le nombre encodé vaut : <math>\text{Bit de poids fort} \times -16 + \text{Valeur encodée sur les 4 bits de poids faible}</math>. La valeur encodée sur 4 bits reste la même après extension de signe, car les poids des bits de poids faible ne changent pas. Par contre, le bit de poids fort change. Sur 8 bits, la valeur -16 est encodée par un nombre de la forme 1111 0000. En remplaçant le bit de poids fort par sa valeur calculée sur plus de bits, on remarque que les bits de poids fort ont été remplacés par des 1.
===La représentation négabinaire===
Enfin, il existe une dernière méthode, assez simple à comprendre, appelée '''représentation négabinaire'''. Dans cette méthode, les nombres sont codés non en base 2, mais en base -2. Oui, vous avez bien lu : la base est un nombre négatif. Dans les faits, la base -2 est similaire à la base 2 : il y a toujours deux chiffres (0 et 1), et la position dans un chiffre indique toujours par quelle puissance de 2 il faut multiplier, sauf qu'il faudra ajouter un signe moins une fois sur 2. Concrètement, les puissances de -2 sont les suivantes : 1, -2, 4, -8, 16, -32, 64, etc. En effet, un nombre négatif multiplié par un nombre négatif donne un nombre positif, ce qui fait qu'une puissance sur deux est négative, alors que les autres sont positives. Ainsi, on peut représenter des nombres négatifs, mais aussi des nombres positifs dans une puissance négative.
Par exemple, la valeur du nombre noté 11011 en base -2 s'obtient comme suit :
{|class="wikitable"
|-
!…
!-32
!16
!-8
!4
!-2
!1
|-
|…
|1
|1
|1
|0
|1
|1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(−8×1)+(4×0)+(−2×1)+(1×1)=−32+16−8−2+1=−25.
==Les nombres à virgule fixe==
On sait donc comment sont stockés nos nombres entiers dans un ordinateur. Néanmoins, les nombres entiers ne sont pas les seuls nombres que l'on utilise au quotidien : il nous arrive d'en utiliser à virgule. Notre ordinateur n'est pas en reste : il est lui aussi capable de manipuler de tels nombres. Dans les grandes lignes, il peut utiliser deux méthodes pour coder des nombres à virgule en binaire : La virgule fixe et la virgule flottante. Parlons d'abord de la virgule fixe, plus simple à comprendre.
===La virgule fixe : des entiers cachés===
La méthode de la virgule fixe consiste à émuler les nombres à virgule à partir de nombres entiers. Un nombre à virgule fixe est codé par un nombre entier proportionnel au nombre à virgule fixe. Pour obtenir la valeur de notre nombre à virgule fixe, il suffit de diviser l'entier servant à le représenter par le facteur de proportionnalité. Par exemple, pour coder 1,23 en virgule fixe, on peut choisir comme « facteur de conversion » 1000, ce qui donne l'entier 1230.
Généralement, les informaticiens utilisent une puissance de deux comme facteur de conversion, pour simplifier les calculs. En faisant cela, on peut écrire les nombres en binaire et les traduire en décimal facilement. Pour l'exemple, cela permet d'écrire des nombres à virgule en binaire comme ceci : 1011101,1011001. Et ces nombres peuvent se traduire en décimal avec la même méthode que des nombres entier, modulo une petite différence. Comme pour les chiffres situés à gauche de la virgule, chaque bit situé à droite de la virgule doit être multiplié par la puissance de deux adéquate. La différence, c'est que les chiffres situés à droite de la virgule sont multipliés par une puissance négative de deux, c'est à dire par <math>1 \over 2</math>, <math>1 \over 4</math>, <math>1 \over 8</math>, <math>1 \over 16</math>, <math>1 \over 32</math>, ...
[[File:Fixed-point binary representation.svg|centre|vignette|upright=2.5|Nombres à virgule fixe.]]
Cette méthode est assez peu utilisée de nos jours, quoiqu'elle puisse avoir quelques rares applications relativement connue. Un bon exemple est celui des banques : les sommes d'argent déposées sur les comptes ou transférées sont codés en virgule fixe. Les sommes manipulées par les ordinateurs ne sont pas exprimées en euros, mais en centimes d'euros. Et c'est une forme de codage en virgule fixe dont le facteur de conversion est égal à 100. La raison de ce choix est que les autres méthodes de codage des nombres à virgule peuvent donner des résultats imprécis : il se peut que les résultats doivent être tronqués ou arrondis, suivant les opérandes. Cela n'arrive jamais en virgule fixe, du moins quand on se limite aux additions et soustractions.
===Les nombres à virgule non-représentables en binaire===
Quel que soit la façon de représenter les nombres à virgules, il existe des nombres qui ne peuvent être représentés de manière exacte, à savoir avec un nombre fini de décimales après la virgule. En soi, ce n'est pas spécifique au binaire, on a la même chose en décimal. Par exemple, la fraction 1/3 en décimal s'écrit 0.3333333..., avec une infinité de 3. La même chose existe en binaire, mais pour des nombres différents.
Déjà, évacuons le cas des nombres irrationnels, à savoir les nombres qui ne peuvent pas s'écrire sous la forme d'une fraction, comme <math>pi</math> ou <math>e</math>. Ils ont une infinité de décimales que ce soit en binaire, en décimal, en hexadécimal, ou autre. Ils ne sont pas représentables avec un nombre fini de décimales quelle que soit la base utilisée. Concentrons-nous sur des nombres qui ne sont pas dans ce cas, et qui ont un nombre fini ou infini de décimales.
Le passage de la base 10 à la base 2 change le nombre de décimales, et peut faire passer d'un nombre fini de décimales à un nombre infini. Par exemple, <math>\frac{1}{5} = 0.2</math> est représenté en binaire avec une séquence infinie de bits : <math>0.001100110011...</math>. Les nombres étant en base binaire et représenté avec un nombre limité de bits, il existe certains nombres décimaux triviaux qui ne sont pas représentables avec un nombre fini de décimales.
Et la réciproque n'est pas vraie : tout nombre binaire avec un nombre fini de décimales en binaire est représentable avec un nombre fini de décimales en base 10. Ceci est lié à la décomposition en facteur premier des bases utilisées :
* <math>2 = 2^1</math>
* <math>10 = 2^1 \times 5^1</math>
Le nombre 10 possède tous les facteurs premiers de 2, mais 2 n'a pas de 5 dans sa décomposition.
==Les nombres flottants==
Les nombres à virgule fixe ont aujourd'hui été remplacés par les '''nombres à virgule flottante''', où le nombre de chiffres après la virgule est variable. Le codage d'un nombre flottant est basée sur son ''écriture scientifique''. Pour rappel, en décimal, l’écriture scientifique d'un nombre consiste à écrire celui-ci comme un produit entre un nombre <math>a</math> et une puissance de 10, avec <math>a</math> étant compris entre 1 et 9. Ce qui donne :
: <math>a \times 10^e</math>, avec <math>a \in [1, 10 [</math>
Le nombre <math>a</math> est appelé le ''significande'' et il est compris entre 1 (inclus) et 10 (exclu). Cette contrainte garantit que l'écriture scientifique d'un nombre est unique, qu'il n'y a qu'une seule façon d'écrire un nombre en notation scientifique. Pour cela, on impose le nombre de chiffre à gauche de la virgule et le plus simple est que celui-ci soit égal à 1. Mais il faut aussi que celui-ci ne soit pas nul. En effet, si on autorise de mettre un 0 à gauche de la virgule, il y a plusieurs manières équivalentes d'écrire un nombre. Ces deux contraintes font que le significande doit être égal ou plus grand que 1, mais strictement inférieur à 10. Par contre, on peut mettre autant de décimales que l'on veut.
En binaire, c'est la même chose, mais avec une puissance de deux. Cela implique de modifier la puissance utilisée : au lieu d'utiliser une puissance de 10, on utilise une puissance de 2.
: <math>a \times 2^e</math>, avec <math>a \in [1, 2 [</math>
Le significande est aussi altéré, au même titre que la puissance, même si les contraintes sont similaires à celles en base 10. En effet, le nombre <math>a</math> ne possède toujours qu'un seul chiffre à gauche de la virgule, comme en base 10. Vu que seuls deux chiffres sont possibles (0 et 1) en binaire, on s'attend à ce que le chiffre situé à gauche de la virgule soit un zéro ou un 1. Mais rappelons que le chiffre à gauche doit être non-nul, pour les mêmes raisons qu'en décimal. En clair, le significande a forcément un bit à 1 à gauche de la virgule. Pour récapituler, l'écriture scientifique binaire d'un nombre consiste à écrire celui-ci sous la forme :
: <math>1,m \times 2^e</math>, avec <math>m \in [0, 1 [</math>
La partie fractionnaire du nombre <math>a</math>, qu'on appelle la '''mantisse'''.
[[File:Mantissa.svg|centre|vignette|Écriture scientifique (anglais).]]
Pour déterminer l'écriture scientifique en binaire d'un nombre quelconque, la procédure dépend de la valeur du nombre en question. Tout dépend s'il est dans l'intervalle <math>[1, 2 [</math>, au-delà de 2 ou en-dessous de 1.
* Pour un nombre entre 1 (inclus) et 2 (exclu), il suffit de le traduire en binaire. Son exposant est 0.
* Pour un nombre au-delà de 2, il faut le diviser par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant est alors le nombre de fois qu'il a fallu diviser par 2.
* Pour un nombre plus petit que 1, il faut le multiplier par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant se calcule en prenant le nombre de fois qu'il a fallu multiplier par 2, et en prenant l'opposé (en mettant un signe - devant le résultat).
===Le codage des nombres flottants et la norme IEEE 754===
La '''virgule flottante''' encode l'écriture scientifique d'un nombre/ Elle utilise pour cela trois données : la mantisse, l'exposant et un bit de signe. Les trois sont concaténés dans un seul "nombre", plus précisément une suite de bits qui est découpée en un bit de signe, un entier qui encode l'exposant, et un entier pour la mantisse.
[[File:General floating point frac.svg|centre|vignette|upright=2|Format Général d'un nombre flottant.]]
[[File:General floating point.svg|centre|vignette|upright=2|Format Général d'un nombre flottant, plus détaillé.]]
Voici à peu-près comment les nombres flottants sont répartis sur une ligne numérique. Vous remarquerez que les flottants sont de plus en plus éloignés au fur et à mesure qu'on s'éloigne de zéro. La raison est l'encodage avec un exposant et une mantisse. L'écart entre deux flottants reste stable pour un même exposant, mais il y a une cassure quand on passe d'un exposant au suivant.
[[File:FloatingPointPrecisionAugmented.png|centre|vignette|upright=3|Floating Point Precision.]]
Le standard actuel pour la virgule flottante est la '''norme IEEE 754'''. Cette norme va (entre autres) définir quatre types de flottants différents, qui utilisent des exposants/mantisses de taille différente. Les deux formats les plus courants sont les flottants '''simple précision''' et '''double précision'''. Les premiers sont codés sur 32 bits, les seconds sur 64 bits. Il y a aussi des formats de '''précision étendus''', codés sur 80 bits ou plus.
[[File:Single double extended2.gif|centre|vignette|upright=2.5|Formats des nombres flottants IEEE754.]]
===L'encodage de la mantisse et de l'exposant===
L'exposant peut être aussi bien positif que négatif (pour permettre de coder des nombres très petits). Pour cela, il est encodé en représentation par excès. Pour un exposant de n bits, le biais est égal à <math>2^{n-1} - 1</math>.
L'encodage de la mantisse a lui aussi ses subtilités. En effet, la mantisse est un nombre à virgule, avec une partie entière et une partie fractionnaire. Le tout est encodé en virgule fixe, reste à choisir son encodage exact. Pour simplifier l'encodage, l'exposant est choisir de manière à ce qu'il y ait un seul chiffre avant la virgule, ce qui permet de coder la partie entière sur 1 bit. En clair, il faut encoder le bit à gauche de la virgule, puis ceux à droite. Les anciens PC utilisaient cette solution, du temps de la FPU x87. Ils géraient nativement des nombres flottants de 80 bits, appelés '''flottants étendus x87'''. Ils utilisaient une mantisse codée sur 64 bits, dont un bit pour la partie entière, et 63 pour la partie fractionnaire.
[[File:X86 Extended Floating Point Format.svg|centre|vignette|upright=2|Flottants étendus des anciens CPU X86.]]
Une simplification plus importante choisir l'exposant de manière à ce que la partie entière vaut 1, systématiquement. En faisant cela, on n'a plus à encoder la partie entière, pas besoin de lui réserver un bit. La mantisse encode donc uniquement la partie fractionnaire, la partie entière n'est pas encodée. Une telle méthode est donc plus efficace. A quelques détails près : de nombreuses valeurs ne peuvent pas être encodées facilement. Par exemple, le zéro ne peut pas être encodé avec cette technique. Et c'est là un des gros défaut de cette technique : ne pas pouvoir encoder le zéro est un énorme problème !
Pour éviter ce défaut, les nombres flottants IEE754 utilisent une variante de la technique précédente. Les flottants IEE754 ont une partie entière de la mantisse qui vaut 1, sauf pour quelques rares exceptions. Quelques nombres flottants spécialisés ne sont pas codés en respectant les règles pour le significande et ont un 0 à gauche de la virgule. Ils sont appelés les '''dénormaux''', aussi nommés flottants dénormalisés. Les dénormaux sont des nombres flottants où l'exposant est le plus petit possible. Le '''zéro''' est un dénormal particulier dont la mantisse est nulle. Au fait, remarquez que le zéro est codé deux fois à cause du bit de signe : on se retrouve avec un -0 et un +0.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur minimale (0 en binaire) || Mantisse différente de zéro (dénormal) ou égale à zéro (zéro)
|}
Toujours est-il que le bit à gauche de la virgule n'est pas codé, que ce soit pour les flottants normaux ou les fameux dénormaux qui font exception. On verra que ce bit peut se déduire en fonction de l'exposant utilisé pour encoder le nombre à virgule, ce qui lui vaut le nom de '''bit implicite'''.
===Les flottants spéciaux : infinis et NaN===
Fait étrange, la norme IEEE754 permet de représenter '''l'infini''', aussi bien en positif qu'en négatif. Celui-ci est codé en mettant l'exposant à sa valeur maximale et la mantisse à zéro. Et le pire, c'est qu'on peut effectuer des calculs sur ces flottants infinis. Mais cela a peu d'utilité.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse égale à zéro
|}
Mais malheureusement, l'invention des flottants infinis n'a pas réglé tous les problèmes. Par exemple, quel est le résultat de <math>\infty - \infty</math> ? Ou encore <math>0 - \infty</math> ? Autant prévenir tout de suite : mathématiquement, on ne peut pas savoir quel est le résultat de ces opérations. Pour pouvoir résoudre ces calculs, il a fallu inventer un nombre flottant qui signifie « je ne sais pas quel est le résultat de ton calcul pourri ». Ce nombre, c'est '''NaN'''. NaN est l'abréviation de ''Not A Number'', ce qui signifie : n'est pas un nombre. Ce NaN a un exposant dont la valeur est maximale, mais une mantisse différente de zéro. Pour être plus précis, il existe différents types de NaN, qui diffèrent par la valeur de leur mantisse, ainsi que par les effets qu'ils peuvent avoir. Malgré son nom explicite, on peut faire des opérations avec NaN, mais cela ne sert pas vraiment à grand chose : une opération arithmétique appliquée avec un NaN aura un résultat toujours égal à NaN.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse différente de zéro
|}
===Les arrondis et exceptions===
La norme impose aussi une gestion des arrondis ou erreurs, qui arrivent lors de calculs particuliers. En voici la liste :
{|class="wikitable"
|-
!Nom de l’exception
!Description
|-
!Invalid operation
|Opération qui produit un NAN. Elle est levée dans le cas de calculs ayant un résultat qui est un nombre complexe, ou quand le calcul est une forme indéterminée. Pour ceux qui ne savent pas ce que sont les formes indéterminées, voici en exclusivité la liste des calculs qui retournent NaN : <math>\infty - \infty</math>, <math>0 \times \infty</math>, <math>0^0</math>, <math>1^{\infty}</math>, <math>\infty^0</math>.
|-
!Overflow
|Résultat trop grand pour être stocké dans un flottant. Le plus souvent, on traite l'erreur en arrondissant le résultat en vue de la taille de la mantisse;
|-
!Underflow
|Pareil que le précédent, mais avec un résultat trop petit. Le plus souvent, on traite l'erreur en arrondissant le résultat vers 0.
|-
!Division par zéro
|Le nom parle de lui-même. La réponse la plus courante est de répondre + ou - l'infini.
|-
!'''Inexact'''
|Le résultat ne peut être représenté par un flottant et on doit l'arrondir.
|}
La gestion des arrondis pose souvent problème. Pour donner un exemple, on va prendre le nombre 0,1. En binaire, ce nombre s'écrit comme ceci : 0,1100110011001100... et ainsi de suite jusqu'à l'infini. Notre nombre utilise une infinité de décimales. Bien évidemment, on ne peut pas utiliser une infinité de bits pour stocker notre nombre et on doit impérativement l'arrondir. Comme vous le voyez avec la dernière exception, le codage des nombres flottants peut parfois poser problème : dans un ordinateur, il se peut qu'une opération sur deux nombres flottants donne un résultat qui ne peut être codé par un flottant. On est alors obligé d'arrondir ou de tronquer le résultat de façon à le faire rentrer dans un flottant. Pour éviter que des ordinateurs différents utilisent des méthodes d'arrondis différentes, on a décidé de normaliser les calculs sur les nombres flottants et les méthodes d'arrondis. Pour cela, la norme impose le support de quatre modes d'arrondis :
* Arrondir vers + l'infini ;
* vers - l'infini ;
* vers zéro ;
* vers le nombre flottant le plus proche.
===Les nombres flottants logarithmiques===
Les '''nombres flottants logarithmiques''' sont des nombres flottants composés d'un bit de signe et d'un exposant, sans mantisse. La mantisse est totalement implicite : tous les flottants logarithmiques ont la même mantisse, qui vaut 1. Il ne reste que l'exposant, qui est tout simplement le logarithme en base 2 du nombre encodé, d'où le nom de codage flottant logarithmique donné à cette méthode. Attention toutefois : l'exposant est ici un nombre fractionnaire, codé en virgule fixe. Le choix d'un exposant fractionnaire permet de représenter pas mal de nombres de taille diverses.
{|class="wikitable"
|-
!
! Bit de signe
! Exposant
|-
! Représentation binaire
| 0
| 01110010101111
|-
! Représentation décimale
| +
| 1040,13245464
|}
L'utilité de cette représentation est de simplifier certains calculs, comme les multiplications, divisions, puissances, etc. En effet, les mathématiques nous disent que le logarithme d'un produit est égal à la somme des logarithmes : <math>\ln{(a \times b)} = \ln{a} + \ln{b}</math>. Or, il se trouve que les ordinateurs sont plus rapides pour faire des additions/soustractions que pour faire des multiplications/divisions. Donc, la représentation logarithmique permet de remplacer les multiplications/divisions par des additions/soustractions, plus simples et plus rapides pour l'ordinateur.
Évidemment, les applications des flottants logarithmiques sont rares, limitées à quelques situations bien précises (traitement d'image, calcul scientifique spécifiques).
==Les encodages binaires alternatifs==
Outre le binaire que nous venons de voir, il existe d'autres manières de coder des nombres avec des bits. Et nous allons les aborder dans cette section. Parmi celle-ci, nous parlerons du code Gray, de la représentation ''one hot'', du unaire et du ternaire. Nous en parlons car elles seront utiles dans la suite du cours, bien que de manière assez limitée. Autant nous passerons notre temps à parler du binaire normal, autant les représentations que nous allons voir sont aujourd'hui utilisées dans des cas assez spécifiques. Et elles sont plus courantes que vous ne le pensez.
===Le code Gray===
Le '''code Gray''' est un encodage binaire qui a une particularité très intéressante : deux nombres consécutifs n'ont qu'un seul bit de différence. Pour exemple, voici ce que donne le codage des 8 premiers entiers sur 3 bits :
{|class="wikitable"
! Décimal
! Binaire naturel
! Codage Gray
|-
|0||000||000
|-
|1||001||001
|-
|2||010||011
|-
|3||011||010
|-
|4||100||110
|-
|5||101||111
|-
|6||110||101
|-
|7||111||100
|}
Les utilisations du code Gray sont assez nombreuses, bien qu'on n'en croise pas tous les jours. Un exemple : le code Gray est très utile dans certains circuits appelés les compteurs, qui mémorisent un nombre et l'incrémentent (+1) ou le décrémentent (-1) suivant les besoins de l'utilisateur. Il est aussi utilisé dans des scénarios difficiles à expliquer ici (des codes correcteurs d'erreur ou des histoires de passages de domaines d'horloge). Mais le point important est que ce code sera absolument nécessaire dans quelques chapitres, quand nous parlerons des tables de Karnaugh, un concept important pour la conception de circuits électroniques. Ne passez pas à côté de cette section.
Pour construire ce code Gray, on peut procéder en suivant plusieurs méthodes, les deux plus connues étant la méthode du miroir et la méthode de l'inversion.
[[File:Binary-reflected Gray code construction.svg|vignette|Construction d'un code gray par la méthode du miroir.]]
La méthode du miroir est relativement simple. Pour connaître le code Gray des nombres codés sur n bits, il faut :
* partir du code Gray sur n-1 bits ;
* symétriser verticalement les nombres déjà obtenus (comme une réflexion dans un miroir) ;
* rajouter un 0 au début des anciens nombres, et un 1 au début des nouveaux nombres.
Il suffit de connaître le code Gray sur 1 bit pour appliquer la méthode : 0 est codé par le bit 0 et 1 par le bit 1.
Une autre méthode pour construire la suite des nombres en code Gray sur n bits est la méthode de l'inversion. Celle-ci permet de connaître le codage du nombre n à partir du codage du nombre n-1, comme la méthode du dessus. On part du nombre 0, systématiquement codé avec uniquement des zéros. Par la suite, on décide quel est le bit à inverser pour obtenir le nombre suivant, avec la règle suivante :
* si le nombre de 1 est pair, il faut inverser le dernier chiffre.
* si le nombre de 1 est impair, il faut localiser le 1 le plus à droite et inverser le chiffre situé à sa gauche.
Pour vous entraîner, essayez par vous-même avec 2, 3, voire 5.
===Les représentations ''one-hot'' et unaire===
La représentation ''one-hot'' et la représentation unaire sont deux représentations assez liées, mais légèrement différentes.
La '''représentation unaire''' est la représentation en base 1. Avec elle, le zéro est codé en mettant tous les bits du nombre à zéro, la valeur 1 est encodée avec la valeur 000...0001, le deux avec 000...0011, le trois avec 000...0111, etc. Pour résumer, le nombre N est encodé en mettant les N bits de poids faible à 1, à l'exception du zéro qui est encodé...par un zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! Unaire
|-
| 0 || 000 || 0000 0000
|-
| 1 || 001 || 0000 0001
|-
| 2 || 010 || 0000 0011
|-
| 3 || 011 || 0000 0111
|-
| 4 || 100 || 0000 1111
|-
| 5 || 101 || 0001 1111
|-
| 6 || 110 || 0011 1111
|-
| 7 || 111 || 0111 1111
|-
| 8 || 1000 || 1111 1111
|}
Pour convertir un nombre codé en unaire vers le binaire, il suffit de compter le nombre de 1. C'est ni plus ni moins ce que fait l'opération dite de ''population count'' dont nous avons parlé plus haut. La représentation unaire permet d'encoder N+1 valeurs sur N bits, car le zéro est encodé à part. Une valeur pour le zéro, plus une par bit.
Avec la '''représentation ''one-hot''''', un nombre est codé sur N bits, dont un seul bit est à 1. La position du 1 dans le nombre indique sa valeur. Précisément, la valeur encodée est égale au poids du bit à 1. En faisant cela, le zéro est codé en mettant le bit de poids faible à 1, la valeur 1 est codée avec les bits 00...00010, la valeur 2 est codée par les bits 00...000100, etc.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Elle permet d'encoder N valeurs différentes avec N bits, zéro inclus. On perd une valeur par rapport à l'unaire, car le zéro n'et pas encodé à part du reste. La traduction du binaire vers le ''one-hot'' est réalisé par un circuit nommé décodeur, que nous verrons d'ici quelques chapitres. De même, la traduction d'un nombre encodé en ''one-hot'' vers le binaire est réalisée par un circuit électronique appelé l'encodeur, qui est en quelque sorte l'inverse de l'encodeur. Les deux circuits sont très utilisés en électronique, ce sont des circuits de base, que nous n'aurons de cesse d'utiliser pour fabriquer d'autres circuits.
L'utilité de ces deux représentations n'est pas évidente. Mais sachez qu'elle le deviendra quand nous parlerons des circuits appelés les "compteurs", tout comme ce sera le cas pour le code Gray. Elles sont très utilisées dans des circuits appelés des machines à état, qui doivent incorporer des circuits compteurs efficients. Et ces représentations permettent d'avoir des circuits pour compter qui sont très simples, efficaces, rapides et économes en circuits électroniques. La représentation unaire sera aussi utile à la toute fin du cours, dans les chapitres liés à l'exécution dans le désordre. Il en sera fait référence quand nous parlerons de fenêtres d'instruction, d'émission dans l'ordre, de ''scoreboarding'', etc.
Il faut aussi noter qu'il a existé des ordinateur qui manipulaient des nombres codés en unaire. Enfin presque, la représentation utilisée était proche de l'unaire, mais ressemblait fortement. De tels ordinateurs étaient appelés des ''ordinateurs stochastiques'' et nous les aborderons à la toute fin de ce wikilivre, dans un chapitre annexe portant sur les ordinateurs ternaires (qui comptent en base 3) et unaires.
==Les encodages hybrides entre décimal et binaire==
Pour terminer, nous allons voir les représentations qui mixent binaire et décimal. Il s'agit en réalité d'encodage qui permettent de manipuler des nombres codés en décimal, mais les chiffres sont codés en utilisant des bits. L'encodage ''Binary Coded Decimal'' est le plus connu de cette catégorie, mais il y en a quelques autres, qui sont moins connus. De telles représentations étaient très utilisées au début de l'informatique, mais sont aujourd'hui tombées en désuétude. Pour désigner ces encodages, nous parlerons d''''encodages bit-décimaux''' : décimaux pour préciser qu'ils encodent des nombres codés en décimal, bit pour préciser que les chiffres sont codés avec des bits.
De tels encodages étaient utilisés sur les tous premiers ordinateurs pour faciliter le travail des programmeurs, mais aussi sur les premières calculettes. Ils sont utiles dans les applications où on doit manipuler chaque chiffre décimal séparément des autres. Un exemple classique est celui d'une horloge digitale. Ils ont aussi l'avantage de bien se marier avec les nombres à virgule. Les représentations des nombres à virgule fixe ou flottante ont le défaut que des arrondis peuvent survenir. Par exemple, la valeur 0,2 est codée comme suit en binaire normal : 0.00110011001100... et ainsi de suite jusqu’à l'infini. Avec les encodages bit-décimaux, on n'a pas ce problème, 0,2 étant codé 0000 , 0010.
Il a existé des ordinateurs qui travaillaient uniquement avec de tels encodages, appelés des '''ordinateurs décimaux'''. Ils étaient assez courants entre les années 60 et 70, même s'ils ne représentent pas la majorité des architectures de l'époque. Avec eux, la mémoire n'était pas organisée en octets, mais elle stockait des chiffres décimaux codés sur 5-6 bits. Le processeur faisait des calculs sur des chiffres bit-décimaux directement.
Leur grand avantage est leur très bonne performance dans les taches de bureautique, de comptabilité, et autres. Les processeurs de l'époque recevaient des entiers codés en bit-décimal de la part des entrées-sorties, et devaient les traiter. Les processeurs binaires devaient faire des conversions décimal-binaire pour communiquer avec les entrées-sorties, mais pas les processeurs décimaux. Le gain en performance pouvait être substantiel dans certaines applications.
Les ordinateurs décimaux se classent en deux sous-types bien précis. Les premiers gèrent des entiers qui ont un nombre de chiffres fixes. Par exemple, l'IBM 7070 gérait des entiers de 10 chiffres, plus un signe +/- pour les entiers signés. Le processeur faisait les calculs directement en bit-décimal, il gérait des entiers faisant environ 10-15 chiffres décimaux et savait faire des calculs avec de tels nombres. Le second sous-type effectue les calculs chiffre par chiffre et géraient des nombres de taille variable, sans limite de chiffres ! Nous reparlerons de ces derniers dans un chapitre ultérieur, quand nous parlerons de la différence entre byte et mot. Pour le moment, ne gardez à l'esprit que les processeurs gérant un nombre fixe de chiffres décimaux, plus simples à comprendre.
===Le ''Binary Coded Decimal''===
Le '''''Binary Coded Decimal''''', abrévié BCD, est une représentation qui mixe binaire et décimal. Avec cette représentation, les nombres sont écrits en décimal, comme nous en avons l'habitude dans la vie courante, sauf que chaque chiffre décimal est directement traduit en binaire sur 4 bits. Prenons l'exemple du nombre 624 : le 6, le 2 et le 4 sont codés en binaire séparément, ce qui donne 0110 0010 0100.
{| class="wikitable"
|+ Codage BCD
|-
! Nombre encodé (décimal)
! scope="col" colspan="4" | BCD
|-
| 0 || 0 || 0 || 0 || 0
|-
| 1 || 0 || 0 || 0 || 1
|-
| 2 || 0 || 0 || 1 || 0
|-
| 3 || 0 || 0 || 1 || 1
|-
| 4 || 0 || 1 || 0 || 0
|-
| 5 || 0 || 1 || 0 || 1
|-
| 6 || 0 || 1 || 1 || 0
|-
| 7 || 0 || 1 || 1 || 1
|-
| 8 || 1 || 0 || 0 || 0
|-
| 9 || 1 || 0 || 0 || 1
|}
On peut remarquer que 4 bits permettent de coder 16 valeurs, là où il n'y a que 10 chiffres. Dans le BCD proprement dit, les combinaisons de bits qui correspondent à 10, 11, 12, 13, 14 ou 15 ne sont tout simplement pas prises en compte. Sur quelques ordinateurs, ces combinaisons codent des chiffres décimaux en double : certains chiffres pouvaient être codés de deux manières différentes. Il est aussi possible d'utiliser ces valeurs pour coder quelque chose d'autre que des chiffres. Par exemple, il est possible de les utiliser pour coder un signe + ou -, afin de gérer les entiers relatifs. Une autre possibilité, complémentaire de la précédente, utilise ces valeurs en trop pour coder une virgule, afin de gérer les nombres non-entiers. Les possibilités sont nombreuses.
Le support du BCD implique souvent que le processeur supporte des '''opérations BCD''', à savoir des opérations capables de travailler sur des opérandes en BCD et de donner un résultat en BCD. Il faut bien faire la différence entre les opérations en binaire et les opérations en BCD. Par exemple, on n'effectue pas une addition de la même manière en binaire et en décimal/BCD, même si les grandes lignes sont presque identiques. Les différences font que les processeurs doivent avoir des opérations différentes pour les deux encodages, de la même manière que les processeurs gèrent les opérations sur les flottants et les entiers séparément.
Le support du codage BCD est abandonné de nos jours, c'est surtout quelque chose qu'on trouve sur les anciens processeurs. Les architectures 8 et 16 bits supportaient à la fois des opérations binaires et des opérations BCD. Mais il a aussi existé une méthode intermédiaire, qui utilisait des additions binaires normales sur des opérandes BCD. L'idée est que le résultat de l'addition est incorrect, car les valeurs 10 à 15 peuvent apparaître comme chiffre dans le résultat. Mais on peut le corriger pour obtenir le résultat exact en BCD. Pour résumé, les processeurs faisaient des additions en binaire, et corrigeaient le résultat avec une opération spécifique pour obtenir un résultat en BCD. Nous en reparlerons dans le chapitre sur le langage machine et l'assembleur, dans lequel nous étudierons les différentes opérations que supporte un processeur.
===Les encodages compacts du BCD===
Des variantes du BCD visent à réduire le nombre de bits utilisés pour encoder un nombre décimal. Nous allons les appeler les '''encodages BCD compacts'''. Avec certaines variantes, on peut utiliser seulement 7 bits pour coder deux chiffres décimaux, au lieu de 8 bits en BCD normal. Idem pour les nombres à 3 chiffres décimaux, qui prennent 10 bits au lieu de 12. Cette économie est réalisée par une variante du BCD assez compliquée, appelée l''''encodage Chen-Ho'''. Une alternative, appelée le ''Densely Packed Decimal'', arrive à une compression identique, mais avec quelques avantages au niveau de l'encodage. Ces encodages sont cependant assez compliqués à expliquer, surtout à ce niveau du cours, aussi je me contente de simplement mentionner leur existence.
Les encodages BCD compacts sont utilisés par les programmeurs pour stocker des données dans un fichier, en mémoire, mais guère plus. Ils ne sont pas gérés par le processeur directement, on ne peut pas faire de calculs avec, le processeur ne gére pas d'opération BCD supportant de tels encodages. C'est théoriquement possible, mais ça n'a jamais été implémenté dans un processeur, le cout en circuit n'en valait pas la chandelle. Pour faire des calculs sur des nombres en BCD compact, on doit les décompresser et les convertir en BCD normal ou en binaire, puis faire les calculs avec des opérations BCD/binaire usuelles.
===L'encodage ''Excess-3''===
La représentation '''''Excess-3''''' (XS-3) est une variante du BCD, qui a autrefois était utilisée sur d'anciens ordinateurs décimaux. Il s'agit d'une sorte d'hybride entre une représentation par excès et BCD. Chaque chiffre décimal est codé sur plusieurs bits en utilisant une représentation binaire par excès. Le biais est de 3 pour la représentation XS-3, il en existe des variantes avec un excès de 4, 5, mais elles sont moins utilisées. Avec elle, la conversion d'un chiffre décimal se fait comme suit : on prend le chiffre décimal, on ajoute 3, puis on traduit en binaire.
{| class="wikitable"
|+ Codage en ''Excess-3''
|-
! Décimal
! scope="col" colspan="4" | Binaire
|-
| -3 || 0 || 0 || 0 || 0
|-
| -2 || 0 || 0 || 0 || 1
|-
| -1 || 0 || 0 || 1 || 0
|-
| 0 || 0 || 0 || 1 || 1
|-
| 1 || 0 || 1 || 0 || 0
|-
| 2 || 0 || 1 || 0 || 1
|-
| 3 || 0 || 1 || 1 || 0
|-
| 4 || 0 || 1 || 1 || 1
|-
| 5 || 1 || 0 || 0 || 0
|-
| 6 || 1 || 0 || 0 || 1
|-
| 7 || 1 || 0 || 1 || 0
|-
| 8 || 1 || 0 || 1 || 1
|-
| 9 || 1 || 1 || 0 || 0
|-
| 10 || 1 || 1 || 0 || 1
|-
| 11 || 1 || 1 || 1 || 0
|-
| 12 || 1 || 1 || 1 || 1
|}
L'avantage de cette représentation est que l'on peut facilement calculer les soustractions en utilisant une méthode bien précise (celle du complément à 10, je ne détaille pas plus). Le défaut est que le calcul des additions est légèrement plus complexe.
L'XS-3 a été utilisé sur quelques ordinateurs décimaux assez anciens, notamment sur l'UNIVAC I et II.
===Les codes bi-quinaires===
Les '''codes bi-quinaires''' utilisent un mélange entre encodage binaire, encodage ''one-hot'' et encodage décimal. L'idée de base derrière ces codes est d'utiliser la représentation ''one-hot'' pour coder un chiffre BCD. Le problème est que cela demande d'utiliser 10 bits pour encoder un chiffre BCD, ce qui est beaucoup. L'encodage bi-quinaire optimise le tout de manière à diviser par deux le nombre de bits requis pour l'encodage.
Pour simplifier, un chiffre encodé en bi-quinaire est composé de deux parties : une partie binaire de un bit, suivie par d'une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits pour encoder les valeurs 0, 1, 2, 3 et 4. La partie binaire indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9 : 0 à 4 si la partie binaire vaut 0, 5 à 9 si elle vaut 1. La valeur du chiffre se calcule comme suit :
* si la partie binaire vaut 0, elle est égale à la partie quinaire ;
* si la partie binaire vaut 1, on ajoute 5 à la partie quinaire.
L'originalité de ce codage est qu'il permet de facilement compter sur ses doigts : on a deux mains, cinq doigts. De nombreuses langues utilisent ce système pour coder des nombres, comme le Wolof. Et ce système est utilisé dans certains pays pour compter sur ses doigts, voire dans la vie de tous les jours.
[[File:Code Biquinaer.svg|vignette|Code bi-quinaire où partie binaire et quinaire sont encodées en représentation ''one-hot''.]]
L'encodage abordé au-dessus est une version simplifiée de l'encodage bi-quinaire, il en existe d'autres variantes. Le bi-quinaire vu juste au-dessus encode la partie binaire en binaire, et la partie quinaire en ''one-hot''. Mais il est aussi possible d'inverser l'encodage, à savoir encoder la partie binaire avec une représentation ''one-hot'', alors que la partie bi-quinaire est encodée en binaire normal.
Cependant, la version la plus intéressante utilise l'encodage ''one-hot'' pour les deux ! Elle est illustrée ci-contre. Avec cette dernière, un chiffre BCD est encodé sur 7 bits, dont 2 sont obligatoirement à 1 et les 5 restants sont à 0. Cette propriété permet de détecter qu'un bit a été inversé suite à une erreur de transmission ou un problème de stockage. Si un bit est inversé, le résultat aura 1 bit à 1 et 6 à 0, ou alors 3 bits à 1 et 4 à 0, ce qui ne colle pas. Le résultat après inversion ne donnera pas un chiffre bi-quinaire valide.
Une autre variante du bi-quinaire a été utilisée sur l'IBM 1401. Avec elle, la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre encodé par la partie quinaire. L'avantage de ce système est que la gestion des retenues lors des additions était grandement simplifiée.
Le bi-quinaire et ses variantes ont été utilisés sur d'anciens ordinateurs, dont l'IBM 650, l'UNIVAC Solid State et le UNIVAC LARC. La plupart utilisaient des variantes de l'encodage bi-quinaire proprement dit. Pour rentrer vraiment dans le détail, voici les encodages utilisés sur ces machines. La lecture est facultative, il est possible de passer directement à la section suivante :
{| class="wikitable"
|-
! !! IBM 650 !! Remington Rand 409 !! UNIVAC Solid State !! UNIVAC LARC
|-
| Chiffre || 1357-9 bits || 05-01234 || p-5-421 bits || p-5-qqq bits
|-
| 0 || 10-10000 || 0000-0 || 1-0-000 || 1-0-000
|-
| 1 || 10-01000 || 1000-0 || 0-0-001 || 0-0-001
|-
| 2 || 10-00100 || 1000-1 || 0-0-010 || 1-0-011
|-
| 3 || 10-00010 || 0100-0 || 1-0-011 || 0-0-111
|-
| 4 || 10-00001 || 0100-1 || 0-0-100 || 1-0-110
|-
| 5 || 01-10000 || 0010-0 || 0-1-000 || 0-1-000
|-
| 6 || 01-01000 || 0010-1 || 1-1-001 || 1-1-001
|-
| 7 || 01-00100 || 0001-0 || 1-1-010|| 0-1-011
|-
| 8 || 01-00010 || 0001-1 || 0-1-011 || 1-1-111
|-
| 9 || 01-00001 || 0000-1 || 1-1-100|| 0-1-110
|}
==Annexe : pourquoi l'octet existe==
Nous avons vu plus haut que les nombres encodés en binaire sont découpés en octets, des paquets de 8 bits. Et les octets sont présents partout dans la conception d'un ordinateur. Par exemple, la mémoire RAM d'un ordinateur moderne est organisée en octets. Elle contient des cases mémoire, chacune contenant un octet. Les octets sont numérotés, ce qui permet de sélectionner un octet parmi tous les autres. La capacité de la mémoire est le nombre d'octets qu'elle contient, elle s'exprime en kilo-octets (1024 octets), mébi-octets (1024 kilo-octets), gibi-octets (1024 mébi-octets). Les octets sont donc présents partout dans l'informatique moderne.
{|class="wikitable"
|+ Contenu de la RAM, simplifié
|-
! Numéro
! Octet
|-
! 0
| 0000 1111
|-
! 1
| 0101 0101
|-
! 2
| 0111 0110
|-
! 3
| 0011 1010
|-
! 4
| 1001 1001
|-
! ...
| ...
|-
! 1023
| 0110 1010
|-
! 1024
| 0001 1110
|}
Mais pourquoi est-ce que les nombres encodés en binaire sont souvent découpés en octets ? Pourquoi ne pas utiliser des groupes de 3, 5, 6, 7, 9, 10 bits ? La question est d'autant plus légitime que d'anciens ordinateurs faisaient ainsi. Leurs mémoires étaient découpé en cases mémoire, mais chacune faisait 7 ou 9 bits, rien d'impossible à cela. À vrai dire, l'usage des octets au tout début de l'informatique était assez rare. Il y a eu un basculement progressif vers l'octet pendant les années 80-90. Reste à expliquer pourquoi. Si on devait résumer, les deux raisons principales sont le support du BCD et le support des caractères pour le texte. Voyons d'abord l'encodage des caractères en binaire.
===L'encodage du texte : ASCII et autres encodages===
Pour les caractères, les encodages se sont concentrés sur l'encodage des alphabets occidentaux. Ils demandaient d'encoder au minimum toutes les lettres, en majuscules et minuscules, plus les chiffres, une dizaine de caractères pour la ponctuation. À cela, il faut ajouter des symboles utiles comme %, µ, $, £, #, {} et bien d'autres. Au minimum, cela fait au minimum 52 lettres (26 lettres doublées pour les majuscules) + 10 chiffres + une dizaine de caractères pour la ponctuation : cela fait plus de 72 valeurs à encoder. Pour encoder cela en binaire, le minimum est de 7 bits, ce qui permet d'encoder 128 caractères : les 72 caractères minimum, plus quelques autres. L'encodage ASCII utilisait d'ailleurs 7 bits pour encoder des nombres.
On pourrait croire que c'était une raison suffisante pour utiliser des groupes de 7 bits. Mais il y a quelques problèmes avec cela. Premièrement, les encodages 7 bits pour le texte marchent bien pour gérer la langue anglaise et la plupart des langues occidentales, mais d'autres langues ont besoin d'encoder des caractères locaux. Les 128 caractères de l'ASCII ne sont pas suffisant, ce qui demande de passer de 7 à 8 bits. On retombe donc sur un octet ! D'ailleurs, les encodages ASCII améliorés utilisaient les 127 premières valeurs pour l'ASCII normal, mais les 128 suivantes encodaient d'autres caractères. Les encodages en question varient d'un pays ou d'une langue à l'autre, mais ils sont terriblement importants, ce qui est un argument pour l'usage des octets.
===Le support des données encodées sur un ''nibble''===
Une autre raison pour l'usage des octets est qu'il est facile de les découper en deux ''nibbles''. Pour rappel, les ''nibbles'' sont des groupes de 4 bits. Et il se trouve que de nombreuses données sont encodées sur 4 bits, ni plus ni moins. Les ''nibbles'' sont très important pour le support du BCD et de l’hexadécimal. Et c'était un avantage important au début de l'informatique, moins maintenant. Un octet regroupe deux ''nibbles'' : un ''nibble'' de poids faible, un ''nibble'' de poids fort.
[[File:Nibbles in a byte.PNG|centre|vignette|upright=2|Nibbles dans un octet.]]
Un chiffre BCD occupe un ''nibble'', ce qui fait que la taille idéale de l'unité mémoire doit être un multiple de 4 bits : 4 bits, 8 bits, 12 bits, 16 bits, etc. Un ''nibble'' de 4 bits est insuffisant pour encoder des caractères, 8 bits fonctionne parfaitement, pas besoin d'aller plus loin. De plus, l'usage des ''nibbles'' permet de gérer la représentation hexadécimale très facilement. Un chiffre hexadécimal est encodé sur exactement un ''nibble'', un octet regroupe deux chiffres hexadécimaux, deux octets 4 chiffres hexadécimaux, etc. Enfin, 8 bits est une puissance de deux, ce qui simplifie grandement l'encodage des entiers en binaire est est une source de nombreuses simplifications. Les circuits de l'ordinateur sont plus simple à concevoir quand ils manipulent des opérandes de 2, 4, 8, 16, 32, 64, 128 bits.
===L'influence sur la taille des opérandes===
Les anciens ordinateurs étaient conçus pour le calcul scientifique, ou du moins des applications similaires. Ils n'avaient pas besoin de supporter le BCD, ni même de gérer du texte. Ils ne faisaient que des calculs et utilisaient des opérandes flottantes. La taille des opérandes était dictée par des considérations calculatoires. La norme à l'époque était que les calculs scientifiques devaient gérer des opérandes pouvant aller jusqu'à 10 chiffres décimaux. Et cela demande d'utiliser des opérandes entiers de 35 bits, avec un bit en plus pour encoder des opérandes positives ou négatives. Les 36 bits étaient parfois découpés en 6 groupes de 6 bits chacun, au lieu des octets de 8 bits.
Mais ces groupes de 6 bits étaient souvent peu utilisés. Prenons l'exemple des mémoires RAM. Plus haut, j'ai dit qu'une mémoire RAM normale est composée de plusieurs octets, tous numérotés. Intuitivement, vous vous dites que les anciens ''mainframes'' remplaçaient ces octets par des groupes de 6 bits dans la RAM. Mais en réalité, l'équivalent des RAM de l'époque utilisait des groupes de 36 bits. les groupes de 36 bits étaient aussi numérotés, les numéros étant appelés des adresses mémoire. Par contre, les adresses/numéros étaient codés sur 12 ou 18 bits, c'est à dire sur 2 ou 3 groupes de 6 bits. La raison est que ces numéros sont manipulés par le processeur, qui gère des opérandes de 36 bits. L'idée est que le numéro ne prend que la moitié ou un tiers des 36 bits gérés par le processeur.
Avec l'apparition de l'informatique commerciale, les ordinateurs ont du s'adapter aux besoins des entreprises, pour gérer la comptabilité, des applications financières, des documents texte, etc. Et cela demandait de supporter à la fois le BCD et les caractères/lettres. Il a fallu trouver une unité mémoire capable de gérer à la fois nombres entiers, chiffres BCD et caractères. L'octet était parfait pour ça. Les ''mainframes'' sont adaptés en supportant des caractères codés sur 6, 7 ou 8 bits. Mais le support demandait d'utiliser des circuits qui découpaient un groupe de 36 bits en plusieurs caractères ou en plusieurs chiffres BCD. Le tout demandait des circuits de décalage et de masquage assez complexes. Les ordinateurs 8 et 16 bits manipulaient des octets nativement : leur mémoire RAM gérait des octets, le processeur traitait des opérandes de 8, 16 ou 32 bits. Ils n'avaient pas besoin de matériel complexe pour extraire un caractère ou un chiffre BCD d'un octet/doublet/multiplet. C'était un avantage que les anciens ''mainframes'' n'avaient pas.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'encodage des données
| prevText=L'encodage des données
| next=Les codes de détection/correction d'erreur
| nextText=Les codes de détection/correction d'erreur
}}
</noinclude>
6lz4sn250wunlic6wo1hza24xo032dg
775646
775645
2026-11-02T19:48:54Z
Mewtow
31375
/* Les nombres flottants */
775646
wikitext
text/x-wiki
Dans le chapitre précédent, nous avons vu que les ordinateurs actuels utilisent un codage binaire. Ce codage binaire ne vous est peut-être pas familier. Aussi, dans ce chapitre, nous allons apprendre comment coder des nombres en binaire. Nous allons commencer par le cas le plus simple : les nombres positifs. Par la suite, nous aborderons les nombres négatifs. Et nous terminerons par les nombres à virgules, appelés aussi nombres flottants.
==Le codage des nombres entiers positifs==
Pour coder des nombres entiers positifs, il existe plusieurs méthodes : le binaire, l’hexadécimal, le code Gray, le décimal codé binaire et bien d'autres encore. La plus connue est certainement le binaire, secondée par l'hexadécimal, les autres étant plus anecdotiques. Pour comprendre ce qu'est le binaire, il nous faut faire un rappel sur les nombres entiers tel que vous les avez appris en primaire, à savoir les entiers écrits en décimal. Prenons un nombre écrit en décimal : le chiffre le plus à droite est le chiffre des unités, celui à côté est pour les dizaines, suivi du chiffre des centaines, et ainsi de suite. Dans un tel nombre :
* on utilise une dizaine de chiffres, de 0 à 9 ;
* chaque chiffre est multiplié par une puissance de 10 : 1, 10, 100, 1000, etc. ;
* la position d'un chiffre dans le nombre indique par quelle puissance de 10 il faut le multiplier : le chiffre des unités doit être multiplié par 1, celui des dizaines par 10, celui des centaines par 100, et ainsi de suite.
Exemple avec le nombre 1337 :
<math>1337 = 1 \times 1000 + 3 \times 100 + 3 \times 10 + 7 \times 1</math>
Pour résumer, un nombre en décimal s'écrit comme la somme de produits, chaque produit multipliant un chiffre par une puissance de 10. On dit alors que le nombre est en base 10.
<math>1337 = 1 \times 10^3 + 3 \times 10^2 + 3 \times 10^1 + 7 \times 10^0</math>
Ce qui peut être fait avec des puissances de 10 peut être fait avec des puissances de 2, 3, 4, 125, etc : n'importe quel nombre entier strictement positif peut servir de '''base'''. En informatique, on utilise rarement la base 10 à laquelle nous sommes tant habitués. On utilise à la place deux autres bases :
* La base 2 (système binaire) : les chiffres utilisés sont 0 et 1 ;
* La base 16 (système hexadécimal) : les chiffres utilisés sont 0, 1, 2, 3, 4, 5, 6, 7, 8 et 9 ; auxquels s'ajoutent les six premières lettres de notre alphabet : A, B, C, D, E et F.
===Le système binaire===
En binaire, on compte en base 2. Cela veut dire qu'au lieu d'utiliser des puissances de 10 comme en décimal, on utilise des puissances de deux : n'importe quel nombre entier peut être écrit sous la forme d'une somme de puissances de 2. Par exemple 6 s'écrira donc 0110 en binaire : <math>0 \times 2^3 + 1 \times 2^2 + 1 \times 2^1 + 0 \times 2^0 = 6</math>. On peut remarquer que le binaire n'autorise que deux chiffres, à savoir 0 ou 1 : ces chiffres binaires sont appelés des '''bits''' (abréviation de Binary Digit). Pour simplifier, on peut dire qu'un bit est un truc qui vaut 0 ou 1. Pour résumer, tout nombre en binaire s'écrit sous la forme d'un produit entre bits et puissances de deux de la forme :
<math>a_0 \times 2^0 + a_1 \times 2^1 + a_2 \times 2^2 + a_3 \times 2^3 + a_4 \times 2^4 +...+ a_n \times 2^n</math>
Les coefficients <math>a_0, a_1, a_2</math> sont les bits, l'exposant n qui correspond à un bit <math>a_n</math> est appelé le '''poids''' du bit.
====La terminologie du binaire====
En informatique, il est rare que l'on code une information sur un seul bit. Dans la plupart des cas, l'ordinateur manipule des nombres codés sur plusieurs bits. Les informaticiens ont donné des noms aux groupes de bits suivant leur taille. Le plus connu est certainement l''''octet''', qui désigne un groupe de 8 bits. Moins connu, on parle de '''''nibble''''' pour un groupe de 4 bits (un demi-octet), de '''doublet''' pour un groupe de 16 bits (deux octets) et de '''quadruplet''' pour un groupe de 32 bits (quatre octets).
Précisons qu'en anglais, le terme ''byte'' n'est pas synonyme d'octet. En réalité, le terme ''octet'' marche aussi bien en français qu'en anglais. Quant au terme ''byte'', il désigne un concept complètement différent, que nous aborderons plus tard (c'est la plus petite unité de mémoire que le processeur peut adresser). Il a existé dans le passé des ordinateurs où le ''byte'' faisait 4, 7, 9, 16, voire 48 bits, par exemple. Il a même existé des ordinateurs où le ''byte'' faisait exactement 1 bit ! Mais sur presque tous les ordinateurs modernes, un ''byte'' fait effectivement 8 bits, ce qui fait que le terme ''byte'' est parfois utilisé en lieu et place d'''octet''. Mais c'est un abus de langage, attention aux confusions ! Dans ce cours, nous parlerons d'octet pour désigner un groupe de 8 bits, en réservant le terme ''byte'' pour sa véritable signification.
À l'intérieur d'un nombre, le '''bit de poids faible''' est celui qui est le plus à droite du nombre, alors que le '''bit de poids fort''' est celui non nul qui est placé le plus à gauche, comme illustré dans le schéma ci-dessous.
C'est le même principe que l'écriture des nombres en base décimale : le chiffre le plus significatif à gauche, et le moins significatif à droite (l'unité ou les décimales après la virgule).
[[File:Msb.svg|centre|vignette|upright=2|Bit de poids fort.]]
[[File:Lsb.svg|centre|vignette|upright=2|Bit de poids faible.]]
Cette terminologie s'applique aussi pour les bits à l'intérieur d'un octet, d'un ''nibble'', d'un doublet ou d'un quadruplet. Pour un nombre codés sur plusieurs octets, on peut aussi parler de l'octet de poids fort et de l'octet de poids faible, du doublet de poids fort ou de poids faible, etc.
====La traduction binaire→décimal====
Pour traduire un nombre binaire en décimal, il faut juste se rappeler que la position d'un bit indique par quelle puissance il faut le multiplier. Ainsi, le chiffre le plus à droite est le chiffre des unités : il doit être multiplié par 1 (<math>2^0</math>). Le chiffre situé immédiatement à gauche du chiffre des unités doit être multiplié par 2 (<math>2^1</math>). Le chiffre encore à gauche doit être multiplié par 4 (<math>2^2</math>), et ainsi de suite. Mathématiquement, on peut dire que le énième bit en partant de la droite doit être multiplié par <math>2^{n-1}</math>. Par exemple, la valeur du nombre noté 1011 en binaire est de <math>8 \times 1 + 4 \times 0 + 2 \times 1 + 1 \times 1 = 11</math>.
[[File:Value of digits in the Binary numeral system.svg|centre|vignette|upright=2|Valeur des chffres dans le système de numération binaire.]]
====La traduction décimal→binaire====
La traduction inverse, du décimal au binaire, demande d'effectuer des divisions successives par deux. Les divisions en question sont des divisions euclidiennes, avec un reste et un quotient. En lisant les restes des divisions dans un certain sens, on obtient le nombre en binaire. Voici comment il faut procéder, pour traduire le nombre 34 :
[[File:Exemple d'illustration de la méthode de conversion décimal vers binaire.png|centre|vignette|upright=2|Exemple d'illustration de la méthode de conversion décimal vers binaire.]]
====Quelques opérations en binaire====
Maintenant que l'on sait coder des nombres en binaire normal, il est utile de savoir comment faire quelques opérations usuelles en binaire. Nous utiliserons les acquis de cette section dans la suite du chapitre, bien que de manière assez marginale.
La première opération est assez spécifique au binaire. Il s'agit d'une opération qui inverse les bits d'un nombre : les 0 deviennent des 1 et réciproquement. Par exemple, le nombre 0001 1001 devient 1110 0110. Elle porte plusieurs noms : opération NOT, opération NON, complémentation, etc. Nous parlerons de '''complémentation''' ou d'opération NOT dans ce qui suit. Beaucoup d'ordinateurs gèrent cette opération, ils savent la faire en un seul calcul. Il faut dire que c'est une opération assez utile, bien que nous ne pouvons pas encore expliquer pourquoi.
[[File:Binary Addition Demonstration.svg|vignette|Exemple d'addition en binaire.]]
La seconde opération à aborder est l'addition. Elle se fait en binaire de la même manière qu'en décimal : Pour faire une addition en binaire, on additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. Sauf que l'on additionne des bits. Heureusement, la table d'addition est très simple en binaire :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
La troisième opération est une variante de l'addition appelée l''''opération XOR''', notée <math>\oplus</math>. Il s'agit d'une addition dans laquelle on ne propage pas les retenues. L'addition des deux bits des opérandes se fait normalement, mais les retenues sont simplement oubliées, on n'en tient pas compte. Le résultat est que l'addition se résume à appliquer la table d'addition précédente :
* 0 <math>\oplus</math> 0 = 0 ;
* 0 <math>\oplus</math> 1 = 1 ;
* 1 <math>\oplus</math> 0 = 1 ;
* 1 <math>\oplus</math> 1 = 0.
Pour résumer, le résultat vaut 1 si les deux bits sont différents, 0 s'ils sont identiques. L'opération XOR sera utilisée rapidement dans le chapitre suivant, quand nous parlerons rapidement du mot de parité. Et elle sera beaucoup utilisée dans la suite du cours, nous en feront fortement usage. Pour le moment, mémorisez juste cette opération, elle n'a rien de compliqué.
Une dernière opération est l'opération de '''''population count'''''. Il s'agit ni plus ni moins que de compter le nombre de bits qui sont à 1 dans un nombre. Par exemple, pour le nombre 0110 0010 1101 1110, elle donne pour résultat 9. Elle est utilisée dans certaines applications, comme le calcul de certains codes correcteurs d'erreur, comme on le verra dans le chapitre suivant. Elle est supportée sur de nombreux ordinateurs, encore que cela dépende du processeur considéré. Il s'agit cependant d'une opération assez courante, supportée par les processeurs ARM, les processeurs x86 modernes (ceux qui gèrent le SSE), et quelques autres.
===L'hexadécimal===
L’hexadécimal est basé sur le même principe que le binaire, sauf qu'il utilise les 16 chiffres suivants :
{| class="wikitable"
|- style="color:#8080ffff; text-align:center;"
! Chiffre hexadécimal
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || A || B || C || D || E || F
|- style="text-align:center;"
! Nombre décimal correspondant
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 10 || 11 || 12 || 13 || 14 || 15
|- style="text-align:center;"
! Notation binaire
| 0000 || 0001 || 0010 || 0011 || 0100 || 0101 || 0110 || 0111 || 1000 || 1001 || 1010 || 1011 || 1100 || 1101 || 1110 || 1111
|}
Dans les textes, afin de différencier les nombres décimaux des nombres hexadécimaux, les nombres hexadécimaux sont suivis par un petit h, indiqué en indice. Si cette notation n'existait pas, des nombres comme 2546 seraient ambigus : on ne saurait pas dire sans autre indication s'ils sont écrits en décimal ou en hexadécimal. Avec la notation, on sait de suite que 2546 est en décimal et que 2546h est en hexadécimal.
Dans les codes sources des programmes, la notation diffère selon le langage de programmation.
Certains supportent le suffixe <code>h</code> pour les nombres hexadécimaux, d'autres utilisent un préfixe <code>0x</code> ou <code>0h</code>.
Pour convertir un nombre hexadécimal en décimal, il suffit de multiplier chaque chiffre par la puissance de 16 qui lui est attribuée. Là encore, la position d'un chiffre indique par quelle puissance celui-ci doit être multiplié : le chiffre le plus à droite est celui des unités, le second chiffre le plus à droite doit être multiplié par 16, le troisième chiffre en partant de la droite doit être multiplié par 256 (16 * 16) et ainsi de suite. La technique pour convertir un nombre décimal vers de l’hexadécimal est similaire à celle utilisée pour traduire un nombre du décimal vers le binaire. On retrouve une suite de divisions successives, mais cette fois-ci les divisions ne sont pas des divisions par 2 : ce sont des divisions par 16.
La conversion inverse, de l'hexadécimal vers le binaire est très simple, nettement plus simple que les autres conversions. Pour passer de l'hexadécimal au binaire, il suffit de traduire chaque chiffre en sa valeur binaire, celle indiquée dans le tableau au tout début du paragraphe nommé « Hexadécimal ». Une fois cela fait, il suffit de faire le remplacement. La traduction inverse est tout aussi simple : il suffit de grouper les bits du nombre par 4, en commençant par la droite (si un groupe est incomplet, on le remplit avec des zéros). Il suffit alors de remplacer le groupe de 4 bits par le chiffre hexadécimal qui correspond.
==Interlude propédeutique : la capacité d'un entier et les débordements d'entiers==
Dans la section précédente, nous avons vu comment coder des entiers positifs en binaire ou dans des représentations proches. La logique voudrait que l'on aborde ensuite le codage des entiers négatifs. Mais nous allons déroger à cette logique simple, pour des raisons pédagogiques. Nous allons faire un interlude qui introduira des notions utiles pour la suite du chapitre. De plus, ces concepts seront abordés de nombreuses fois dans ce wikilivre et l'introduire ici est de loin la solution idéale.
===Les ordinateurs manipulent des nombres codés sur un nombre fixe de bits===
Vous avez certainement déjà entendu parler de processeurs 32 ou 64 bits. Et si vous avez joué aux jeux vidéos durant votre jeunesse et êtes assez agé, vous avez entendu parler de consoles de jeu 8 bits, 16 bits, 32 bits, voire 64 bits (pour la Jaguar, et c'était un peu trompeur). Derrière cette appellation qu'on retrouvait autrefois comme argument commercial dans la presse se cache un concept simple. Tout ordinateur manipule des nombres entiers dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Une console 16 bits manipulait des entiers codés en binaire sur 16 bits, pas un de plus, pas un de moins. Pareil pour les anciens ordinateurs 32 bits, qui manipulaient des nombres entiers codés sur 32 bits.
Aujourd'hui, les ordinateurs modernes utilisent presque un nombre de bits qui est une puissance de 2 : 8, 16, 32, 64, 128, 256, voire 512 bits. Mais cette règle souffre évidemment d'exceptions. Aux tout débuts de l'informatique, certaines machines utilisaient 3, 7, 13, 17, 23, 36 et 48 bits ; mais elles sont aujourd'hui tombées en désuétude. De nos jours, il ne reste que les processeurs dédiés au traitement de signal audio, que l'on trouve dans les chaînes HIFI, les décodeurs TNT, les lecteurs DVD, etc. Ceux-ci utilisent des nombres entiers de 24 bits, car l'information audio est souvent codée par des nombres de 24 bits.
Anecdote amusante, il a existé des ordinateurs de 1 bit, qui sont capables de manipuler des nombres codés sur 1 bit, pas plus. Un exemple est le Motorola MC14500B, commercialisé de 1976.
====Le lien entre nombre de bits et valeurs codables====
Évidemment, on ne peut pas coder tous les entiers possibles et imaginables avec seulement 8 bits, ou 16 bits. Et il parait intuitif que l'on ait plus de valeurs codables sur 16 bits qu'avec 8 bits, par exemple. Plus le nombre de bits est important, plus on pourra coder de valeurs entières différentes. Mais combien de plus ? Par exemple, si je passe de 8 bits à 16 bits, est-ce que le nombre de valeurs que l'on peut coder double, quadruple, pentuple ? De même, combien de valeurs différentes on peut coder avec <math>n</math> bits. Par exemple, combien de nombres différents peut-on coder avec 4, 8 ou 16 bits ? La section précédente vous l'expliquer.
Avec <math>n</math> bits, on peut coder <math>2^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>2^n - 1</math>. N'oubliez pas cette formule : elle sera assez utile dans la suite de ce tutoriel. Pour exemple, on peut coder 16 valeurs avec 4 bits, qui vont de 0 à 15. De même, on peut coder 256 valeurs avec un octet, qui vont de 0 à 255. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de bits
! Nombre de valeurs codables
|-
! 4
| 16
|-
! 8
| 256
|-
! 16
| 65 536
|-
! 32
| 4 294 967 296
|-
! 64
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien de bits il faut pour coder une valeur quelconque, que nous noterons N. Pour cela, il faut utiliser la formule précédente, mais à l'envers. On cherche alors <math>n</math> tel que <math>N = 2^n</math>. L'opération qui donne <math>n</math> est appelée le logarithme, et plus précisément un logarithme en base 2, noté <math>\log_2{(X)}</math>. Le problème est que le résultat du logarithme ne tombe juste que si le nombre X est une puissance de 2. Si ce n'est pas le cas, le résultat est un nombre à virgule, ce qui n'a pas de sens pratique. Par exemple, la formule nous dit que pour coder le nombre 13, on a besoin de 3,70043971814 bits, ce qui est impossible. Pour que le résultat ait un sens, il faut arrondir à l'entier supérieur. Pour l'exemple précédent, les 3,70043971814 bits s'arrondissent en 4 bits.
====Le lien entre nombre de chiffres hexadécimaux et valeurs codables====
Maintenant, voyons combien de valeurs peut-on coder avec <math>n</math> chiffres hexadécimaux. La réponse n'est pas très différente de celle obtenue en binaire, si ce n'est qu'il faut remplacer le 2 par un 16 dans la formule précédente. Avec <math>n</math> chiffres hexadécimaux, on peut coder <math>16^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>16^n - 1</math>. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de chiffres héxadécimaux
! Nombre de valeurs codables
|-
! 1 (4 bits)
| 16
|-
! 2 (8 bits)
| 256
|-
! 4 (16 bits)
| 65 536
|-
! 8 (32 bits)
| 4 294 967 296
|-
! 16 (64 bits)
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien faut-il de chiffres hexadécimaux pour coder une valeur quelconque en hexadécimal. La formule est là encore la même qu'en binaire, sauf qu'on remplace le 2 par un 16. Pour trouver le nombre de chiffres hexadécimaux pour encoder un nombre X, il faut calculer <math>\log_{16}{(X)}</math>. Notons que le logarithme utilisé est un logarithme en base 16, et non un logarithme en base 2, comme pour le binaire. Là encore, le résultat ne tombe juste que si le nombre X est une puissance de 16 et il faut arrondir à l'entier supérieur si ce n'est pas le cas.
Une propriété mathématique des logarithmes nous dit que l'on peut passer d'un logarithme en base X et à un logarithme en base Y avec une simple division, en utilisant la formule suivante :
: <math>\log_X{(N)} = {\log_{Y}{(N)} \over \log_Y(X)}</math>
Dans le cas qui nous intéresse, on a Y = 2 et X = 16, ce qui donne :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over \log_2(16)}</math>
Or, <math>\log_2(16)</math> est tout simplement égal à 4, car il faut 4 bits pour coder la valeur 16. On a donc :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over 4}</math>
En clair, il faut quatre fois moins de chiffres hexadécimaux que de bits, ce qui est assez intuitif vu qu'il faut 4 bits pour coder un chiffre hexadécimal.
===Les débordements d'entier===
On vient de voir que tout ordinateur manipule des nombres dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Et cela limite les valeurs qu'il peut encoder, qui sont comprises dans un intervalle bien précis. Mais que ce passe-t-il si jamais le résultat d'un calcul ne rentre pas dans cet intervalle ? Par exemple, pour du binaire normal, que faire si le résultat d'un calcul atteint ou dépasse <math>2^n</math> ? Dans ce cas, le résultat ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
On peut imaginer d'autres codages pour lesquels les entiers ne commencent pas à zéro ou ne terminent pas à <math>2^n - 1</math>. On peut prendre le cas où l'ordinateur gère les nombres négatifs, par exemple. Dans le cas général, l'ordinateur peut coder les valeurs comprises dans un intervalle, qui va de la valeur la plus basse <math>N_\text{min}</math> à la valeur la plus grande <math>N_\text{max}</math>. Et encore une fois, si un résultat de calcul sort de cet intervalle, on fait face à un débordement d'entier.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math>, avec <math>N_\text{max}</math> la plus grande valeur codable par l'ordinateur.
On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur basse de débordement vaut <math>N_\text{min} - 1</math>, avec <math>N_\text{min}</math> la plus petite valeur codable par l'ordinateur.
===La gestion des débordements d'entiers===
Face à un débordement d'entier, l'ordinateur peut utiliser deux méthodes : l'arithmétique saturée ou l'arithmétique modulaire.
L''''arithmétique saturée''' consiste à arrondir le résultat pour prendre la plus grande ou la plus petite valeur. Si le résultat d'un calcul dépasse la valeur haute de débordement, le résultat est remplacé par le plus grand entier supporté par l'ordinateur. La même chose est possible quand le résultat est inférieur à la plus petite valeur possible, par exemple lors d'une soustraction : l'ordinateur arrondit au plus petit entier possible.
Pour donner un exemple, voici ce que cela donne avec des entiers codés sur 4 bits, qui codent des nombres de 0 à 15. Si je fais le calcul 8 + 9, le résultat normal vaut 17, ce qui ne rentre pas dans l'intervalle. Le résultat est alors arrondi à 15. Inversement, si je fais le calcul 8 - 9, le résultat sera de -1, ce qui ne rentre pas dans l'intervalle : le résultat est alors arrondi à 0.
[[File:Odometer rollover.jpg|vignette|Exemple de débordement d'entier sur un compteur mécanique quelconque. L'image montre clairement que le compteur revient à zéro une fois la valeur maximale dépassée.]]
L''''arithmétique modulaire''' est plus compliquée et c'est elle qui va nous intéresser dans ce qui suit. Pour simplifier, imaginons que l'on décompte à partir de zéro. Quand on arrive à la valeur haute de débordement, on recommence à compter à partir de zéro. L'arithmétique modulaire n'est pas si contre-intuitive et vous l'utilisez sans doute au quotidien. Après tout, c'est comme cela que l'on compte les heures, les minutes et les secondes. Quand on compte les minutes, on revient à 0 au bout de 60 minutes. Pareil pour les heures : on revient à zéro quand on arrive à 24 heures. Divers compteurs mécaniques fonctionnent sur le même principe et reviennent à zéro quand ils dépassent la plus grande valeur possible, l'image ci-contre en montrant un exemple.
Mathématiquement, l'arithmétique modulaire implique des divisions euclidiennes, celles qui donnent un quotient et un reste. Lors d'un débordement, le résultat s'obtient comme suit : on divise le nombre qui déborde par la valeur haute de débordement, et que l'on conserve le reste de la division. Au passage, l'opération qui consiste à faire une division et à garder le reste au lieu du quotient est appelée le ''modulo''. Prenons l'exemple où l'ordinateur peut coder tous les nombres entre 0 et 1023, soit une valeur haute de débordement de 1024. Pour coder le nombre 4563, on fait le calcul 4563 / 1024. On obtient : <math>4563 = 4 \times 1024 + 467</math>. Le reste de la division est de 467 et c'est lui qui sera utilisé pour coder la valeur de départ, 4563. Le nombre 4563 est donc codé par la valeur 467 dans un tel ordinateur en arithmétique modulaire. Au passage, la valeur haute de débordement est toujours codée par un zéro dans ce genre d'arithmétique.
Les ordinateurs utilisent le plus souvent une valeur haute de débordement de <math>2^n</math>, avec n le nombre de bits utilisé pour coder les nombres entiers positifs. En faisant cela, l'opération modulo devient très simple et revient à éliminer les bits de poids forts au-delà du énième bit. Par exemple, reprenons l'exemple d'un ordinateur qui code ses nombres sur 4 bits. Imaginons qu'il fasse le calcul <math>13 + 3</math>, soit 1101 + 0011 = 1 0000 en binaire. Le résultat entraîne un débordement d'entier et l'ordinateur ne conserve que les 4 bits de poids faible. Cela donne : 1101 + 0011 = 0000. Comme autre exemple l'addition 1111 + 0010 ne donnera pas 17 (1 0001), mais 1 (0001).
L'avantage est que les calculs sont beaucoup plus simples avec cette méthode qu'avec les autres. L'ordinateur a juste à ne pas calculer les bits de poids fort. Pas besoin de faire une division pour calculer un modulo, pas besoin de corriger le résultat pour faire de l'arithmétique saturée.
===Aparté : quelques valeurs particulières en binaire===
Plus haut, on a dit qu'avec n bits, on peut encoder toutes les valeurs allant de <math>0</math> à <math>2^n - 1</math>. Ce simple fait permet de déterminer quelle est la valeur de certains entiers à vue d’œil. Dans ce qui va suivre, nous allons poser quelques bases que nous réutiliserons dans la suite du chapitre. Il s'agit de quelques trivias qui sont cependant assez utiles.
Le premier trivia concerne la valeur maximale <math>2^n - 1</math> : elle est encodée par un nombre dont tous les bits sont à 1.
Le deuxième trivia concerne les nombres de la forme 000...000 1111 1111, à savoir des nombres dont les x bits de poids faible sont à 1 et tous les autres valent 0. Par définition, de tels nombres codent la plus grande valeur possible sur x bits, ce qui fait qu'ils valent <math>2^x - 1</math>.
Le troisième trivia concerne les nombres de la forme 11111...0000. En clair, des nombres où on a une suite consécutive de 1 dans les bits de poids fort et les x bits de poids faibles à 0. De tels nombres valent : <math>2^n - 2^x</math>. La preuve est assez simple : ils s'obtiennent en prenant la valeur maximale <math>2^n - 1</math>, et en soustrayant un nombre de la forme <math>2^x - 1</math>.
[[File:Valeurs particulières en binaire.png|centre|vignette|upright=2|Valeurs particulières en binaire]]
Si on utilise l'arithmétique modulaire, la valeur <math>2^n</math> n'est autre que la valeur de débordement haute pour les nombres stockés sur n bits, et se code comme le zéro (il faudrait 1 bit de plus pour stocker le 1 de poids fort de <math>2^n</math>). Les 1er et 3ème nombres évoqués dans les paragraphes précédents peuvent donc se passer de <math>2^n</math> dans leur expression :
* <math>- 1</math> est codé 1111111111111111...111 (n bits à 1)
* <math>- 2^x</math> est codé 11111...111000..000000 (x bits à 0 précédés de n-x bits à 1)
==Les nombres entiers négatifs==
<noinclude>[[File:Binary Representations.svg|vignette|Représentations signées, exemples sur 4 bits.]]</noinclude>
Passons maintenant aux entiers négatifs en binaire : comment représenter le signe moins ("-") avec des 0 et des 1 ? Eh bien, il existe plusieurs méthodes :
* la représentation en signe-valeur absolue ;
* la représentation en complément à un ;
* la représentation en complément à deux;
* la représentation par excès ;
* la représentation dans une base négative ;
* d'autres représentations encore moins utilisées que les autres.
===La représentation en signe-valeur absolue===
La solution la plus simple pour représenter un entier négatif consiste à coder sa valeur absolue en binaire, et rajouter un bit qui précise si c'est un entier positif ou un entier négatif. Par convention, ce bit de signe vaut 0 si le nombre est positif et 1 s'il est négatif. On parle alors de représentation en signe-valeur absolue, aussi appelée '''représentation en signe-magnitude'''.
Avec cette technique, il y autant de nombres positifs que négatifs. Mieux : pour chaque nombre représentable en représentation signe-valeur absolue, son inverse l'est aussi. Ce qui fait qu'avec cette méthode, le zéro est codé deux fois : on a un -0, et un +0. Cela pose des problèmes lorsqu'on demande à notre ordinateur d'effectuer des calculs ou des comparaisons avec zéro.
[[File:Codage sur 4 bits en signe-valeur absolue.png|centre|vignette|upright=2|Codage sur 4 bits en signe-valeur absolue]]
===La représentation en complément à un===
La représentation en '''complément à un''' peut être vue, en première approximation, comme une variante de la représentation en signe-magnitude. Et je dis bien : "en première approximation", car il y a beaucoup à dire sur la représentation en complément à un, mais nous verrons cela dans la section suivante sur le complément à deux. Conceptuellement, la représentation en signe-magnitude et celle du complément à un sont drastiquement différentes et sont basées sur des concepts mathématiques totalement différents. Mais elles ont des ressemblances de surface, qui font que faire la comparaison entre les deux est assez utile pédagogiquement parlant.
En complément à un, les nombres sont codés en utilisant un bit de signe qui indique si le nombre de positif ou négatif, couplé à une valeur absolue. Par contre, la valeur absolue est codée différemment en binaire. Pour les nombres positifs, la valeur absolue est codée en binaire normal, pas de changement comparé aux autres représentations. Mais pour les valeurs négatives, la valeur absolue est codée en binaire normal, puis tous les bits sont inversés : les 0 deviennent des 1 et réciproquement. En clair, on utilise une opération de complémentation pour les nombres négatifs.
Prenons un exemple avec un nombre codé sur 4 bits, avec un cinquième bit de signe. La valeur 5 est codée comme suit : 0 pour le bit de signe, 5 donne 0101 en binaire, le résultat est 0 0101. Pour la valeur -5, le bit de signe est 1, 5 donne 0101 en binaire, on inverse les bits ce qui donne 1010 : cela donne 11010. Notez qu'on peut passer d'un résultat à l'autre avec une opération de complémentation, à savoir en inversant les bits de l'autre et réciproquement.
Il s'agit là d'une propriété générale avec le complément à 1 : l'opposé d'un nombre, à savoir passer de sa valeur positive à sa valeur négative ou inversement, se calcule avec une opération NOT, une opération de complémentation (d'où son nom). L'avantage est que les ordinateurs gèrent naturellement d'opération de complémentation. Par contre, en signe-magnitude, inverser le bit de signe est une opération spécifique et ne sert qu'à ça. Il faut donc rajouter une opération en plus pour calculer l'opposé d'un nombre.
La représentation en complément à un garde les défauts de la représentation en signe-magnitude. Le zéro est codé deux fois, avec un zéro positif et un zéro négatif. La différence est que les valeurs négatives sont dans l'ordre inverse, il y a une symétrie un peu meilleure. Les deux zéros sont d'ailleurs totalement éloignés, ils correspondent aux valeurs extrêmes encodables : le zéro positif a tous ses bits à 0, le zéro négatif a tous ses bits à 1. Le résultat est que l'implémentation des comparaisons et de certains calculs est plus complexe qu'avec le signe-magnitude, mais de peu.
[[File:Codage sur 4 bits en complément à 1.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 1.]]
===La représentation par excès===
La représentation par excès consiste à ajouter un biais aux nombres à encoder afin de les encoder par un entier positif. Pour encoder tous les nombres compris entre -X et Y en représentation par excès, il suffit de prendre la valeur du nombre à encoder, et de lui ajouter un biais égal à X. Ainsi, la valeur -X sera encodée par zéro, et toutes les autres valeurs le seront par un entier positif, le zéro sera encodé par X, 1, par X+1, etc. Par exemple, prenons des nombres compris entre -127 et 128. On va devoir ajouter un biais égal à 127, ce qui donne :
{|class="wikitable"
|-
!Valeur avant encodage!!Valeur après encodage
|-
||-127||0
|-
||-126||1
|-
||-125||2
|-
||…||…
|-
||0||127
|-
||…||…
|-
||127||254
|-
||128||255
|}
===La représentation en complément à deux===
La représentation en '''complément à deux''' est basée sur les mêmes mathématiques que le complément à un, mais fonctionne très différemment en pratique.
Si on regarde de loin, son principe est assez différent : il n'y a pas de bit de signe ni quoi que ce soit d'autre. À la place, un nombre en complément à deux est encodé comme en binaire normal, à un point près : le bit de poids fort est soustrait, et non additionné aux autres. Il a une valeur négative : on soustrait la puissance de deux associée s'il vaut 1, on ne la tient pas en compte s'il vaut 0.
Par exemple, la valeur du nombre noté 111001 en complément à deux s'obtient comme suit :
{|class="wikitable" style="margin:auto;"
|-
! -32 !! 16 !! 8 !! 4 !! 2 !! 1
|-
| 1 || 1 || 1 || 0 || 0 || 1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(8×1)+(4×0)+(2×1)+(1×1) = −32+16+8+1 = -7.
: ''Avec le complément à deux, comme avec le complément à un, le bit de poids fort vaut 0 pour les nombres positifs et 1 pour les négatifs.''
L'avantage de cette représentation est qu'elle n'a pas de double zéro : le zéro n'est encodé que par une seule valeur. Par contre, la valeur autrefois prise par le zéro négatif est réutilisée pour encoder une valeur négative. La conséquence est que l'on a un nombre négatif en plus d'encodé, il n'y a plus le même nombre de valeurs strictement positives et de valeurs négatives encodées. Le nombre négatif en question est appelé le '''nombre le plus négatif''', ce nom trahit le fait que c'est celui qui a la plus petite valeur (la plus grande valeur absolue). Il est impossible de coder l'entier positif associé, sa valeur absolue.
[[File:Codage sur 4 bits en complément à 2.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 2.]]
====Le calcul du complément à deux : première méthode====
Les représentations en complément à un et en complément à deux sont basées sur le même principe mathématique. Leur idée est de remplacer chaque nombre négatif par un nombre positif équivalent, appelé le ''complément''. Par équivalent, on veut dire que tout calcul donne le même résultat si on remplace un nombre négatif par son complément (idem avec un nombre positif, son complément aura un signe inverse). Et pour faire cela, elles se basent sur les débordements d'entier pour fonctionner, et plus précisément sur l'arithmétique modulaire abordée plus haut.
Si on fait les calculs avec le complément, les résultats du calcul entraînent un débordement d'entier, qui sera résolu par un '''modulo''' : l'ordinateur ne conserve que les bits de poids faible du résultat, les autres bits sont oubliés. Par exemple, prenons l'addition 15 + 2, 1111 + 0010 en binaire : le résultat ne sera pas 17 (10001), vu qu'on n'a pas assez de bits pour encoder le résultat, mais 1 (0001). Et le résultat après modulo sera identique au résultat qu'on aurait obtenu avec le nombre négatif sans modulo. En clair, c'est la gestion des débordements qui permet de corriger le résultat de manière à ce que l'opération avec le complément donne le même résultat qu'avec le nombre négatif voulu. Ainsi, on peut coder un nombre négatif en utilisant son complément positif.
: Cela ressemble beaucoup à la méthode de soustraction basée sur un complément à 9 pour ceux qui connaissent, sauf que c'est une version binaire qui nous intéresse ici.
Prenons un exemple, qui permettra d'introduire la suite. Encore une fois, on utilise un codage sur 4 bits dont la valeur haute de débordement est de 16. Prenons l'addition de 13 + 3 = 16. Avec l'arithmétique modulaire, 16 est équivalent à 0, ce qui donne : 13 + 3 = 0 ! On peut aussi reformuler en disant que 13 = -3, ou encore que 3 = -13. Dit autrement, 3 est le complément de -13 pour ce codage. Et ne croyez pas que ça marche uniquement dans cet exemple : cela se généralise assez rapidement à tout nombre négatif.
Prenons un nombre N dont un veut calculer le complément à deux K. Dans le cas général, on a :
: <math>N + K = 2^n</math>, vu que <math>2^n = 0</math> en utilisant le modulo.
En réorganisant très légèrement les termes pour isoler K, on a :
: <math>K = 2^n - N</math>
La formule précédent permet de calculer la complément à deux assez simplement, en faisant le calcul à la main.
Avant de poursuivre, prenons un exemple très intéressant : le cas où N = -1. Son complément à deux vaut donc :
: <math>K = 2^n - 1</math>
Le terme <math>2^n - 1</math> devrait vous rappeler quelque chose : il s'agit d'un nombre dont tous les bits sont à 1. En clair, le complément à deux de -1 est un nombre de la forme 1111...111. Et cela vaut quel que soit le nombre de bits n. La représentation de -1 est similaire, peu importe que l'on utilise des nombres de 16 bits, 32 bits, 64 bits, etc.
Voyons maintenant le cas des puissance de deux, par exemple 2, 4, 8, 16, etc. Leur complément à deux vaut :
: <math>K = 2^n - 2^x</math>
Nous avions vu précédemment dans le chapitre que ces nombres sont de la forme 11111...0000. En clair, des nombres dont les x bits de poids faible sont à 0, et tous les autres bits sont à 1. Par exemple, le complément à deux de -2 est un nombre dont les bits sont à 1, sauf le bit de poids faible. De même, le complément à deux de -4 a tous ses bits à 1, sauf les deux bits de poids faible. Et le complément à deux de -8 a tous ses bits à 1, sauf les trois bits de poids faible. Pour résumer, tout nombre de la forme <math>- 2^x</math> a tous ses bits à 1, sauf les x bits de poids faible qui sont à 0.
{|class="wikitable"
|+ Exemple avec des nombres de 8 bits
|-
! -1
| 1111 1111
|-
! -2
| 1111 1110
|-
! -4
| 1111 1100
|-
! -8
| 1111 1000
|-
! -16
| 1111 0000
|-
! -32
| 1110 0000
|-
! -64
| 1100 0000
|-
! -128
| 1000 0000
|-
! 0 / - 256
| 0000 0000
|}
====Le calcul du complément à deux : seconde méthode====
Il existe une seconde méthode pour calculer le complément à deux d'un nombre, la voici. Pour les nombres positifs, encodez-les comme en binaire normale. Pour les nombres négatifs, faites pareil, puis inversez tous les bits, avant d'ajouter un. La procédure est identique à celle du complément à un, sauf que l'on incrémente le résultat final. Et ce n'est pas une coïncidence, comme nous allons le voir immédiatement.
Pour comprendre pourquoi la méthode marche, repartons de la formule précédente <math>K = 2^n - N</math>. Elle peut se reformuler comme suit :
: <math>K = (2^n - 1 + 1) - N</math>
: <math>K = (2^n - 1) - N + 1</math>
La valeur <math>(2^n - 1)</math> est par définition un nombre dont tous les bits sont à 1. À cette valeur, on soustrait un nombre <math>|N|</math> dont certains bits sont à 1 et d'autres à 0. En clair, pour chaque colonne, on a deux possibilités : soit on doit faire la soustraction <math>1-1</math>, soit la soustraction <math>1-0</math>. Or, les règles de l'arithmétique binaire disent que <math>1-1=0</math> et <math>1-0=1</math>. En regardant attentivement, on se rend compte que le bit du résultat est l'inverse du bit de départ. De plus, les deux cas ne donnent pas de retenue : le calcul pour chaque bit n'influence pas les bits voisins.
Le terme <math>(2^n - 1) - N</math> est donc le complément à un du nombre N, un nombre égal à sa représentation en binaire dont tous les bits sont inversés. Notons <math>\overline{|N|}</math> le nombre formé en inversant tous les bits de N. On a alors :
: <math>K = \overline{N} + 1</math>
En clair, le complément à deux s'obtient en prenant le complément à un et en ajoutant 1. Dit autrement, il faut prendre le nombre N, en inverser tous les bits et ajouter 1.
Une autre manière équivalente consiste à faire le calcul suivant :
: <math>K = \overline{N - 1}</math>
On prend le nombre dont on veut le complément, on soustrait 1 et on inverse les bits.
: Notons que tout ce qui a été dit plus haut marche aussi pour le complément à un, avec cependant une petite différence : la valeur haute de débordement n'est pas la même, ce qui change les calculs. Pour des nombres codés sur <math>n</math> bits, la valeur haute de débordement est égale à <math>2^n</math> en complément à deux, alors qu'elle est de <math>2^n - 1</math> en complément à un. De ce fait, la gestion des débordements est plus simple en complément à deux.
====L'extension de signe====
Dans les ordinateurs, tous les nombres sont codés sur un nombre fixé et constant de bits. Ainsi, les circuits d'un ordinateur ne peuvent manipuler que des nombres de 4, 8, 12, 16, 32, 48, 64 bits, suivant la machine. Si l'on veut utiliser un entier codé sur 16 bits et que l'ordinateur ne peut manipuler que des nombres de 32 bits, il faut bien trouver un moyen de convertir le nombre de 16 bits en un nombre de 32 bits, sans changer sa valeur et en conservant son signe. Cette conversion d'un entier en un entier plus grand, qui conserve valeur et signe s'appelle l''''extension de signe'''.
L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
L'explication plus simple tient dans la manière de coder le bit de poids fort. Prenons l'exemple de la conversion d'un entier de 5 bits en un entier de 8bits. Les 4 bits de poids faible ont un poids positif (on les additionne), alors que le bit de poids fort a un poids négatif. Le nombre encodé vaut : <math>\text{Bit de poids fort} \times -16 + \text{Valeur encodée sur les 4 bits de poids faible}</math>. La valeur encodée sur 4 bits reste la même après extension de signe, car les poids des bits de poids faible ne changent pas. Par contre, le bit de poids fort change. Sur 8 bits, la valeur -16 est encodée par un nombre de la forme 1111 0000. En remplaçant le bit de poids fort par sa valeur calculée sur plus de bits, on remarque que les bits de poids fort ont été remplacés par des 1.
===La représentation négabinaire===
Enfin, il existe une dernière méthode, assez simple à comprendre, appelée '''représentation négabinaire'''. Dans cette méthode, les nombres sont codés non en base 2, mais en base -2. Oui, vous avez bien lu : la base est un nombre négatif. Dans les faits, la base -2 est similaire à la base 2 : il y a toujours deux chiffres (0 et 1), et la position dans un chiffre indique toujours par quelle puissance de 2 il faut multiplier, sauf qu'il faudra ajouter un signe moins une fois sur 2. Concrètement, les puissances de -2 sont les suivantes : 1, -2, 4, -8, 16, -32, 64, etc. En effet, un nombre négatif multiplié par un nombre négatif donne un nombre positif, ce qui fait qu'une puissance sur deux est négative, alors que les autres sont positives. Ainsi, on peut représenter des nombres négatifs, mais aussi des nombres positifs dans une puissance négative.
Par exemple, la valeur du nombre noté 11011 en base -2 s'obtient comme suit :
{|class="wikitable"
|-
!…
!-32
!16
!-8
!4
!-2
!1
|-
|…
|1
|1
|1
|0
|1
|1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(−8×1)+(4×0)+(−2×1)+(1×1)=−32+16−8−2+1=−25.
==Les nombres à virgule fixe==
On sait donc comment sont stockés nos nombres entiers dans un ordinateur. Néanmoins, les nombres entiers ne sont pas les seuls nombres que l'on utilise au quotidien : il nous arrive d'en utiliser à virgule. Notre ordinateur n'est pas en reste : il est lui aussi capable de manipuler de tels nombres. Dans les grandes lignes, il peut utiliser deux méthodes pour coder des nombres à virgule en binaire : La virgule fixe et la virgule flottante. Parlons d'abord de la virgule fixe, plus simple à comprendre.
===La virgule fixe : des entiers cachés===
La méthode de la virgule fixe consiste à émuler les nombres à virgule à partir de nombres entiers. Un nombre à virgule fixe est codé par un nombre entier proportionnel au nombre à virgule fixe. Pour obtenir la valeur de notre nombre à virgule fixe, il suffit de diviser l'entier servant à le représenter par le facteur de proportionnalité. Par exemple, pour coder 1,23 en virgule fixe, on peut choisir comme « facteur de conversion » 1000, ce qui donne l'entier 1230.
Généralement, les informaticiens utilisent une puissance de deux comme facteur de conversion, pour simplifier les calculs. En faisant cela, on peut écrire les nombres en binaire et les traduire en décimal facilement. Pour l'exemple, cela permet d'écrire des nombres à virgule en binaire comme ceci : 1011101,1011001. Et ces nombres peuvent se traduire en décimal avec la même méthode que des nombres entier, modulo une petite différence. Comme pour les chiffres situés à gauche de la virgule, chaque bit situé à droite de la virgule doit être multiplié par la puissance de deux adéquate. La différence, c'est que les chiffres situés à droite de la virgule sont multipliés par une puissance négative de deux, c'est à dire par <math>1 \over 2</math>, <math>1 \over 4</math>, <math>1 \over 8</math>, <math>1 \over 16</math>, <math>1 \over 32</math>, ...
[[File:Fixed-point binary representation.svg|centre|vignette|upright=2.5|Nombres à virgule fixe.]]
Cette méthode est assez peu utilisée de nos jours, quoiqu'elle puisse avoir quelques rares applications relativement connue. Un bon exemple est celui des banques : les sommes d'argent déposées sur les comptes ou transférées sont codés en virgule fixe. Les sommes manipulées par les ordinateurs ne sont pas exprimées en euros, mais en centimes d'euros. Et c'est une forme de codage en virgule fixe dont le facteur de conversion est égal à 100. La raison de ce choix est que les autres méthodes de codage des nombres à virgule peuvent donner des résultats imprécis : il se peut que les résultats doivent être tronqués ou arrondis, suivant les opérandes. Cela n'arrive jamais en virgule fixe, du moins quand on se limite aux additions et soustractions.
===Les nombres à virgule non-représentables en binaire===
Quel que soit la façon de représenter les nombres à virgules, il existe des nombres qui ne peuvent être représentés de manière exacte, à savoir avec un nombre fini de décimales après la virgule. En soi, ce n'est pas spécifique au binaire, on a la même chose en décimal. Par exemple, la fraction 1/3 en décimal s'écrit 0.3333333..., avec une infinité de 3. La même chose existe en binaire, mais pour des nombres différents.
Déjà, évacuons le cas des nombres irrationnels, à savoir les nombres qui ne peuvent pas s'écrire sous la forme d'une fraction, comme <math>pi</math> ou <math>e</math>. Ils ont une infinité de décimales que ce soit en binaire, en décimal, en hexadécimal, ou autre. Ils ne sont pas représentables avec un nombre fini de décimales quelle que soit la base utilisée. Concentrons-nous sur des nombres qui ne sont pas dans ce cas, et qui ont un nombre fini ou infini de décimales.
Le passage de la base 10 à la base 2 change le nombre de décimales, et peut faire passer d'un nombre fini de décimales à un nombre infini. Par exemple, <math>\frac{1}{5} = 0.2</math> est représenté en binaire avec une séquence infinie de bits : <math>0.001100110011...</math>. Les nombres étant en base binaire et représenté avec un nombre limité de bits, il existe certains nombres décimaux triviaux qui ne sont pas représentables avec un nombre fini de décimales.
Et la réciproque n'est pas vraie : tout nombre binaire avec un nombre fini de décimales en binaire est représentable avec un nombre fini de décimales en base 10. Ceci est lié à la décomposition en facteur premier des bases utilisées :
* <math>2 = 2^1</math>
* <math>10 = 2^1 \times 5^1</math>
Le nombre 10 possède tous les facteurs premiers de 2, mais 2 n'a pas de 5 dans sa décomposition.
==Les nombres flottants==
Les nombres à virgule fixe ont aujourd'hui été remplacés par les '''nombres à virgule flottante''', où le nombre de chiffres après la virgule est variable. Le codage d'un nombre flottant est basée sur son ''écriture scientifique''. Pour rappel, en décimal, l’écriture scientifique d'un nombre consiste à écrire celui-ci comme un produit entre un nombre <math>a</math> et une puissance de 10, avec <math>a</math> étant compris entre 1 et 9. Ce qui donne :
: <math>a \times 10^e</math>, avec <math>a \in [1, 10 [</math>
Le nombre <math>a</math> est appelé le ''significande'' et il est compris entre 1 (inclus) et 10 (exclu). Cette contrainte garantit que l'écriture scientifique d'un nombre est unique, qu'il n'y a qu'une seule façon d'écrire un nombre en notation scientifique. Pour cela, on impose le nombre de chiffre à gauche de la virgule et le plus simple est que celui-ci soit égal à 1. Mais il faut aussi que celui-ci ne soit pas nul. En effet, si on autorise de mettre un 0 à gauche de la virgule, il y a plusieurs manières équivalentes d'écrire un nombre. Ces deux contraintes font que le significande doit être égal ou plus grand que 1, mais strictement inférieur à 10. Par contre, on peut mettre autant de décimales que l'on veut.
En binaire, c'est la même chose, mais avec une puissance de deux. Cela implique de modifier la puissance utilisée : au lieu d'utiliser une puissance de 10, on utilise une puissance de 2.
: <math>a \times 2^e</math>, avec <math>a \in [1, 2 [</math>
Le significande est aussi altéré, au même titre que la puissance, même si les contraintes sont similaires à celles en base 10. En effet, le nombre <math>a</math> ne possède toujours qu'un seul chiffre à gauche de la virgule, comme en base 10. Vu que seuls deux chiffres sont possibles (0 et 1) en binaire, on s'attend à ce que le chiffre situé à gauche de la virgule soit un zéro ou un 1. Mais rappelons que le chiffre à gauche doit être non-nul, pour les mêmes raisons qu'en décimal. En clair, le significande a forcément un bit à 1 à gauche de la virgule. Pour récapituler, l'écriture scientifique binaire d'un nombre consiste à écrire celui-ci sous la forme :
: <math>1,m \times 2^e</math>, avec <math>m \in [0, 1 [</math>
La partie fractionnaire du nombre <math>a</math>, qu'on appelle la '''mantisse'''.
[[File:Mantissa.svg|centre|vignette|Écriture scientifique (anglais).]]
Pour déterminer l'écriture scientifique en binaire d'un nombre quelconque, la procédure dépend de la valeur du nombre en question. Tout dépend s'il est dans l'intervalle <math>[1, 2 [</math>, au-delà de 2 ou en-dessous de 1.
* Pour un nombre entre 1 (inclus) et 2 (exclu), il suffit de le traduire en binaire. Son exposant est 0.
* Pour un nombre au-delà de 2, il faut le diviser par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant est alors le nombre de fois qu'il a fallu diviser par 2.
* Pour un nombre plus petit que 1, il faut le multiplier par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant se calcule en prenant le nombre de fois qu'il a fallu multiplier par 2, et en prenant l'opposé (en mettant un signe - devant le résultat).
===Le codage des nombres flottants et la norme IEEE 754===
La '''virgule flottante''' encode l'écriture scientifique d'un nombre/ Elle utilise pour cela trois données : la mantisse, l'exposant et un bit de signe. Les trois sont concaténés dans un seul "nombre", plus précisément une suite de bits qui est découpée en un bit de signe, un entier qui encode l'exposant, et un entier pour la mantisse.
[[File:General floating point frac.svg|centre|vignette|upright=2|Format Général d'un nombre flottant.]]
[[File:General floating point.svg|centre|vignette|upright=2|Format Général d'un nombre flottant, plus détaillé.]]
Voici à peu-près comment les nombres flottants sont répartis sur une ligne numérique. Vous remarquerez que les flottants sont de plus en plus éloignés au fur et à mesure qu'on s'éloigne de zéro. La raison est l'encodage avec un exposant et une mantisse. L'écart entre deux flottants reste stable pour un même exposant, mais il y a une cassure quand on passe d'un exposant au suivant.
[[File:FloatingPointPrecisionAugmented.png|centre|vignette|upright=3|Floating Point Precision.]]
Le standard actuel pour la virgule flottante est la '''norme IEEE 754'''. Cette norme va (entre autres) définir quatre types de flottants différents, qui utilisent des exposants/mantisses de taille différente. Les deux formats les plus courants sont les flottants '''simple précision''' et '''double précision'''. Les premiers sont codés sur 32 bits, les seconds sur 64 bits. Il y a aussi des formats de '''précision étendus''', codés sur 80 bits ou plus.
[[File:Single double extended2.gif|centre|vignette|upright=2.5|Formats des nombres flottants IEEE754.]]
===L'encodage de la mantisse et de l'exposant===
L'exposant peut être aussi bien positif que négatif (pour permettre de coder des nombres très petits). Pour cela, il est encodé en représentation par excès. Pour un exposant de n bits, le biais est égal à <math>2^{n-1} - 1</math>.
L'encodage de la mantisse a lui aussi ses subtilités. En effet, la mantisse est un nombre à virgule, avec une partie entière et une partie fractionnaire. Le tout est encodé en virgule fixe, reste à choisir son encodage exact. Pour simplifier l'encodage, l'exposant est choisir de manière à ce qu'il y ait un seul chiffre avant la virgule, ce qui permet de coder la partie entière sur 1 bit. En clair, il faut encoder le bit à gauche de la virgule, puis ceux à droite. Les anciens PC utilisaient cette solution, du temps de la FPU x87. Ils géraient nativement des nombres flottants de 80 bits, appelés '''flottants étendus x87'''. Ils utilisaient une mantisse codée sur 64 bits, dont un bit pour la partie entière, et 63 pour la partie fractionnaire.
[[File:X86 Extended Floating Point Format.svg|centre|vignette|upright=2|Flottants étendus des anciens CPU X86.]]
Une simplification plus importante choisir l'exposant de manière à ce que la partie entière vaut 1, systématiquement. En faisant cela, on n'a plus à encoder la partie entière, pas besoin de lui réserver un bit. La mantisse encode donc uniquement la partie fractionnaire, la partie entière n'est pas encodée. Une telle méthode est donc plus efficace. A quelques détails près : de nombreuses valeurs ne peuvent pas être encodées facilement. Par exemple, le zéro ne peut pas être encodé avec cette technique. Et c'est là un des gros défaut de cette technique : ne pas pouvoir encoder le zéro est un énorme problème !
Pour éviter ce défaut, les nombres flottants utilisent une technique : la partie entière n'est pas codée directement. La partie entière vaut 0 pour certains flottants, elle vaut 1 pour d'autres. Vu que la partie entière fait un bit, mais n'est pas encodée, elle est aussi appelé le '''bit implicite'''. Déterminer le bit implicite demande de regarder l'exposant . Typiquement, si l'exposant vaut zéro, alors la partie entière vaut 0. Sinon, elle vaut 1.
Une telle solution permet d'encoder un zéro, mais aussi des nombres flottants très petits. Ces derniers ne sont pas codés en respectant les règles pour le significande et ont un 0 à gauche de la virgule. Ils sont appelés les '''dénormaux''', aussi nommés flottants dénormalisés. Les dénormaux sont des nombres flottants où l'exposant est le plus petit possible. Le '''zéro''' est un dénormal particulier dont la mantisse est nulle. Au fait, remarquez que le zéro est codé deux fois à cause du bit de signe : on se retrouve avec un -0 et un +0.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur minimale (0 en binaire) || Mantisse différente de zéro (dénormal) ou égale à zéro (zéro)
|}
===Les spécificités de la norme IEEE754===
Les nombres flottants IEE754 ont quelques spécificités qu'il vaut mieux voir à part. Premièrement, ils n'encodent pas la partie entière d'un flottant, la mantisse encode uniquement la partie fractionnaire. La technique précédente, qui utilise l'exposant pour déterminer le bit implicite, est utilisée. Les flottants IEE754 ont une partie entière de la mantisse qui vaut 1, sauf pour quelques rares dénormaux qui font exceptions. Les dénormaux sont des nombres flottants où l'exposant est le plus petit possible.
Le '''zéro''' est un dénormal particulier dont la mantisse est nulle. Au fait, remarquez que le zéro est codé deux fois à cause du bit de signe : on se retrouve avec un -0 et un +0.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur minimale (0 en binaire) || Mantisse différente de zéro (dénormal) ou égale à zéro (zéro)
|}
Fait étrange, la norme IEEE754 permet de représenter '''l'infini''', aussi bien en positif qu'en négatif. Celui-ci est codé en mettant l'exposant à sa valeur maximale et la mantisse à zéro. Et le pire, c'est qu'on peut effectuer des calculs sur ces flottants infinis. Mais cela a peu d'utilité.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse égale à zéro
|}
Mais malheureusement, l'invention des flottants infinis n'a pas réglé tous les problèmes. Par exemple, quel est le résultat de <math>\infty - \infty</math> ? Ou encore <math>0 - \infty</math> ? Autant prévenir tout de suite : mathématiquement, on ne peut pas savoir quel est le résultat de ces opérations. Pour pouvoir résoudre ces calculs, il a fallu inventer un nombre flottant qui signifie « je ne sais pas quel est le résultat de ton calcul pourri ». Ce nombre, c'est '''NaN'''.
NaN est l'abréviation de ''Not A Number'', ce qui signifie : n'est pas un nombre. Un NaN a un exposant dont la valeur est maximale, mais une mantisse différente de zéro. Pour être plus précis, il existe différents types de NaN, qui diffèrent par la valeur de leur mantisse, ainsi que par les effets qu'ils peuvent avoir. Malgré son nom explicite, on peut faire des opérations avec NaN, mais cela ne sert pas vraiment à grand chose : une opération arithmétique appliquée avec un NaN aura un résultat toujours égal à NaN.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse différente de zéro
|}
Au-delà du support de quelques flottants spéciaux, la norme impose une gestion des arrondis ou erreurs, qui arrivent lors de calculs particuliers. En voici la liste :
{|class="wikitable"
|-
! Nom de l’exception
! Description
|-
! Invalid operation
| Opération qui produit un NAN. Elle est levée dans le cas de calculs ayant un résultat qui est un nombre complexe, ou quand le calcul est une forme indéterminée. Pour ceux qui ne savent pas ce que sont les formes indéterminées, voici en exclusivité la liste des calculs qui retournent NaN : <math>\infty - \infty</math>, <math>0 \times \infty</math>, <math>0^0</math>, <math>1^{\infty}</math>, <math>\infty^0</math>.
|-
! Overflow
| Résultat trop grand pour être stocké dans un flottant. Le plus souvent, on traite l'erreur en arrondissant le résultat en vue de la taille de la mantisse;
|-
! Underflow
| Pareil que le précédent, mais avec un résultat trop petit. Le plus souvent, on traite l'erreur en arrondissant le résultat vers 0.
|-
! Division par zéro
| Le nom parle de lui-même. La réponse la plus courante est de répondre + ou - l'infini.
|-
! '''Inexact'''
| Le résultat ne peut être représenté par un flottant et on doit l'arrondir.
|}
La gestion des arrondis pose souvent problème. Pour donner un exemple, on va prendre le nombre 0,1. En binaire, ce nombre s'écrit comme ceci : 0,1100110011001100... et ainsi de suite jusqu'à l'infini. Notre nombre utilise une infinité de décimales. Bien évidemment, on ne peut pas utiliser une infinité de bits pour stocker notre nombre et on doit impérativement l'arrondir. Comme vous le voyez avec la dernière exception, le codage des nombres flottants peut parfois poser problème : dans un ordinateur, il se peut qu'une opération sur deux nombres flottants donne un résultat qui ne peut être codé par un flottant. On est alors obligé d'arrondir ou de tronquer le résultat de façon à le faire rentrer dans un flottant. Pour éviter que des ordinateurs différents utilisent des méthodes d'arrondis différentes, on a décidé de normaliser les calculs sur les nombres flottants et les méthodes d'arrondis. Pour cela, la norme impose le support de quatre modes d'arrondis :
* Arrondir vers + l'infini ;
* vers - l'infini ;
* vers zéro ;
* vers le nombre flottant le plus proche.
===Les nombres flottants logarithmiques===
Les '''nombres flottants logarithmiques''' sont des nombres flottants composés d'un bit de signe et d'un exposant, sans mantisse. La mantisse est totalement implicite : tous les flottants logarithmiques ont la même mantisse, qui vaut 1. Il ne reste que l'exposant, qui est tout simplement le logarithme en base 2 du nombre encodé, d'où le nom de codage flottant logarithmique donné à cette méthode. Attention toutefois : l'exposant est ici un nombre fractionnaire, codé en virgule fixe. Le choix d'un exposant fractionnaire permet de représenter pas mal de nombres de taille diverses.
{|class="wikitable"
|-
!
! Bit de signe
! Exposant
|-
! Représentation binaire
| 0
| 01110010101111
|-
! Représentation décimale
| +
| 1040,13245464
|}
L'utilité de cette représentation est de simplifier certains calculs, comme les multiplications, divisions, puissances, etc. En effet, les mathématiques nous disent que le logarithme d'un produit est égal à la somme des logarithmes : <math>\ln{(a \times b)} = \ln{a} + \ln{b}</math>. Or, il se trouve que les ordinateurs sont plus rapides pour faire des additions/soustractions que pour faire des multiplications/divisions. Donc, la représentation logarithmique permet de remplacer les multiplications/divisions par des additions/soustractions, plus simples et plus rapides pour l'ordinateur.
Évidemment, les applications des flottants logarithmiques sont rares, limitées à quelques situations bien précises (traitement d'image, calcul scientifique spécifiques).
==Les encodages binaires alternatifs==
Outre le binaire que nous venons de voir, il existe d'autres manières de coder des nombres avec des bits. Et nous allons les aborder dans cette section. Parmi celle-ci, nous parlerons du code Gray, de la représentation ''one hot'', du unaire et du ternaire. Nous en parlons car elles seront utiles dans la suite du cours, bien que de manière assez limitée. Autant nous passerons notre temps à parler du binaire normal, autant les représentations que nous allons voir sont aujourd'hui utilisées dans des cas assez spécifiques. Et elles sont plus courantes que vous ne le pensez.
===Le code Gray===
Le '''code Gray''' est un encodage binaire qui a une particularité très intéressante : deux nombres consécutifs n'ont qu'un seul bit de différence. Pour exemple, voici ce que donne le codage des 8 premiers entiers sur 3 bits :
{|class="wikitable"
! Décimal
! Binaire naturel
! Codage Gray
|-
|0||000||000
|-
|1||001||001
|-
|2||010||011
|-
|3||011||010
|-
|4||100||110
|-
|5||101||111
|-
|6||110||101
|-
|7||111||100
|}
Les utilisations du code Gray sont assez nombreuses, bien qu'on n'en croise pas tous les jours. Un exemple : le code Gray est très utile dans certains circuits appelés les compteurs, qui mémorisent un nombre et l'incrémentent (+1) ou le décrémentent (-1) suivant les besoins de l'utilisateur. Il est aussi utilisé dans des scénarios difficiles à expliquer ici (des codes correcteurs d'erreur ou des histoires de passages de domaines d'horloge). Mais le point important est que ce code sera absolument nécessaire dans quelques chapitres, quand nous parlerons des tables de Karnaugh, un concept important pour la conception de circuits électroniques. Ne passez pas à côté de cette section.
Pour construire ce code Gray, on peut procéder en suivant plusieurs méthodes, les deux plus connues étant la méthode du miroir et la méthode de l'inversion.
[[File:Binary-reflected Gray code construction.svg|vignette|Construction d'un code gray par la méthode du miroir.]]
La méthode du miroir est relativement simple. Pour connaître le code Gray des nombres codés sur n bits, il faut :
* partir du code Gray sur n-1 bits ;
* symétriser verticalement les nombres déjà obtenus (comme une réflexion dans un miroir) ;
* rajouter un 0 au début des anciens nombres, et un 1 au début des nouveaux nombres.
Il suffit de connaître le code Gray sur 1 bit pour appliquer la méthode : 0 est codé par le bit 0 et 1 par le bit 1.
Une autre méthode pour construire la suite des nombres en code Gray sur n bits est la méthode de l'inversion. Celle-ci permet de connaître le codage du nombre n à partir du codage du nombre n-1, comme la méthode du dessus. On part du nombre 0, systématiquement codé avec uniquement des zéros. Par la suite, on décide quel est le bit à inverser pour obtenir le nombre suivant, avec la règle suivante :
* si le nombre de 1 est pair, il faut inverser le dernier chiffre.
* si le nombre de 1 est impair, il faut localiser le 1 le plus à droite et inverser le chiffre situé à sa gauche.
Pour vous entraîner, essayez par vous-même avec 2, 3, voire 5.
===Les représentations ''one-hot'' et unaire===
La représentation ''one-hot'' et la représentation unaire sont deux représentations assez liées, mais légèrement différentes.
La '''représentation unaire''' est la représentation en base 1. Avec elle, le zéro est codé en mettant tous les bits du nombre à zéro, la valeur 1 est encodée avec la valeur 000...0001, le deux avec 000...0011, le trois avec 000...0111, etc. Pour résumer, le nombre N est encodé en mettant les N bits de poids faible à 1, à l'exception du zéro qui est encodé...par un zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! Unaire
|-
| 0 || 000 || 0000 0000
|-
| 1 || 001 || 0000 0001
|-
| 2 || 010 || 0000 0011
|-
| 3 || 011 || 0000 0111
|-
| 4 || 100 || 0000 1111
|-
| 5 || 101 || 0001 1111
|-
| 6 || 110 || 0011 1111
|-
| 7 || 111 || 0111 1111
|-
| 8 || 1000 || 1111 1111
|}
Pour convertir un nombre codé en unaire vers le binaire, il suffit de compter le nombre de 1. C'est ni plus ni moins ce que fait l'opération dite de ''population count'' dont nous avons parlé plus haut. La représentation unaire permet d'encoder N+1 valeurs sur N bits, car le zéro est encodé à part. Une valeur pour le zéro, plus une par bit.
Avec la '''représentation ''one-hot''''', un nombre est codé sur N bits, dont un seul bit est à 1. La position du 1 dans le nombre indique sa valeur. Précisément, la valeur encodée est égale au poids du bit à 1. En faisant cela, le zéro est codé en mettant le bit de poids faible à 1, la valeur 1 est codée avec les bits 00...00010, la valeur 2 est codée par les bits 00...000100, etc.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Elle permet d'encoder N valeurs différentes avec N bits, zéro inclus. On perd une valeur par rapport à l'unaire, car le zéro n'et pas encodé à part du reste. La traduction du binaire vers le ''one-hot'' est réalisé par un circuit nommé décodeur, que nous verrons d'ici quelques chapitres. De même, la traduction d'un nombre encodé en ''one-hot'' vers le binaire est réalisée par un circuit électronique appelé l'encodeur, qui est en quelque sorte l'inverse de l'encodeur. Les deux circuits sont très utilisés en électronique, ce sont des circuits de base, que nous n'aurons de cesse d'utiliser pour fabriquer d'autres circuits.
L'utilité de ces deux représentations n'est pas évidente. Mais sachez qu'elle le deviendra quand nous parlerons des circuits appelés les "compteurs", tout comme ce sera le cas pour le code Gray. Elles sont très utilisées dans des circuits appelés des machines à état, qui doivent incorporer des circuits compteurs efficients. Et ces représentations permettent d'avoir des circuits pour compter qui sont très simples, efficaces, rapides et économes en circuits électroniques. La représentation unaire sera aussi utile à la toute fin du cours, dans les chapitres liés à l'exécution dans le désordre. Il en sera fait référence quand nous parlerons de fenêtres d'instruction, d'émission dans l'ordre, de ''scoreboarding'', etc.
Il faut aussi noter qu'il a existé des ordinateur qui manipulaient des nombres codés en unaire. Enfin presque, la représentation utilisée était proche de l'unaire, mais ressemblait fortement. De tels ordinateurs étaient appelés des ''ordinateurs stochastiques'' et nous les aborderons à la toute fin de ce wikilivre, dans un chapitre annexe portant sur les ordinateurs ternaires (qui comptent en base 3) et unaires.
==Les encodages hybrides entre décimal et binaire==
Pour terminer, nous allons voir les représentations qui mixent binaire et décimal. Il s'agit en réalité d'encodage qui permettent de manipuler des nombres codés en décimal, mais les chiffres sont codés en utilisant des bits. L'encodage ''Binary Coded Decimal'' est le plus connu de cette catégorie, mais il y en a quelques autres, qui sont moins connus. De telles représentations étaient très utilisées au début de l'informatique, mais sont aujourd'hui tombées en désuétude. Pour désigner ces encodages, nous parlerons d''''encodages bit-décimaux''' : décimaux pour préciser qu'ils encodent des nombres codés en décimal, bit pour préciser que les chiffres sont codés avec des bits.
De tels encodages étaient utilisés sur les tous premiers ordinateurs pour faciliter le travail des programmeurs, mais aussi sur les premières calculettes. Ils sont utiles dans les applications où on doit manipuler chaque chiffre décimal séparément des autres. Un exemple classique est celui d'une horloge digitale. Ils ont aussi l'avantage de bien se marier avec les nombres à virgule. Les représentations des nombres à virgule fixe ou flottante ont le défaut que des arrondis peuvent survenir. Par exemple, la valeur 0,2 est codée comme suit en binaire normal : 0.00110011001100... et ainsi de suite jusqu’à l'infini. Avec les encodages bit-décimaux, on n'a pas ce problème, 0,2 étant codé 0000 , 0010.
Il a existé des ordinateurs qui travaillaient uniquement avec de tels encodages, appelés des '''ordinateurs décimaux'''. Ils étaient assez courants entre les années 60 et 70, même s'ils ne représentent pas la majorité des architectures de l'époque. Avec eux, la mémoire n'était pas organisée en octets, mais elle stockait des chiffres décimaux codés sur 5-6 bits. Le processeur faisait des calculs sur des chiffres bit-décimaux directement.
Leur grand avantage est leur très bonne performance dans les taches de bureautique, de comptabilité, et autres. Les processeurs de l'époque recevaient des entiers codés en bit-décimal de la part des entrées-sorties, et devaient les traiter. Les processeurs binaires devaient faire des conversions décimal-binaire pour communiquer avec les entrées-sorties, mais pas les processeurs décimaux. Le gain en performance pouvait être substantiel dans certaines applications.
Les ordinateurs décimaux se classent en deux sous-types bien précis. Les premiers gèrent des entiers qui ont un nombre de chiffres fixes. Par exemple, l'IBM 7070 gérait des entiers de 10 chiffres, plus un signe +/- pour les entiers signés. Le processeur faisait les calculs directement en bit-décimal, il gérait des entiers faisant environ 10-15 chiffres décimaux et savait faire des calculs avec de tels nombres. Le second sous-type effectue les calculs chiffre par chiffre et géraient des nombres de taille variable, sans limite de chiffres ! Nous reparlerons de ces derniers dans un chapitre ultérieur, quand nous parlerons de la différence entre byte et mot. Pour le moment, ne gardez à l'esprit que les processeurs gérant un nombre fixe de chiffres décimaux, plus simples à comprendre.
===Le ''Binary Coded Decimal''===
Le '''''Binary Coded Decimal''''', abrévié BCD, est une représentation qui mixe binaire et décimal. Avec cette représentation, les nombres sont écrits en décimal, comme nous en avons l'habitude dans la vie courante, sauf que chaque chiffre décimal est directement traduit en binaire sur 4 bits. Prenons l'exemple du nombre 624 : le 6, le 2 et le 4 sont codés en binaire séparément, ce qui donne 0110 0010 0100.
{| class="wikitable"
|+ Codage BCD
|-
! Nombre encodé (décimal)
! scope="col" colspan="4" | BCD
|-
| 0 || 0 || 0 || 0 || 0
|-
| 1 || 0 || 0 || 0 || 1
|-
| 2 || 0 || 0 || 1 || 0
|-
| 3 || 0 || 0 || 1 || 1
|-
| 4 || 0 || 1 || 0 || 0
|-
| 5 || 0 || 1 || 0 || 1
|-
| 6 || 0 || 1 || 1 || 0
|-
| 7 || 0 || 1 || 1 || 1
|-
| 8 || 1 || 0 || 0 || 0
|-
| 9 || 1 || 0 || 0 || 1
|}
On peut remarquer que 4 bits permettent de coder 16 valeurs, là où il n'y a que 10 chiffres. Dans le BCD proprement dit, les combinaisons de bits qui correspondent à 10, 11, 12, 13, 14 ou 15 ne sont tout simplement pas prises en compte. Sur quelques ordinateurs, ces combinaisons codent des chiffres décimaux en double : certains chiffres pouvaient être codés de deux manières différentes. Il est aussi possible d'utiliser ces valeurs pour coder quelque chose d'autre que des chiffres. Par exemple, il est possible de les utiliser pour coder un signe + ou -, afin de gérer les entiers relatifs. Une autre possibilité, complémentaire de la précédente, utilise ces valeurs en trop pour coder une virgule, afin de gérer les nombres non-entiers. Les possibilités sont nombreuses.
Le support du BCD implique souvent que le processeur supporte des '''opérations BCD''', à savoir des opérations capables de travailler sur des opérandes en BCD et de donner un résultat en BCD. Il faut bien faire la différence entre les opérations en binaire et les opérations en BCD. Par exemple, on n'effectue pas une addition de la même manière en binaire et en décimal/BCD, même si les grandes lignes sont presque identiques. Les différences font que les processeurs doivent avoir des opérations différentes pour les deux encodages, de la même manière que les processeurs gèrent les opérations sur les flottants et les entiers séparément.
Le support du codage BCD est abandonné de nos jours, c'est surtout quelque chose qu'on trouve sur les anciens processeurs. Les architectures 8 et 16 bits supportaient à la fois des opérations binaires et des opérations BCD. Mais il a aussi existé une méthode intermédiaire, qui utilisait des additions binaires normales sur des opérandes BCD. L'idée est que le résultat de l'addition est incorrect, car les valeurs 10 à 15 peuvent apparaître comme chiffre dans le résultat. Mais on peut le corriger pour obtenir le résultat exact en BCD. Pour résumé, les processeurs faisaient des additions en binaire, et corrigeaient le résultat avec une opération spécifique pour obtenir un résultat en BCD. Nous en reparlerons dans le chapitre sur le langage machine et l'assembleur, dans lequel nous étudierons les différentes opérations que supporte un processeur.
===Les encodages compacts du BCD===
Des variantes du BCD visent à réduire le nombre de bits utilisés pour encoder un nombre décimal. Nous allons les appeler les '''encodages BCD compacts'''. Avec certaines variantes, on peut utiliser seulement 7 bits pour coder deux chiffres décimaux, au lieu de 8 bits en BCD normal. Idem pour les nombres à 3 chiffres décimaux, qui prennent 10 bits au lieu de 12. Cette économie est réalisée par une variante du BCD assez compliquée, appelée l''''encodage Chen-Ho'''. Une alternative, appelée le ''Densely Packed Decimal'', arrive à une compression identique, mais avec quelques avantages au niveau de l'encodage. Ces encodages sont cependant assez compliqués à expliquer, surtout à ce niveau du cours, aussi je me contente de simplement mentionner leur existence.
Les encodages BCD compacts sont utilisés par les programmeurs pour stocker des données dans un fichier, en mémoire, mais guère plus. Ils ne sont pas gérés par le processeur directement, on ne peut pas faire de calculs avec, le processeur ne gére pas d'opération BCD supportant de tels encodages. C'est théoriquement possible, mais ça n'a jamais été implémenté dans un processeur, le cout en circuit n'en valait pas la chandelle. Pour faire des calculs sur des nombres en BCD compact, on doit les décompresser et les convertir en BCD normal ou en binaire, puis faire les calculs avec des opérations BCD/binaire usuelles.
===L'encodage ''Excess-3''===
La représentation '''''Excess-3''''' (XS-3) est une variante du BCD, qui a autrefois était utilisée sur d'anciens ordinateurs décimaux. Il s'agit d'une sorte d'hybride entre une représentation par excès et BCD. Chaque chiffre décimal est codé sur plusieurs bits en utilisant une représentation binaire par excès. Le biais est de 3 pour la représentation XS-3, il en existe des variantes avec un excès de 4, 5, mais elles sont moins utilisées. Avec elle, la conversion d'un chiffre décimal se fait comme suit : on prend le chiffre décimal, on ajoute 3, puis on traduit en binaire.
{| class="wikitable"
|+ Codage en ''Excess-3''
|-
! Décimal
! scope="col" colspan="4" | Binaire
|-
| -3 || 0 || 0 || 0 || 0
|-
| -2 || 0 || 0 || 0 || 1
|-
| -1 || 0 || 0 || 1 || 0
|-
| 0 || 0 || 0 || 1 || 1
|-
| 1 || 0 || 1 || 0 || 0
|-
| 2 || 0 || 1 || 0 || 1
|-
| 3 || 0 || 1 || 1 || 0
|-
| 4 || 0 || 1 || 1 || 1
|-
| 5 || 1 || 0 || 0 || 0
|-
| 6 || 1 || 0 || 0 || 1
|-
| 7 || 1 || 0 || 1 || 0
|-
| 8 || 1 || 0 || 1 || 1
|-
| 9 || 1 || 1 || 0 || 0
|-
| 10 || 1 || 1 || 0 || 1
|-
| 11 || 1 || 1 || 1 || 0
|-
| 12 || 1 || 1 || 1 || 1
|}
L'avantage de cette représentation est que l'on peut facilement calculer les soustractions en utilisant une méthode bien précise (celle du complément à 10, je ne détaille pas plus). Le défaut est que le calcul des additions est légèrement plus complexe.
L'XS-3 a été utilisé sur quelques ordinateurs décimaux assez anciens, notamment sur l'UNIVAC I et II.
===Les codes bi-quinaires===
Les '''codes bi-quinaires''' utilisent un mélange entre encodage binaire, encodage ''one-hot'' et encodage décimal. L'idée de base derrière ces codes est d'utiliser la représentation ''one-hot'' pour coder un chiffre BCD. Le problème est que cela demande d'utiliser 10 bits pour encoder un chiffre BCD, ce qui est beaucoup. L'encodage bi-quinaire optimise le tout de manière à diviser par deux le nombre de bits requis pour l'encodage.
Pour simplifier, un chiffre encodé en bi-quinaire est composé de deux parties : une partie binaire de un bit, suivie par d'une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits pour encoder les valeurs 0, 1, 2, 3 et 4. La partie binaire indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9 : 0 à 4 si la partie binaire vaut 0, 5 à 9 si elle vaut 1. La valeur du chiffre se calcule comme suit :
* si la partie binaire vaut 0, elle est égale à la partie quinaire ;
* si la partie binaire vaut 1, on ajoute 5 à la partie quinaire.
L'originalité de ce codage est qu'il permet de facilement compter sur ses doigts : on a deux mains, cinq doigts. De nombreuses langues utilisent ce système pour coder des nombres, comme le Wolof. Et ce système est utilisé dans certains pays pour compter sur ses doigts, voire dans la vie de tous les jours.
[[File:Code Biquinaer.svg|vignette|Code bi-quinaire où partie binaire et quinaire sont encodées en représentation ''one-hot''.]]
L'encodage abordé au-dessus est une version simplifiée de l'encodage bi-quinaire, il en existe d'autres variantes. Le bi-quinaire vu juste au-dessus encode la partie binaire en binaire, et la partie quinaire en ''one-hot''. Mais il est aussi possible d'inverser l'encodage, à savoir encoder la partie binaire avec une représentation ''one-hot'', alors que la partie bi-quinaire est encodée en binaire normal.
Cependant, la version la plus intéressante utilise l'encodage ''one-hot'' pour les deux ! Elle est illustrée ci-contre. Avec cette dernière, un chiffre BCD est encodé sur 7 bits, dont 2 sont obligatoirement à 1 et les 5 restants sont à 0. Cette propriété permet de détecter qu'un bit a été inversé suite à une erreur de transmission ou un problème de stockage. Si un bit est inversé, le résultat aura 1 bit à 1 et 6 à 0, ou alors 3 bits à 1 et 4 à 0, ce qui ne colle pas. Le résultat après inversion ne donnera pas un chiffre bi-quinaire valide.
Une autre variante du bi-quinaire a été utilisée sur l'IBM 1401. Avec elle, la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre encodé par la partie quinaire. L'avantage de ce système est que la gestion des retenues lors des additions était grandement simplifiée.
Le bi-quinaire et ses variantes ont été utilisés sur d'anciens ordinateurs, dont l'IBM 650, l'UNIVAC Solid State et le UNIVAC LARC. La plupart utilisaient des variantes de l'encodage bi-quinaire proprement dit. Pour rentrer vraiment dans le détail, voici les encodages utilisés sur ces machines. La lecture est facultative, il est possible de passer directement à la section suivante :
{| class="wikitable"
|-
! !! IBM 650 !! Remington Rand 409 !! UNIVAC Solid State !! UNIVAC LARC
|-
| Chiffre || 1357-9 bits || 05-01234 || p-5-421 bits || p-5-qqq bits
|-
| 0 || 10-10000 || 0000-0 || 1-0-000 || 1-0-000
|-
| 1 || 10-01000 || 1000-0 || 0-0-001 || 0-0-001
|-
| 2 || 10-00100 || 1000-1 || 0-0-010 || 1-0-011
|-
| 3 || 10-00010 || 0100-0 || 1-0-011 || 0-0-111
|-
| 4 || 10-00001 || 0100-1 || 0-0-100 || 1-0-110
|-
| 5 || 01-10000 || 0010-0 || 0-1-000 || 0-1-000
|-
| 6 || 01-01000 || 0010-1 || 1-1-001 || 1-1-001
|-
| 7 || 01-00100 || 0001-0 || 1-1-010|| 0-1-011
|-
| 8 || 01-00010 || 0001-1 || 0-1-011 || 1-1-111
|-
| 9 || 01-00001 || 0000-1 || 1-1-100|| 0-1-110
|}
==Annexe : pourquoi l'octet existe==
Nous avons vu plus haut que les nombres encodés en binaire sont découpés en octets, des paquets de 8 bits. Et les octets sont présents partout dans la conception d'un ordinateur. Par exemple, la mémoire RAM d'un ordinateur moderne est organisée en octets. Elle contient des cases mémoire, chacune contenant un octet. Les octets sont numérotés, ce qui permet de sélectionner un octet parmi tous les autres. La capacité de la mémoire est le nombre d'octets qu'elle contient, elle s'exprime en kilo-octets (1024 octets), mébi-octets (1024 kilo-octets), gibi-octets (1024 mébi-octets). Les octets sont donc présents partout dans l'informatique moderne.
{|class="wikitable"
|+ Contenu de la RAM, simplifié
|-
! Numéro
! Octet
|-
! 0
| 0000 1111
|-
! 1
| 0101 0101
|-
! 2
| 0111 0110
|-
! 3
| 0011 1010
|-
! 4
| 1001 1001
|-
! ...
| ...
|-
! 1023
| 0110 1010
|-
! 1024
| 0001 1110
|}
Mais pourquoi est-ce que les nombres encodés en binaire sont souvent découpés en octets ? Pourquoi ne pas utiliser des groupes de 3, 5, 6, 7, 9, 10 bits ? La question est d'autant plus légitime que d'anciens ordinateurs faisaient ainsi. Leurs mémoires étaient découpé en cases mémoire, mais chacune faisait 7 ou 9 bits, rien d'impossible à cela. À vrai dire, l'usage des octets au tout début de l'informatique était assez rare. Il y a eu un basculement progressif vers l'octet pendant les années 80-90. Reste à expliquer pourquoi. Si on devait résumer, les deux raisons principales sont le support du BCD et le support des caractères pour le texte. Voyons d'abord l'encodage des caractères en binaire.
===L'encodage du texte : ASCII et autres encodages===
Pour les caractères, les encodages se sont concentrés sur l'encodage des alphabets occidentaux. Ils demandaient d'encoder au minimum toutes les lettres, en majuscules et minuscules, plus les chiffres, une dizaine de caractères pour la ponctuation. À cela, il faut ajouter des symboles utiles comme %, µ, $, £, #, {} et bien d'autres. Au minimum, cela fait au minimum 52 lettres (26 lettres doublées pour les majuscules) + 10 chiffres + une dizaine de caractères pour la ponctuation : cela fait plus de 72 valeurs à encoder. Pour encoder cela en binaire, le minimum est de 7 bits, ce qui permet d'encoder 128 caractères : les 72 caractères minimum, plus quelques autres. L'encodage ASCII utilisait d'ailleurs 7 bits pour encoder des nombres.
On pourrait croire que c'était une raison suffisante pour utiliser des groupes de 7 bits. Mais il y a quelques problèmes avec cela. Premièrement, les encodages 7 bits pour le texte marchent bien pour gérer la langue anglaise et la plupart des langues occidentales, mais d'autres langues ont besoin d'encoder des caractères locaux. Les 128 caractères de l'ASCII ne sont pas suffisant, ce qui demande de passer de 7 à 8 bits. On retombe donc sur un octet ! D'ailleurs, les encodages ASCII améliorés utilisaient les 127 premières valeurs pour l'ASCII normal, mais les 128 suivantes encodaient d'autres caractères. Les encodages en question varient d'un pays ou d'une langue à l'autre, mais ils sont terriblement importants, ce qui est un argument pour l'usage des octets.
===Le support des données encodées sur un ''nibble''===
Une autre raison pour l'usage des octets est qu'il est facile de les découper en deux ''nibbles''. Pour rappel, les ''nibbles'' sont des groupes de 4 bits. Et il se trouve que de nombreuses données sont encodées sur 4 bits, ni plus ni moins. Les ''nibbles'' sont très important pour le support du BCD et de l’hexadécimal. Et c'était un avantage important au début de l'informatique, moins maintenant. Un octet regroupe deux ''nibbles'' : un ''nibble'' de poids faible, un ''nibble'' de poids fort.
[[File:Nibbles in a byte.PNG|centre|vignette|upright=2|Nibbles dans un octet.]]
Un chiffre BCD occupe un ''nibble'', ce qui fait que la taille idéale de l'unité mémoire doit être un multiple de 4 bits : 4 bits, 8 bits, 12 bits, 16 bits, etc. Un ''nibble'' de 4 bits est insuffisant pour encoder des caractères, 8 bits fonctionne parfaitement, pas besoin d'aller plus loin. De plus, l'usage des ''nibbles'' permet de gérer la représentation hexadécimale très facilement. Un chiffre hexadécimal est encodé sur exactement un ''nibble'', un octet regroupe deux chiffres hexadécimaux, deux octets 4 chiffres hexadécimaux, etc. Enfin, 8 bits est une puissance de deux, ce qui simplifie grandement l'encodage des entiers en binaire est est une source de nombreuses simplifications. Les circuits de l'ordinateur sont plus simple à concevoir quand ils manipulent des opérandes de 2, 4, 8, 16, 32, 64, 128 bits.
===L'influence sur la taille des opérandes===
Les anciens ordinateurs étaient conçus pour le calcul scientifique, ou du moins des applications similaires. Ils n'avaient pas besoin de supporter le BCD, ni même de gérer du texte. Ils ne faisaient que des calculs et utilisaient des opérandes flottantes. La taille des opérandes était dictée par des considérations calculatoires. La norme à l'époque était que les calculs scientifiques devaient gérer des opérandes pouvant aller jusqu'à 10 chiffres décimaux. Et cela demande d'utiliser des opérandes entiers de 35 bits, avec un bit en plus pour encoder des opérandes positives ou négatives. Les 36 bits étaient parfois découpés en 6 groupes de 6 bits chacun, au lieu des octets de 8 bits.
Mais ces groupes de 6 bits étaient souvent peu utilisés. Prenons l'exemple des mémoires RAM. Plus haut, j'ai dit qu'une mémoire RAM normale est composée de plusieurs octets, tous numérotés. Intuitivement, vous vous dites que les anciens ''mainframes'' remplaçaient ces octets par des groupes de 6 bits dans la RAM. Mais en réalité, l'équivalent des RAM de l'époque utilisait des groupes de 36 bits. les groupes de 36 bits étaient aussi numérotés, les numéros étant appelés des adresses mémoire. Par contre, les adresses/numéros étaient codés sur 12 ou 18 bits, c'est à dire sur 2 ou 3 groupes de 6 bits. La raison est que ces numéros sont manipulés par le processeur, qui gère des opérandes de 36 bits. L'idée est que le numéro ne prend que la moitié ou un tiers des 36 bits gérés par le processeur.
Avec l'apparition de l'informatique commerciale, les ordinateurs ont du s'adapter aux besoins des entreprises, pour gérer la comptabilité, des applications financières, des documents texte, etc. Et cela demandait de supporter à la fois le BCD et les caractères/lettres. Il a fallu trouver une unité mémoire capable de gérer à la fois nombres entiers, chiffres BCD et caractères. L'octet était parfait pour ça. Les ''mainframes'' sont adaptés en supportant des caractères codés sur 6, 7 ou 8 bits. Mais le support demandait d'utiliser des circuits qui découpaient un groupe de 36 bits en plusieurs caractères ou en plusieurs chiffres BCD. Le tout demandait des circuits de décalage et de masquage assez complexes. Les ordinateurs 8 et 16 bits manipulaient des octets nativement : leur mémoire RAM gérait des octets, le processeur traitait des opérandes de 8, 16 ou 32 bits. Ils n'avaient pas besoin de matériel complexe pour extraire un caractère ou un chiffre BCD d'un octet/doublet/multiplet. C'était un avantage que les anciens ''mainframes'' n'avaient pas.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'encodage des données
| prevText=L'encodage des données
| next=Les codes de détection/correction d'erreur
| nextText=Les codes de détection/correction d'erreur
}}
</noinclude>
j0jrz9ox8xb62dwnabog9x4jkuujq4e
775647
775646
2026-11-02T19:51:33Z
Mewtow
31375
/* Les spécificités de la norme IEEE754 */
775647
wikitext
text/x-wiki
Dans le chapitre précédent, nous avons vu que les ordinateurs actuels utilisent un codage binaire. Ce codage binaire ne vous est peut-être pas familier. Aussi, dans ce chapitre, nous allons apprendre comment coder des nombres en binaire. Nous allons commencer par le cas le plus simple : les nombres positifs. Par la suite, nous aborderons les nombres négatifs. Et nous terminerons par les nombres à virgules, appelés aussi nombres flottants.
==Le codage des nombres entiers positifs==
Pour coder des nombres entiers positifs, il existe plusieurs méthodes : le binaire, l’hexadécimal, le code Gray, le décimal codé binaire et bien d'autres encore. La plus connue est certainement le binaire, secondée par l'hexadécimal, les autres étant plus anecdotiques. Pour comprendre ce qu'est le binaire, il nous faut faire un rappel sur les nombres entiers tel que vous les avez appris en primaire, à savoir les entiers écrits en décimal. Prenons un nombre écrit en décimal : le chiffre le plus à droite est le chiffre des unités, celui à côté est pour les dizaines, suivi du chiffre des centaines, et ainsi de suite. Dans un tel nombre :
* on utilise une dizaine de chiffres, de 0 à 9 ;
* chaque chiffre est multiplié par une puissance de 10 : 1, 10, 100, 1000, etc. ;
* la position d'un chiffre dans le nombre indique par quelle puissance de 10 il faut le multiplier : le chiffre des unités doit être multiplié par 1, celui des dizaines par 10, celui des centaines par 100, et ainsi de suite.
Exemple avec le nombre 1337 :
<math>1337 = 1 \times 1000 + 3 \times 100 + 3 \times 10 + 7 \times 1</math>
Pour résumer, un nombre en décimal s'écrit comme la somme de produits, chaque produit multipliant un chiffre par une puissance de 10. On dit alors que le nombre est en base 10.
<math>1337 = 1 \times 10^3 + 3 \times 10^2 + 3 \times 10^1 + 7 \times 10^0</math>
Ce qui peut être fait avec des puissances de 10 peut être fait avec des puissances de 2, 3, 4, 125, etc : n'importe quel nombre entier strictement positif peut servir de '''base'''. En informatique, on utilise rarement la base 10 à laquelle nous sommes tant habitués. On utilise à la place deux autres bases :
* La base 2 (système binaire) : les chiffres utilisés sont 0 et 1 ;
* La base 16 (système hexadécimal) : les chiffres utilisés sont 0, 1, 2, 3, 4, 5, 6, 7, 8 et 9 ; auxquels s'ajoutent les six premières lettres de notre alphabet : A, B, C, D, E et F.
===Le système binaire===
En binaire, on compte en base 2. Cela veut dire qu'au lieu d'utiliser des puissances de 10 comme en décimal, on utilise des puissances de deux : n'importe quel nombre entier peut être écrit sous la forme d'une somme de puissances de 2. Par exemple 6 s'écrira donc 0110 en binaire : <math>0 \times 2^3 + 1 \times 2^2 + 1 \times 2^1 + 0 \times 2^0 = 6</math>. On peut remarquer que le binaire n'autorise que deux chiffres, à savoir 0 ou 1 : ces chiffres binaires sont appelés des '''bits''' (abréviation de Binary Digit). Pour simplifier, on peut dire qu'un bit est un truc qui vaut 0 ou 1. Pour résumer, tout nombre en binaire s'écrit sous la forme d'un produit entre bits et puissances de deux de la forme :
<math>a_0 \times 2^0 + a_1 \times 2^1 + a_2 \times 2^2 + a_3 \times 2^3 + a_4 \times 2^4 +...+ a_n \times 2^n</math>
Les coefficients <math>a_0, a_1, a_2</math> sont les bits, l'exposant n qui correspond à un bit <math>a_n</math> est appelé le '''poids''' du bit.
====La terminologie du binaire====
En informatique, il est rare que l'on code une information sur un seul bit. Dans la plupart des cas, l'ordinateur manipule des nombres codés sur plusieurs bits. Les informaticiens ont donné des noms aux groupes de bits suivant leur taille. Le plus connu est certainement l''''octet''', qui désigne un groupe de 8 bits. Moins connu, on parle de '''''nibble''''' pour un groupe de 4 bits (un demi-octet), de '''doublet''' pour un groupe de 16 bits (deux octets) et de '''quadruplet''' pour un groupe de 32 bits (quatre octets).
Précisons qu'en anglais, le terme ''byte'' n'est pas synonyme d'octet. En réalité, le terme ''octet'' marche aussi bien en français qu'en anglais. Quant au terme ''byte'', il désigne un concept complètement différent, que nous aborderons plus tard (c'est la plus petite unité de mémoire que le processeur peut adresser). Il a existé dans le passé des ordinateurs où le ''byte'' faisait 4, 7, 9, 16, voire 48 bits, par exemple. Il a même existé des ordinateurs où le ''byte'' faisait exactement 1 bit ! Mais sur presque tous les ordinateurs modernes, un ''byte'' fait effectivement 8 bits, ce qui fait que le terme ''byte'' est parfois utilisé en lieu et place d'''octet''. Mais c'est un abus de langage, attention aux confusions ! Dans ce cours, nous parlerons d'octet pour désigner un groupe de 8 bits, en réservant le terme ''byte'' pour sa véritable signification.
À l'intérieur d'un nombre, le '''bit de poids faible''' est celui qui est le plus à droite du nombre, alors que le '''bit de poids fort''' est celui non nul qui est placé le plus à gauche, comme illustré dans le schéma ci-dessous.
C'est le même principe que l'écriture des nombres en base décimale : le chiffre le plus significatif à gauche, et le moins significatif à droite (l'unité ou les décimales après la virgule).
[[File:Msb.svg|centre|vignette|upright=2|Bit de poids fort.]]
[[File:Lsb.svg|centre|vignette|upright=2|Bit de poids faible.]]
Cette terminologie s'applique aussi pour les bits à l'intérieur d'un octet, d'un ''nibble'', d'un doublet ou d'un quadruplet. Pour un nombre codés sur plusieurs octets, on peut aussi parler de l'octet de poids fort et de l'octet de poids faible, du doublet de poids fort ou de poids faible, etc.
====La traduction binaire→décimal====
Pour traduire un nombre binaire en décimal, il faut juste se rappeler que la position d'un bit indique par quelle puissance il faut le multiplier. Ainsi, le chiffre le plus à droite est le chiffre des unités : il doit être multiplié par 1 (<math>2^0</math>). Le chiffre situé immédiatement à gauche du chiffre des unités doit être multiplié par 2 (<math>2^1</math>). Le chiffre encore à gauche doit être multiplié par 4 (<math>2^2</math>), et ainsi de suite. Mathématiquement, on peut dire que le énième bit en partant de la droite doit être multiplié par <math>2^{n-1}</math>. Par exemple, la valeur du nombre noté 1011 en binaire est de <math>8 \times 1 + 4 \times 0 + 2 \times 1 + 1 \times 1 = 11</math>.
[[File:Value of digits in the Binary numeral system.svg|centre|vignette|upright=2|Valeur des chffres dans le système de numération binaire.]]
====La traduction décimal→binaire====
La traduction inverse, du décimal au binaire, demande d'effectuer des divisions successives par deux. Les divisions en question sont des divisions euclidiennes, avec un reste et un quotient. En lisant les restes des divisions dans un certain sens, on obtient le nombre en binaire. Voici comment il faut procéder, pour traduire le nombre 34 :
[[File:Exemple d'illustration de la méthode de conversion décimal vers binaire.png|centre|vignette|upright=2|Exemple d'illustration de la méthode de conversion décimal vers binaire.]]
====Quelques opérations en binaire====
Maintenant que l'on sait coder des nombres en binaire normal, il est utile de savoir comment faire quelques opérations usuelles en binaire. Nous utiliserons les acquis de cette section dans la suite du chapitre, bien que de manière assez marginale.
La première opération est assez spécifique au binaire. Il s'agit d'une opération qui inverse les bits d'un nombre : les 0 deviennent des 1 et réciproquement. Par exemple, le nombre 0001 1001 devient 1110 0110. Elle porte plusieurs noms : opération NOT, opération NON, complémentation, etc. Nous parlerons de '''complémentation''' ou d'opération NOT dans ce qui suit. Beaucoup d'ordinateurs gèrent cette opération, ils savent la faire en un seul calcul. Il faut dire que c'est une opération assez utile, bien que nous ne pouvons pas encore expliquer pourquoi.
[[File:Binary Addition Demonstration.svg|vignette|Exemple d'addition en binaire.]]
La seconde opération à aborder est l'addition. Elle se fait en binaire de la même manière qu'en décimal : Pour faire une addition en binaire, on additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. Sauf que l'on additionne des bits. Heureusement, la table d'addition est très simple en binaire :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
La troisième opération est une variante de l'addition appelée l''''opération XOR''', notée <math>\oplus</math>. Il s'agit d'une addition dans laquelle on ne propage pas les retenues. L'addition des deux bits des opérandes se fait normalement, mais les retenues sont simplement oubliées, on n'en tient pas compte. Le résultat est que l'addition se résume à appliquer la table d'addition précédente :
* 0 <math>\oplus</math> 0 = 0 ;
* 0 <math>\oplus</math> 1 = 1 ;
* 1 <math>\oplus</math> 0 = 1 ;
* 1 <math>\oplus</math> 1 = 0.
Pour résumer, le résultat vaut 1 si les deux bits sont différents, 0 s'ils sont identiques. L'opération XOR sera utilisée rapidement dans le chapitre suivant, quand nous parlerons rapidement du mot de parité. Et elle sera beaucoup utilisée dans la suite du cours, nous en feront fortement usage. Pour le moment, mémorisez juste cette opération, elle n'a rien de compliqué.
Une dernière opération est l'opération de '''''population count'''''. Il s'agit ni plus ni moins que de compter le nombre de bits qui sont à 1 dans un nombre. Par exemple, pour le nombre 0110 0010 1101 1110, elle donne pour résultat 9. Elle est utilisée dans certaines applications, comme le calcul de certains codes correcteurs d'erreur, comme on le verra dans le chapitre suivant. Elle est supportée sur de nombreux ordinateurs, encore que cela dépende du processeur considéré. Il s'agit cependant d'une opération assez courante, supportée par les processeurs ARM, les processeurs x86 modernes (ceux qui gèrent le SSE), et quelques autres.
===L'hexadécimal===
L’hexadécimal est basé sur le même principe que le binaire, sauf qu'il utilise les 16 chiffres suivants :
{| class="wikitable"
|- style="color:#8080ffff; text-align:center;"
! Chiffre hexadécimal
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || A || B || C || D || E || F
|- style="text-align:center;"
! Nombre décimal correspondant
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 10 || 11 || 12 || 13 || 14 || 15
|- style="text-align:center;"
! Notation binaire
| 0000 || 0001 || 0010 || 0011 || 0100 || 0101 || 0110 || 0111 || 1000 || 1001 || 1010 || 1011 || 1100 || 1101 || 1110 || 1111
|}
Dans les textes, afin de différencier les nombres décimaux des nombres hexadécimaux, les nombres hexadécimaux sont suivis par un petit h, indiqué en indice. Si cette notation n'existait pas, des nombres comme 2546 seraient ambigus : on ne saurait pas dire sans autre indication s'ils sont écrits en décimal ou en hexadécimal. Avec la notation, on sait de suite que 2546 est en décimal et que 2546h est en hexadécimal.
Dans les codes sources des programmes, la notation diffère selon le langage de programmation.
Certains supportent le suffixe <code>h</code> pour les nombres hexadécimaux, d'autres utilisent un préfixe <code>0x</code> ou <code>0h</code>.
Pour convertir un nombre hexadécimal en décimal, il suffit de multiplier chaque chiffre par la puissance de 16 qui lui est attribuée. Là encore, la position d'un chiffre indique par quelle puissance celui-ci doit être multiplié : le chiffre le plus à droite est celui des unités, le second chiffre le plus à droite doit être multiplié par 16, le troisième chiffre en partant de la droite doit être multiplié par 256 (16 * 16) et ainsi de suite. La technique pour convertir un nombre décimal vers de l’hexadécimal est similaire à celle utilisée pour traduire un nombre du décimal vers le binaire. On retrouve une suite de divisions successives, mais cette fois-ci les divisions ne sont pas des divisions par 2 : ce sont des divisions par 16.
La conversion inverse, de l'hexadécimal vers le binaire est très simple, nettement plus simple que les autres conversions. Pour passer de l'hexadécimal au binaire, il suffit de traduire chaque chiffre en sa valeur binaire, celle indiquée dans le tableau au tout début du paragraphe nommé « Hexadécimal ». Une fois cela fait, il suffit de faire le remplacement. La traduction inverse est tout aussi simple : il suffit de grouper les bits du nombre par 4, en commençant par la droite (si un groupe est incomplet, on le remplit avec des zéros). Il suffit alors de remplacer le groupe de 4 bits par le chiffre hexadécimal qui correspond.
==Interlude propédeutique : la capacité d'un entier et les débordements d'entiers==
Dans la section précédente, nous avons vu comment coder des entiers positifs en binaire ou dans des représentations proches. La logique voudrait que l'on aborde ensuite le codage des entiers négatifs. Mais nous allons déroger à cette logique simple, pour des raisons pédagogiques. Nous allons faire un interlude qui introduira des notions utiles pour la suite du chapitre. De plus, ces concepts seront abordés de nombreuses fois dans ce wikilivre et l'introduire ici est de loin la solution idéale.
===Les ordinateurs manipulent des nombres codés sur un nombre fixe de bits===
Vous avez certainement déjà entendu parler de processeurs 32 ou 64 bits. Et si vous avez joué aux jeux vidéos durant votre jeunesse et êtes assez agé, vous avez entendu parler de consoles de jeu 8 bits, 16 bits, 32 bits, voire 64 bits (pour la Jaguar, et c'était un peu trompeur). Derrière cette appellation qu'on retrouvait autrefois comme argument commercial dans la presse se cache un concept simple. Tout ordinateur manipule des nombres entiers dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Une console 16 bits manipulait des entiers codés en binaire sur 16 bits, pas un de plus, pas un de moins. Pareil pour les anciens ordinateurs 32 bits, qui manipulaient des nombres entiers codés sur 32 bits.
Aujourd'hui, les ordinateurs modernes utilisent presque un nombre de bits qui est une puissance de 2 : 8, 16, 32, 64, 128, 256, voire 512 bits. Mais cette règle souffre évidemment d'exceptions. Aux tout débuts de l'informatique, certaines machines utilisaient 3, 7, 13, 17, 23, 36 et 48 bits ; mais elles sont aujourd'hui tombées en désuétude. De nos jours, il ne reste que les processeurs dédiés au traitement de signal audio, que l'on trouve dans les chaînes HIFI, les décodeurs TNT, les lecteurs DVD, etc. Ceux-ci utilisent des nombres entiers de 24 bits, car l'information audio est souvent codée par des nombres de 24 bits.
Anecdote amusante, il a existé des ordinateurs de 1 bit, qui sont capables de manipuler des nombres codés sur 1 bit, pas plus. Un exemple est le Motorola MC14500B, commercialisé de 1976.
====Le lien entre nombre de bits et valeurs codables====
Évidemment, on ne peut pas coder tous les entiers possibles et imaginables avec seulement 8 bits, ou 16 bits. Et il parait intuitif que l'on ait plus de valeurs codables sur 16 bits qu'avec 8 bits, par exemple. Plus le nombre de bits est important, plus on pourra coder de valeurs entières différentes. Mais combien de plus ? Par exemple, si je passe de 8 bits à 16 bits, est-ce que le nombre de valeurs que l'on peut coder double, quadruple, pentuple ? De même, combien de valeurs différentes on peut coder avec <math>n</math> bits. Par exemple, combien de nombres différents peut-on coder avec 4, 8 ou 16 bits ? La section précédente vous l'expliquer.
Avec <math>n</math> bits, on peut coder <math>2^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>2^n - 1</math>. N'oubliez pas cette formule : elle sera assez utile dans la suite de ce tutoriel. Pour exemple, on peut coder 16 valeurs avec 4 bits, qui vont de 0 à 15. De même, on peut coder 256 valeurs avec un octet, qui vont de 0 à 255. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de bits
! Nombre de valeurs codables
|-
! 4
| 16
|-
! 8
| 256
|-
! 16
| 65 536
|-
! 32
| 4 294 967 296
|-
! 64
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien de bits il faut pour coder une valeur quelconque, que nous noterons N. Pour cela, il faut utiliser la formule précédente, mais à l'envers. On cherche alors <math>n</math> tel que <math>N = 2^n</math>. L'opération qui donne <math>n</math> est appelée le logarithme, et plus précisément un logarithme en base 2, noté <math>\log_2{(X)}</math>. Le problème est que le résultat du logarithme ne tombe juste que si le nombre X est une puissance de 2. Si ce n'est pas le cas, le résultat est un nombre à virgule, ce qui n'a pas de sens pratique. Par exemple, la formule nous dit que pour coder le nombre 13, on a besoin de 3,70043971814 bits, ce qui est impossible. Pour que le résultat ait un sens, il faut arrondir à l'entier supérieur. Pour l'exemple précédent, les 3,70043971814 bits s'arrondissent en 4 bits.
====Le lien entre nombre de chiffres hexadécimaux et valeurs codables====
Maintenant, voyons combien de valeurs peut-on coder avec <math>n</math> chiffres hexadécimaux. La réponse n'est pas très différente de celle obtenue en binaire, si ce n'est qu'il faut remplacer le 2 par un 16 dans la formule précédente. Avec <math>n</math> chiffres hexadécimaux, on peut coder <math>16^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>16^n - 1</math>. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de chiffres héxadécimaux
! Nombre de valeurs codables
|-
! 1 (4 bits)
| 16
|-
! 2 (8 bits)
| 256
|-
! 4 (16 bits)
| 65 536
|-
! 8 (32 bits)
| 4 294 967 296
|-
! 16 (64 bits)
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien faut-il de chiffres hexadécimaux pour coder une valeur quelconque en hexadécimal. La formule est là encore la même qu'en binaire, sauf qu'on remplace le 2 par un 16. Pour trouver le nombre de chiffres hexadécimaux pour encoder un nombre X, il faut calculer <math>\log_{16}{(X)}</math>. Notons que le logarithme utilisé est un logarithme en base 16, et non un logarithme en base 2, comme pour le binaire. Là encore, le résultat ne tombe juste que si le nombre X est une puissance de 16 et il faut arrondir à l'entier supérieur si ce n'est pas le cas.
Une propriété mathématique des logarithmes nous dit que l'on peut passer d'un logarithme en base X et à un logarithme en base Y avec une simple division, en utilisant la formule suivante :
: <math>\log_X{(N)} = {\log_{Y}{(N)} \over \log_Y(X)}</math>
Dans le cas qui nous intéresse, on a Y = 2 et X = 16, ce qui donne :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over \log_2(16)}</math>
Or, <math>\log_2(16)</math> est tout simplement égal à 4, car il faut 4 bits pour coder la valeur 16. On a donc :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over 4}</math>
En clair, il faut quatre fois moins de chiffres hexadécimaux que de bits, ce qui est assez intuitif vu qu'il faut 4 bits pour coder un chiffre hexadécimal.
===Les débordements d'entier===
On vient de voir que tout ordinateur manipule des nombres dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Et cela limite les valeurs qu'il peut encoder, qui sont comprises dans un intervalle bien précis. Mais que ce passe-t-il si jamais le résultat d'un calcul ne rentre pas dans cet intervalle ? Par exemple, pour du binaire normal, que faire si le résultat d'un calcul atteint ou dépasse <math>2^n</math> ? Dans ce cas, le résultat ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
On peut imaginer d'autres codages pour lesquels les entiers ne commencent pas à zéro ou ne terminent pas à <math>2^n - 1</math>. On peut prendre le cas où l'ordinateur gère les nombres négatifs, par exemple. Dans le cas général, l'ordinateur peut coder les valeurs comprises dans un intervalle, qui va de la valeur la plus basse <math>N_\text{min}</math> à la valeur la plus grande <math>N_\text{max}</math>. Et encore une fois, si un résultat de calcul sort de cet intervalle, on fait face à un débordement d'entier.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math>, avec <math>N_\text{max}</math> la plus grande valeur codable par l'ordinateur.
On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur basse de débordement vaut <math>N_\text{min} - 1</math>, avec <math>N_\text{min}</math> la plus petite valeur codable par l'ordinateur.
===La gestion des débordements d'entiers===
Face à un débordement d'entier, l'ordinateur peut utiliser deux méthodes : l'arithmétique saturée ou l'arithmétique modulaire.
L''''arithmétique saturée''' consiste à arrondir le résultat pour prendre la plus grande ou la plus petite valeur. Si le résultat d'un calcul dépasse la valeur haute de débordement, le résultat est remplacé par le plus grand entier supporté par l'ordinateur. La même chose est possible quand le résultat est inférieur à la plus petite valeur possible, par exemple lors d'une soustraction : l'ordinateur arrondit au plus petit entier possible.
Pour donner un exemple, voici ce que cela donne avec des entiers codés sur 4 bits, qui codent des nombres de 0 à 15. Si je fais le calcul 8 + 9, le résultat normal vaut 17, ce qui ne rentre pas dans l'intervalle. Le résultat est alors arrondi à 15. Inversement, si je fais le calcul 8 - 9, le résultat sera de -1, ce qui ne rentre pas dans l'intervalle : le résultat est alors arrondi à 0.
[[File:Odometer rollover.jpg|vignette|Exemple de débordement d'entier sur un compteur mécanique quelconque. L'image montre clairement que le compteur revient à zéro une fois la valeur maximale dépassée.]]
L''''arithmétique modulaire''' est plus compliquée et c'est elle qui va nous intéresser dans ce qui suit. Pour simplifier, imaginons que l'on décompte à partir de zéro. Quand on arrive à la valeur haute de débordement, on recommence à compter à partir de zéro. L'arithmétique modulaire n'est pas si contre-intuitive et vous l'utilisez sans doute au quotidien. Après tout, c'est comme cela que l'on compte les heures, les minutes et les secondes. Quand on compte les minutes, on revient à 0 au bout de 60 minutes. Pareil pour les heures : on revient à zéro quand on arrive à 24 heures. Divers compteurs mécaniques fonctionnent sur le même principe et reviennent à zéro quand ils dépassent la plus grande valeur possible, l'image ci-contre en montrant un exemple.
Mathématiquement, l'arithmétique modulaire implique des divisions euclidiennes, celles qui donnent un quotient et un reste. Lors d'un débordement, le résultat s'obtient comme suit : on divise le nombre qui déborde par la valeur haute de débordement, et que l'on conserve le reste de la division. Au passage, l'opération qui consiste à faire une division et à garder le reste au lieu du quotient est appelée le ''modulo''. Prenons l'exemple où l'ordinateur peut coder tous les nombres entre 0 et 1023, soit une valeur haute de débordement de 1024. Pour coder le nombre 4563, on fait le calcul 4563 / 1024. On obtient : <math>4563 = 4 \times 1024 + 467</math>. Le reste de la division est de 467 et c'est lui qui sera utilisé pour coder la valeur de départ, 4563. Le nombre 4563 est donc codé par la valeur 467 dans un tel ordinateur en arithmétique modulaire. Au passage, la valeur haute de débordement est toujours codée par un zéro dans ce genre d'arithmétique.
Les ordinateurs utilisent le plus souvent une valeur haute de débordement de <math>2^n</math>, avec n le nombre de bits utilisé pour coder les nombres entiers positifs. En faisant cela, l'opération modulo devient très simple et revient à éliminer les bits de poids forts au-delà du énième bit. Par exemple, reprenons l'exemple d'un ordinateur qui code ses nombres sur 4 bits. Imaginons qu'il fasse le calcul <math>13 + 3</math>, soit 1101 + 0011 = 1 0000 en binaire. Le résultat entraîne un débordement d'entier et l'ordinateur ne conserve que les 4 bits de poids faible. Cela donne : 1101 + 0011 = 0000. Comme autre exemple l'addition 1111 + 0010 ne donnera pas 17 (1 0001), mais 1 (0001).
L'avantage est que les calculs sont beaucoup plus simples avec cette méthode qu'avec les autres. L'ordinateur a juste à ne pas calculer les bits de poids fort. Pas besoin de faire une division pour calculer un modulo, pas besoin de corriger le résultat pour faire de l'arithmétique saturée.
===Aparté : quelques valeurs particulières en binaire===
Plus haut, on a dit qu'avec n bits, on peut encoder toutes les valeurs allant de <math>0</math> à <math>2^n - 1</math>. Ce simple fait permet de déterminer quelle est la valeur de certains entiers à vue d’œil. Dans ce qui va suivre, nous allons poser quelques bases que nous réutiliserons dans la suite du chapitre. Il s'agit de quelques trivias qui sont cependant assez utiles.
Le premier trivia concerne la valeur maximale <math>2^n - 1</math> : elle est encodée par un nombre dont tous les bits sont à 1.
Le deuxième trivia concerne les nombres de la forme 000...000 1111 1111, à savoir des nombres dont les x bits de poids faible sont à 1 et tous les autres valent 0. Par définition, de tels nombres codent la plus grande valeur possible sur x bits, ce qui fait qu'ils valent <math>2^x - 1</math>.
Le troisième trivia concerne les nombres de la forme 11111...0000. En clair, des nombres où on a une suite consécutive de 1 dans les bits de poids fort et les x bits de poids faibles à 0. De tels nombres valent : <math>2^n - 2^x</math>. La preuve est assez simple : ils s'obtiennent en prenant la valeur maximale <math>2^n - 1</math>, et en soustrayant un nombre de la forme <math>2^x - 1</math>.
[[File:Valeurs particulières en binaire.png|centre|vignette|upright=2|Valeurs particulières en binaire]]
Si on utilise l'arithmétique modulaire, la valeur <math>2^n</math> n'est autre que la valeur de débordement haute pour les nombres stockés sur n bits, et se code comme le zéro (il faudrait 1 bit de plus pour stocker le 1 de poids fort de <math>2^n</math>). Les 1er et 3ème nombres évoqués dans les paragraphes précédents peuvent donc se passer de <math>2^n</math> dans leur expression :
* <math>- 1</math> est codé 1111111111111111...111 (n bits à 1)
* <math>- 2^x</math> est codé 11111...111000..000000 (x bits à 0 précédés de n-x bits à 1)
==Les nombres entiers négatifs==
<noinclude>[[File:Binary Representations.svg|vignette|Représentations signées, exemples sur 4 bits.]]</noinclude>
Passons maintenant aux entiers négatifs en binaire : comment représenter le signe moins ("-") avec des 0 et des 1 ? Eh bien, il existe plusieurs méthodes :
* la représentation en signe-valeur absolue ;
* la représentation en complément à un ;
* la représentation en complément à deux;
* la représentation par excès ;
* la représentation dans une base négative ;
* d'autres représentations encore moins utilisées que les autres.
===La représentation en signe-valeur absolue===
La solution la plus simple pour représenter un entier négatif consiste à coder sa valeur absolue en binaire, et rajouter un bit qui précise si c'est un entier positif ou un entier négatif. Par convention, ce bit de signe vaut 0 si le nombre est positif et 1 s'il est négatif. On parle alors de représentation en signe-valeur absolue, aussi appelée '''représentation en signe-magnitude'''.
Avec cette technique, il y autant de nombres positifs que négatifs. Mieux : pour chaque nombre représentable en représentation signe-valeur absolue, son inverse l'est aussi. Ce qui fait qu'avec cette méthode, le zéro est codé deux fois : on a un -0, et un +0. Cela pose des problèmes lorsqu'on demande à notre ordinateur d'effectuer des calculs ou des comparaisons avec zéro.
[[File:Codage sur 4 bits en signe-valeur absolue.png|centre|vignette|upright=2|Codage sur 4 bits en signe-valeur absolue]]
===La représentation en complément à un===
La représentation en '''complément à un''' peut être vue, en première approximation, comme une variante de la représentation en signe-magnitude. Et je dis bien : "en première approximation", car il y a beaucoup à dire sur la représentation en complément à un, mais nous verrons cela dans la section suivante sur le complément à deux. Conceptuellement, la représentation en signe-magnitude et celle du complément à un sont drastiquement différentes et sont basées sur des concepts mathématiques totalement différents. Mais elles ont des ressemblances de surface, qui font que faire la comparaison entre les deux est assez utile pédagogiquement parlant.
En complément à un, les nombres sont codés en utilisant un bit de signe qui indique si le nombre de positif ou négatif, couplé à une valeur absolue. Par contre, la valeur absolue est codée différemment en binaire. Pour les nombres positifs, la valeur absolue est codée en binaire normal, pas de changement comparé aux autres représentations. Mais pour les valeurs négatives, la valeur absolue est codée en binaire normal, puis tous les bits sont inversés : les 0 deviennent des 1 et réciproquement. En clair, on utilise une opération de complémentation pour les nombres négatifs.
Prenons un exemple avec un nombre codé sur 4 bits, avec un cinquième bit de signe. La valeur 5 est codée comme suit : 0 pour le bit de signe, 5 donne 0101 en binaire, le résultat est 0 0101. Pour la valeur -5, le bit de signe est 1, 5 donne 0101 en binaire, on inverse les bits ce qui donne 1010 : cela donne 11010. Notez qu'on peut passer d'un résultat à l'autre avec une opération de complémentation, à savoir en inversant les bits de l'autre et réciproquement.
Il s'agit là d'une propriété générale avec le complément à 1 : l'opposé d'un nombre, à savoir passer de sa valeur positive à sa valeur négative ou inversement, se calcule avec une opération NOT, une opération de complémentation (d'où son nom). L'avantage est que les ordinateurs gèrent naturellement d'opération de complémentation. Par contre, en signe-magnitude, inverser le bit de signe est une opération spécifique et ne sert qu'à ça. Il faut donc rajouter une opération en plus pour calculer l'opposé d'un nombre.
La représentation en complément à un garde les défauts de la représentation en signe-magnitude. Le zéro est codé deux fois, avec un zéro positif et un zéro négatif. La différence est que les valeurs négatives sont dans l'ordre inverse, il y a une symétrie un peu meilleure. Les deux zéros sont d'ailleurs totalement éloignés, ils correspondent aux valeurs extrêmes encodables : le zéro positif a tous ses bits à 0, le zéro négatif a tous ses bits à 1. Le résultat est que l'implémentation des comparaisons et de certains calculs est plus complexe qu'avec le signe-magnitude, mais de peu.
[[File:Codage sur 4 bits en complément à 1.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 1.]]
===La représentation par excès===
La représentation par excès consiste à ajouter un biais aux nombres à encoder afin de les encoder par un entier positif. Pour encoder tous les nombres compris entre -X et Y en représentation par excès, il suffit de prendre la valeur du nombre à encoder, et de lui ajouter un biais égal à X. Ainsi, la valeur -X sera encodée par zéro, et toutes les autres valeurs le seront par un entier positif, le zéro sera encodé par X, 1, par X+1, etc. Par exemple, prenons des nombres compris entre -127 et 128. On va devoir ajouter un biais égal à 127, ce qui donne :
{|class="wikitable"
|-
!Valeur avant encodage!!Valeur après encodage
|-
||-127||0
|-
||-126||1
|-
||-125||2
|-
||…||…
|-
||0||127
|-
||…||…
|-
||127||254
|-
||128||255
|}
===La représentation en complément à deux===
La représentation en '''complément à deux''' est basée sur les mêmes mathématiques que le complément à un, mais fonctionne très différemment en pratique.
Si on regarde de loin, son principe est assez différent : il n'y a pas de bit de signe ni quoi que ce soit d'autre. À la place, un nombre en complément à deux est encodé comme en binaire normal, à un point près : le bit de poids fort est soustrait, et non additionné aux autres. Il a une valeur négative : on soustrait la puissance de deux associée s'il vaut 1, on ne la tient pas en compte s'il vaut 0.
Par exemple, la valeur du nombre noté 111001 en complément à deux s'obtient comme suit :
{|class="wikitable" style="margin:auto;"
|-
! -32 !! 16 !! 8 !! 4 !! 2 !! 1
|-
| 1 || 1 || 1 || 0 || 0 || 1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(8×1)+(4×0)+(2×1)+(1×1) = −32+16+8+1 = -7.
: ''Avec le complément à deux, comme avec le complément à un, le bit de poids fort vaut 0 pour les nombres positifs et 1 pour les négatifs.''
L'avantage de cette représentation est qu'elle n'a pas de double zéro : le zéro n'est encodé que par une seule valeur. Par contre, la valeur autrefois prise par le zéro négatif est réutilisée pour encoder une valeur négative. La conséquence est que l'on a un nombre négatif en plus d'encodé, il n'y a plus le même nombre de valeurs strictement positives et de valeurs négatives encodées. Le nombre négatif en question est appelé le '''nombre le plus négatif''', ce nom trahit le fait que c'est celui qui a la plus petite valeur (la plus grande valeur absolue). Il est impossible de coder l'entier positif associé, sa valeur absolue.
[[File:Codage sur 4 bits en complément à 2.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 2.]]
====Le calcul du complément à deux : première méthode====
Les représentations en complément à un et en complément à deux sont basées sur le même principe mathématique. Leur idée est de remplacer chaque nombre négatif par un nombre positif équivalent, appelé le ''complément''. Par équivalent, on veut dire que tout calcul donne le même résultat si on remplace un nombre négatif par son complément (idem avec un nombre positif, son complément aura un signe inverse). Et pour faire cela, elles se basent sur les débordements d'entier pour fonctionner, et plus précisément sur l'arithmétique modulaire abordée plus haut.
Si on fait les calculs avec le complément, les résultats du calcul entraînent un débordement d'entier, qui sera résolu par un '''modulo''' : l'ordinateur ne conserve que les bits de poids faible du résultat, les autres bits sont oubliés. Par exemple, prenons l'addition 15 + 2, 1111 + 0010 en binaire : le résultat ne sera pas 17 (10001), vu qu'on n'a pas assez de bits pour encoder le résultat, mais 1 (0001). Et le résultat après modulo sera identique au résultat qu'on aurait obtenu avec le nombre négatif sans modulo. En clair, c'est la gestion des débordements qui permet de corriger le résultat de manière à ce que l'opération avec le complément donne le même résultat qu'avec le nombre négatif voulu. Ainsi, on peut coder un nombre négatif en utilisant son complément positif.
: Cela ressemble beaucoup à la méthode de soustraction basée sur un complément à 9 pour ceux qui connaissent, sauf que c'est une version binaire qui nous intéresse ici.
Prenons un exemple, qui permettra d'introduire la suite. Encore une fois, on utilise un codage sur 4 bits dont la valeur haute de débordement est de 16. Prenons l'addition de 13 + 3 = 16. Avec l'arithmétique modulaire, 16 est équivalent à 0, ce qui donne : 13 + 3 = 0 ! On peut aussi reformuler en disant que 13 = -3, ou encore que 3 = -13. Dit autrement, 3 est le complément de -13 pour ce codage. Et ne croyez pas que ça marche uniquement dans cet exemple : cela se généralise assez rapidement à tout nombre négatif.
Prenons un nombre N dont un veut calculer le complément à deux K. Dans le cas général, on a :
: <math>N + K = 2^n</math>, vu que <math>2^n = 0</math> en utilisant le modulo.
En réorganisant très légèrement les termes pour isoler K, on a :
: <math>K = 2^n - N</math>
La formule précédent permet de calculer la complément à deux assez simplement, en faisant le calcul à la main.
Avant de poursuivre, prenons un exemple très intéressant : le cas où N = -1. Son complément à deux vaut donc :
: <math>K = 2^n - 1</math>
Le terme <math>2^n - 1</math> devrait vous rappeler quelque chose : il s'agit d'un nombre dont tous les bits sont à 1. En clair, le complément à deux de -1 est un nombre de la forme 1111...111. Et cela vaut quel que soit le nombre de bits n. La représentation de -1 est similaire, peu importe que l'on utilise des nombres de 16 bits, 32 bits, 64 bits, etc.
Voyons maintenant le cas des puissance de deux, par exemple 2, 4, 8, 16, etc. Leur complément à deux vaut :
: <math>K = 2^n - 2^x</math>
Nous avions vu précédemment dans le chapitre que ces nombres sont de la forme 11111...0000. En clair, des nombres dont les x bits de poids faible sont à 0, et tous les autres bits sont à 1. Par exemple, le complément à deux de -2 est un nombre dont les bits sont à 1, sauf le bit de poids faible. De même, le complément à deux de -4 a tous ses bits à 1, sauf les deux bits de poids faible. Et le complément à deux de -8 a tous ses bits à 1, sauf les trois bits de poids faible. Pour résumer, tout nombre de la forme <math>- 2^x</math> a tous ses bits à 1, sauf les x bits de poids faible qui sont à 0.
{|class="wikitable"
|+ Exemple avec des nombres de 8 bits
|-
! -1
| 1111 1111
|-
! -2
| 1111 1110
|-
! -4
| 1111 1100
|-
! -8
| 1111 1000
|-
! -16
| 1111 0000
|-
! -32
| 1110 0000
|-
! -64
| 1100 0000
|-
! -128
| 1000 0000
|-
! 0 / - 256
| 0000 0000
|}
====Le calcul du complément à deux : seconde méthode====
Il existe une seconde méthode pour calculer le complément à deux d'un nombre, la voici. Pour les nombres positifs, encodez-les comme en binaire normale. Pour les nombres négatifs, faites pareil, puis inversez tous les bits, avant d'ajouter un. La procédure est identique à celle du complément à un, sauf que l'on incrémente le résultat final. Et ce n'est pas une coïncidence, comme nous allons le voir immédiatement.
Pour comprendre pourquoi la méthode marche, repartons de la formule précédente <math>K = 2^n - N</math>. Elle peut se reformuler comme suit :
: <math>K = (2^n - 1 + 1) - N</math>
: <math>K = (2^n - 1) - N + 1</math>
La valeur <math>(2^n - 1)</math> est par définition un nombre dont tous les bits sont à 1. À cette valeur, on soustrait un nombre <math>|N|</math> dont certains bits sont à 1 et d'autres à 0. En clair, pour chaque colonne, on a deux possibilités : soit on doit faire la soustraction <math>1-1</math>, soit la soustraction <math>1-0</math>. Or, les règles de l'arithmétique binaire disent que <math>1-1=0</math> et <math>1-0=1</math>. En regardant attentivement, on se rend compte que le bit du résultat est l'inverse du bit de départ. De plus, les deux cas ne donnent pas de retenue : le calcul pour chaque bit n'influence pas les bits voisins.
Le terme <math>(2^n - 1) - N</math> est donc le complément à un du nombre N, un nombre égal à sa représentation en binaire dont tous les bits sont inversés. Notons <math>\overline{|N|}</math> le nombre formé en inversant tous les bits de N. On a alors :
: <math>K = \overline{N} + 1</math>
En clair, le complément à deux s'obtient en prenant le complément à un et en ajoutant 1. Dit autrement, il faut prendre le nombre N, en inverser tous les bits et ajouter 1.
Une autre manière équivalente consiste à faire le calcul suivant :
: <math>K = \overline{N - 1}</math>
On prend le nombre dont on veut le complément, on soustrait 1 et on inverse les bits.
: Notons que tout ce qui a été dit plus haut marche aussi pour le complément à un, avec cependant une petite différence : la valeur haute de débordement n'est pas la même, ce qui change les calculs. Pour des nombres codés sur <math>n</math> bits, la valeur haute de débordement est égale à <math>2^n</math> en complément à deux, alors qu'elle est de <math>2^n - 1</math> en complément à un. De ce fait, la gestion des débordements est plus simple en complément à deux.
====L'extension de signe====
Dans les ordinateurs, tous les nombres sont codés sur un nombre fixé et constant de bits. Ainsi, les circuits d'un ordinateur ne peuvent manipuler que des nombres de 4, 8, 12, 16, 32, 48, 64 bits, suivant la machine. Si l'on veut utiliser un entier codé sur 16 bits et que l'ordinateur ne peut manipuler que des nombres de 32 bits, il faut bien trouver un moyen de convertir le nombre de 16 bits en un nombre de 32 bits, sans changer sa valeur et en conservant son signe. Cette conversion d'un entier en un entier plus grand, qui conserve valeur et signe s'appelle l''''extension de signe'''.
L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
L'explication plus simple tient dans la manière de coder le bit de poids fort. Prenons l'exemple de la conversion d'un entier de 5 bits en un entier de 8bits. Les 4 bits de poids faible ont un poids positif (on les additionne), alors que le bit de poids fort a un poids négatif. Le nombre encodé vaut : <math>\text{Bit de poids fort} \times -16 + \text{Valeur encodée sur les 4 bits de poids faible}</math>. La valeur encodée sur 4 bits reste la même après extension de signe, car les poids des bits de poids faible ne changent pas. Par contre, le bit de poids fort change. Sur 8 bits, la valeur -16 est encodée par un nombre de la forme 1111 0000. En remplaçant le bit de poids fort par sa valeur calculée sur plus de bits, on remarque que les bits de poids fort ont été remplacés par des 1.
===La représentation négabinaire===
Enfin, il existe une dernière méthode, assez simple à comprendre, appelée '''représentation négabinaire'''. Dans cette méthode, les nombres sont codés non en base 2, mais en base -2. Oui, vous avez bien lu : la base est un nombre négatif. Dans les faits, la base -2 est similaire à la base 2 : il y a toujours deux chiffres (0 et 1), et la position dans un chiffre indique toujours par quelle puissance de 2 il faut multiplier, sauf qu'il faudra ajouter un signe moins une fois sur 2. Concrètement, les puissances de -2 sont les suivantes : 1, -2, 4, -8, 16, -32, 64, etc. En effet, un nombre négatif multiplié par un nombre négatif donne un nombre positif, ce qui fait qu'une puissance sur deux est négative, alors que les autres sont positives. Ainsi, on peut représenter des nombres négatifs, mais aussi des nombres positifs dans une puissance négative.
Par exemple, la valeur du nombre noté 11011 en base -2 s'obtient comme suit :
{|class="wikitable"
|-
!…
!-32
!16
!-8
!4
!-2
!1
|-
|…
|1
|1
|1
|0
|1
|1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(−8×1)+(4×0)+(−2×1)+(1×1)=−32+16−8−2+1=−25.
==Les nombres à virgule fixe==
On sait donc comment sont stockés nos nombres entiers dans un ordinateur. Néanmoins, les nombres entiers ne sont pas les seuls nombres que l'on utilise au quotidien : il nous arrive d'en utiliser à virgule. Notre ordinateur n'est pas en reste : il est lui aussi capable de manipuler de tels nombres. Dans les grandes lignes, il peut utiliser deux méthodes pour coder des nombres à virgule en binaire : La virgule fixe et la virgule flottante. Parlons d'abord de la virgule fixe, plus simple à comprendre.
===La virgule fixe : des entiers cachés===
La méthode de la virgule fixe consiste à émuler les nombres à virgule à partir de nombres entiers. Un nombre à virgule fixe est codé par un nombre entier proportionnel au nombre à virgule fixe. Pour obtenir la valeur de notre nombre à virgule fixe, il suffit de diviser l'entier servant à le représenter par le facteur de proportionnalité. Par exemple, pour coder 1,23 en virgule fixe, on peut choisir comme « facteur de conversion » 1000, ce qui donne l'entier 1230.
Généralement, les informaticiens utilisent une puissance de deux comme facteur de conversion, pour simplifier les calculs. En faisant cela, on peut écrire les nombres en binaire et les traduire en décimal facilement. Pour l'exemple, cela permet d'écrire des nombres à virgule en binaire comme ceci : 1011101,1011001. Et ces nombres peuvent se traduire en décimal avec la même méthode que des nombres entier, modulo une petite différence. Comme pour les chiffres situés à gauche de la virgule, chaque bit situé à droite de la virgule doit être multiplié par la puissance de deux adéquate. La différence, c'est que les chiffres situés à droite de la virgule sont multipliés par une puissance négative de deux, c'est à dire par <math>1 \over 2</math>, <math>1 \over 4</math>, <math>1 \over 8</math>, <math>1 \over 16</math>, <math>1 \over 32</math>, ...
[[File:Fixed-point binary representation.svg|centre|vignette|upright=2.5|Nombres à virgule fixe.]]
Cette méthode est assez peu utilisée de nos jours, quoiqu'elle puisse avoir quelques rares applications relativement connue. Un bon exemple est celui des banques : les sommes d'argent déposées sur les comptes ou transférées sont codés en virgule fixe. Les sommes manipulées par les ordinateurs ne sont pas exprimées en euros, mais en centimes d'euros. Et c'est une forme de codage en virgule fixe dont le facteur de conversion est égal à 100. La raison de ce choix est que les autres méthodes de codage des nombres à virgule peuvent donner des résultats imprécis : il se peut que les résultats doivent être tronqués ou arrondis, suivant les opérandes. Cela n'arrive jamais en virgule fixe, du moins quand on se limite aux additions et soustractions.
===Les nombres à virgule non-représentables en binaire===
Quel que soit la façon de représenter les nombres à virgules, il existe des nombres qui ne peuvent être représentés de manière exacte, à savoir avec un nombre fini de décimales après la virgule. En soi, ce n'est pas spécifique au binaire, on a la même chose en décimal. Par exemple, la fraction 1/3 en décimal s'écrit 0.3333333..., avec une infinité de 3. La même chose existe en binaire, mais pour des nombres différents.
Déjà, évacuons le cas des nombres irrationnels, à savoir les nombres qui ne peuvent pas s'écrire sous la forme d'une fraction, comme <math>pi</math> ou <math>e</math>. Ils ont une infinité de décimales que ce soit en binaire, en décimal, en hexadécimal, ou autre. Ils ne sont pas représentables avec un nombre fini de décimales quelle que soit la base utilisée. Concentrons-nous sur des nombres qui ne sont pas dans ce cas, et qui ont un nombre fini ou infini de décimales.
Le passage de la base 10 à la base 2 change le nombre de décimales, et peut faire passer d'un nombre fini de décimales à un nombre infini. Par exemple, <math>\frac{1}{5} = 0.2</math> est représenté en binaire avec une séquence infinie de bits : <math>0.001100110011...</math>. Les nombres étant en base binaire et représenté avec un nombre limité de bits, il existe certains nombres décimaux triviaux qui ne sont pas représentables avec un nombre fini de décimales.
Et la réciproque n'est pas vraie : tout nombre binaire avec un nombre fini de décimales en binaire est représentable avec un nombre fini de décimales en base 10. Ceci est lié à la décomposition en facteur premier des bases utilisées :
* <math>2 = 2^1</math>
* <math>10 = 2^1 \times 5^1</math>
Le nombre 10 possède tous les facteurs premiers de 2, mais 2 n'a pas de 5 dans sa décomposition.
==Les nombres flottants==
Les nombres à virgule fixe ont aujourd'hui été remplacés par les '''nombres à virgule flottante''', où le nombre de chiffres après la virgule est variable. Le codage d'un nombre flottant est basée sur son ''écriture scientifique''. Pour rappel, en décimal, l’écriture scientifique d'un nombre consiste à écrire celui-ci comme un produit entre un nombre <math>a</math> et une puissance de 10, avec <math>a</math> étant compris entre 1 et 9. Ce qui donne :
: <math>a \times 10^e</math>, avec <math>a \in [1, 10 [</math>
Le nombre <math>a</math> est appelé le ''significande'' et il est compris entre 1 (inclus) et 10 (exclu). Cette contrainte garantit que l'écriture scientifique d'un nombre est unique, qu'il n'y a qu'une seule façon d'écrire un nombre en notation scientifique. Pour cela, on impose le nombre de chiffre à gauche de la virgule et le plus simple est que celui-ci soit égal à 1. Mais il faut aussi que celui-ci ne soit pas nul. En effet, si on autorise de mettre un 0 à gauche de la virgule, il y a plusieurs manières équivalentes d'écrire un nombre. Ces deux contraintes font que le significande doit être égal ou plus grand que 1, mais strictement inférieur à 10. Par contre, on peut mettre autant de décimales que l'on veut.
En binaire, c'est la même chose, mais avec une puissance de deux. Cela implique de modifier la puissance utilisée : au lieu d'utiliser une puissance de 10, on utilise une puissance de 2.
: <math>a \times 2^e</math>, avec <math>a \in [1, 2 [</math>
Le significande est aussi altéré, au même titre que la puissance, même si les contraintes sont similaires à celles en base 10. En effet, le nombre <math>a</math> ne possède toujours qu'un seul chiffre à gauche de la virgule, comme en base 10. Vu que seuls deux chiffres sont possibles (0 et 1) en binaire, on s'attend à ce que le chiffre situé à gauche de la virgule soit un zéro ou un 1. Mais rappelons que le chiffre à gauche doit être non-nul, pour les mêmes raisons qu'en décimal. En clair, le significande a forcément un bit à 1 à gauche de la virgule. Pour récapituler, l'écriture scientifique binaire d'un nombre consiste à écrire celui-ci sous la forme :
: <math>1,m \times 2^e</math>, avec <math>m \in [0, 1 [</math>
La partie fractionnaire du nombre <math>a</math>, qu'on appelle la '''mantisse'''.
[[File:Mantissa.svg|centre|vignette|Écriture scientifique (anglais).]]
Pour déterminer l'écriture scientifique en binaire d'un nombre quelconque, la procédure dépend de la valeur du nombre en question. Tout dépend s'il est dans l'intervalle <math>[1, 2 [</math>, au-delà de 2 ou en-dessous de 1.
* Pour un nombre entre 1 (inclus) et 2 (exclu), il suffit de le traduire en binaire. Son exposant est 0.
* Pour un nombre au-delà de 2, il faut le diviser par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant est alors le nombre de fois qu'il a fallu diviser par 2.
* Pour un nombre plus petit que 1, il faut le multiplier par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant se calcule en prenant le nombre de fois qu'il a fallu multiplier par 2, et en prenant l'opposé (en mettant un signe - devant le résultat).
===Le codage des nombres flottants et la norme IEEE 754===
La '''virgule flottante''' encode l'écriture scientifique d'un nombre/ Elle utilise pour cela trois données : la mantisse, l'exposant et un bit de signe. Les trois sont concaténés dans un seul "nombre", plus précisément une suite de bits qui est découpée en un bit de signe, un entier qui encode l'exposant, et un entier pour la mantisse.
[[File:General floating point frac.svg|centre|vignette|upright=2|Format Général d'un nombre flottant.]]
[[File:General floating point.svg|centre|vignette|upright=2|Format Général d'un nombre flottant, plus détaillé.]]
Voici à peu-près comment les nombres flottants sont répartis sur une ligne numérique. Vous remarquerez que les flottants sont de plus en plus éloignés au fur et à mesure qu'on s'éloigne de zéro. La raison est l'encodage avec un exposant et une mantisse. L'écart entre deux flottants reste stable pour un même exposant, mais il y a une cassure quand on passe d'un exposant au suivant.
[[File:FloatingPointPrecisionAugmented.png|centre|vignette|upright=3|Floating Point Precision.]]
Le standard actuel pour la virgule flottante est la '''norme IEEE 754'''. Cette norme va (entre autres) définir quatre types de flottants différents, qui utilisent des exposants/mantisses de taille différente. Les deux formats les plus courants sont les flottants '''simple précision''' et '''double précision'''. Les premiers sont codés sur 32 bits, les seconds sur 64 bits. Il y a aussi des formats de '''précision étendus''', codés sur 80 bits ou plus.
[[File:Single double extended2.gif|centre|vignette|upright=2.5|Formats des nombres flottants IEEE754.]]
===L'encodage de la mantisse et de l'exposant===
L'exposant peut être aussi bien positif que négatif (pour permettre de coder des nombres très petits). Pour cela, il est encodé en représentation par excès. Pour un exposant de n bits, le biais est égal à <math>2^{n-1} - 1</math>.
L'encodage de la mantisse a lui aussi ses subtilités. En effet, la mantisse est un nombre à virgule, avec une partie entière et une partie fractionnaire. Le tout est encodé en virgule fixe, reste à choisir son encodage exact. Pour simplifier l'encodage, l'exposant est choisir de manière à ce qu'il y ait un seul chiffre avant la virgule, ce qui permet de coder la partie entière sur 1 bit. En clair, il faut encoder le bit à gauche de la virgule, puis ceux à droite. Les anciens PC utilisaient cette solution, du temps de la FPU x87. Ils géraient nativement des nombres flottants de 80 bits, appelés '''flottants étendus x87'''. Ils utilisaient une mantisse codée sur 64 bits, dont un bit pour la partie entière, et 63 pour la partie fractionnaire.
[[File:X86 Extended Floating Point Format.svg|centre|vignette|upright=2|Flottants étendus des anciens CPU X86.]]
Une simplification plus importante choisir l'exposant de manière à ce que la partie entière vaut 1, systématiquement. En faisant cela, on n'a plus à encoder la partie entière, pas besoin de lui réserver un bit. La mantisse encode donc uniquement la partie fractionnaire, la partie entière n'est pas encodée. Une telle méthode est donc plus efficace. A quelques détails près : de nombreuses valeurs ne peuvent pas être encodées facilement. Par exemple, le zéro ne peut pas être encodé avec cette technique. Et c'est là un des gros défaut de cette technique : ne pas pouvoir encoder le zéro est un énorme problème !
Pour éviter ce défaut, les nombres flottants utilisent une technique : la partie entière n'est pas codée directement. La partie entière vaut 0 pour certains flottants, elle vaut 1 pour d'autres. Vu que la partie entière fait un bit, mais n'est pas encodée, elle est aussi appelé le '''bit implicite'''. Déterminer le bit implicite demande de regarder l'exposant . Typiquement, si l'exposant vaut zéro, alors la partie entière vaut 0. Sinon, elle vaut 1.
Une telle solution permet d'encoder un zéro, mais aussi des nombres flottants très petits. Ces derniers ne sont pas codés en respectant les règles pour le significande et ont un 0 à gauche de la virgule. Ils sont appelés les '''dénormaux''', aussi nommés flottants dénormalisés. Les dénormaux sont des nombres flottants où l'exposant est le plus petit possible. Le '''zéro''' est un dénormal particulier dont la mantisse est nulle. Au fait, remarquez que le zéro est codé deux fois à cause du bit de signe : on se retrouve avec un -0 et un +0.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur minimale (0 en binaire) || Mantisse différente de zéro (dénormal) ou égale à zéro (zéro)
|}
===Les spécificités de la norme IEEE754===
Les nombres flottants IEE754 ont quelques spécificités qu'il vaut mieux voir à part. Premièrement, ils n'encodent pas la partie entière d'un flottant, la mantisse encode uniquement la partie fractionnaire. La technique précédente, qui utilise l'exposant pour déterminer le bit implicite, est utilisée. Les flottants IEE754 ont une partie entière de la mantisse qui vaut 1, sauf pour quelques rares dénormaux qui font exceptions. Les dénormaux sont des nombres flottants où l'exposant est le plus petit possible.
Le '''zéro''' est un dénormal particulier dont la mantisse est nulle. Au fait, remarquez que le zéro est codé deux fois à cause du bit de signe : on se retrouve avec un -0 et un +0.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur minimale (0 en binaire) || Mantisse différente de zéro (dénormal) ou égale à zéro (zéro)
|}
Fait étrange, la norme IEEE754 permet de représenter '''l'infini''', aussi bien en positif qu'en négatif. Celui-ci est codé en mettant l'exposant à sa valeur maximale et la mantisse à zéro. Et le pire, c'est qu'on peut effectuer des calculs sur ces flottants infinis. Mais cela a peu d'utilité.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse égale à zéro
|}
Mais malheureusement, l'invention des flottants infinis n'a pas réglé tous les problèmes. Par exemple, quel est le résultat de <math>\infty - \infty</math> ? Ou encore <math>0 - \infty</math> ? Autant prévenir tout de suite : mathématiquement, on ne peut pas savoir quel est le résultat de ces opérations. Pour pouvoir résoudre ces calculs, il a fallu inventer un nombre flottant qui signifie « je ne sais pas quel est le résultat de ton calcul pourri ». Ce nombre, c'est '''NaN'''.
NaN est l'abréviation de ''Not A Number'', ce qui signifie : n'est pas un nombre. Un NaN a un exposant dont la valeur est maximale, mais une mantisse différente de zéro. Pour être plus précis, il existe différents types de NaN, qui diffèrent par la valeur de leur mantisse, ainsi que par les effets qu'ils peuvent avoir. Malgré son nom explicite, on peut faire des opérations avec NaN, mais cela ne sert pas vraiment à grand chose : une opération arithmétique appliquée avec un NaN aura un résultat toujours égal à NaN.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse différente de zéro
|}
Au-delà du support de quelques flottants spéciaux, la norme impose une gestion des arrondis ou erreurs, qui arrivent lors de calculs particuliers. En voici la liste :
{|class="wikitable"
|-
! Nom de l’exception
! Description
|-
! Invalid operation
| Opération qui produit un NAN. Elle est levée dans le cas de calculs ayant un résultat qui est un nombre complexe, ou quand le calcul est une forme indéterminée. Pour ceux qui ne savent pas ce que sont les formes indéterminées, voici en exclusivité la liste des calculs qui retournent NaN : <math>\infty - \infty</math>, <math>0 \times \infty</math>, <math>0^0</math>, <math>1^{\infty}</math>, <math>\infty^0</math>.
|-
! Overflow
| Résultat trop grand pour être stocké dans un flottant. Le plus souvent, on traite l'erreur en arrondissant le résultat en vue de la taille de la mantisse;
|-
! Underflow
| Pareil que le précédent, mais avec un résultat trop petit. Le plus souvent, on traite l'erreur en arrondissant le résultat vers 0.
|-
! Division par zéro
| Le nom parle de lui-même. La réponse la plus courante est de répondre + ou - l'infini.
|-
! '''Inexact'''
| Le résultat ne peut être représenté par un flottant et on doit l'arrondir.
|}
La dernière exception, ''Inexact'', survient quand le résultat d'une opération flottante donne un résultat qui ne peut être codé par un flottant. On est alors obligé d'arrondir ou de tronquer le résultat de façon à le faire rentrer dans un flottant. Pour éviter que des ordinateurs différents utilisent des méthodes d'arrondis différentes, la norme IEEEE754 impose le support de quatre modes d'arrondis :
* Arrondir vers + l'infini ;
* vers - l'infini ;
* vers zéro ;
* vers le nombre flottant le plus proche.
===Les nombres flottants logarithmiques===
Les '''nombres flottants logarithmiques''' sont des nombres flottants composés d'un bit de signe et d'un exposant, sans mantisse. La mantisse est totalement implicite : tous les flottants logarithmiques ont la même mantisse, qui vaut 1. Il ne reste que l'exposant, qui est tout simplement le logarithme en base 2 du nombre encodé, d'où le nom de codage flottant logarithmique donné à cette méthode. Attention toutefois : l'exposant est ici un nombre fractionnaire, codé en virgule fixe. Le choix d'un exposant fractionnaire permet de représenter pas mal de nombres de taille diverses.
{|class="wikitable"
|-
!
! Bit de signe
! Exposant
|-
! Représentation binaire
| 0
| 01110010101111
|-
! Représentation décimale
| +
| 1040,13245464
|}
L'utilité de cette représentation est de simplifier certains calculs, comme les multiplications, divisions, puissances, etc. En effet, les mathématiques nous disent que le logarithme d'un produit est égal à la somme des logarithmes : <math>\ln{(a \times b)} = \ln{a} + \ln{b}</math>. Or, il se trouve que les ordinateurs sont plus rapides pour faire des additions/soustractions que pour faire des multiplications/divisions. Donc, la représentation logarithmique permet de remplacer les multiplications/divisions par des additions/soustractions, plus simples et plus rapides pour l'ordinateur.
Évidemment, les applications des flottants logarithmiques sont rares, limitées à quelques situations bien précises (traitement d'image, calcul scientifique spécifiques).
==Les encodages binaires alternatifs==
Outre le binaire que nous venons de voir, il existe d'autres manières de coder des nombres avec des bits. Et nous allons les aborder dans cette section. Parmi celle-ci, nous parlerons du code Gray, de la représentation ''one hot'', du unaire et du ternaire. Nous en parlons car elles seront utiles dans la suite du cours, bien que de manière assez limitée. Autant nous passerons notre temps à parler du binaire normal, autant les représentations que nous allons voir sont aujourd'hui utilisées dans des cas assez spécifiques. Et elles sont plus courantes que vous ne le pensez.
===Le code Gray===
Le '''code Gray''' est un encodage binaire qui a une particularité très intéressante : deux nombres consécutifs n'ont qu'un seul bit de différence. Pour exemple, voici ce que donne le codage des 8 premiers entiers sur 3 bits :
{|class="wikitable"
! Décimal
! Binaire naturel
! Codage Gray
|-
|0||000||000
|-
|1||001||001
|-
|2||010||011
|-
|3||011||010
|-
|4||100||110
|-
|5||101||111
|-
|6||110||101
|-
|7||111||100
|}
Les utilisations du code Gray sont assez nombreuses, bien qu'on n'en croise pas tous les jours. Un exemple : le code Gray est très utile dans certains circuits appelés les compteurs, qui mémorisent un nombre et l'incrémentent (+1) ou le décrémentent (-1) suivant les besoins de l'utilisateur. Il est aussi utilisé dans des scénarios difficiles à expliquer ici (des codes correcteurs d'erreur ou des histoires de passages de domaines d'horloge). Mais le point important est que ce code sera absolument nécessaire dans quelques chapitres, quand nous parlerons des tables de Karnaugh, un concept important pour la conception de circuits électroniques. Ne passez pas à côté de cette section.
Pour construire ce code Gray, on peut procéder en suivant plusieurs méthodes, les deux plus connues étant la méthode du miroir et la méthode de l'inversion.
[[File:Binary-reflected Gray code construction.svg|vignette|Construction d'un code gray par la méthode du miroir.]]
La méthode du miroir est relativement simple. Pour connaître le code Gray des nombres codés sur n bits, il faut :
* partir du code Gray sur n-1 bits ;
* symétriser verticalement les nombres déjà obtenus (comme une réflexion dans un miroir) ;
* rajouter un 0 au début des anciens nombres, et un 1 au début des nouveaux nombres.
Il suffit de connaître le code Gray sur 1 bit pour appliquer la méthode : 0 est codé par le bit 0 et 1 par le bit 1.
Une autre méthode pour construire la suite des nombres en code Gray sur n bits est la méthode de l'inversion. Celle-ci permet de connaître le codage du nombre n à partir du codage du nombre n-1, comme la méthode du dessus. On part du nombre 0, systématiquement codé avec uniquement des zéros. Par la suite, on décide quel est le bit à inverser pour obtenir le nombre suivant, avec la règle suivante :
* si le nombre de 1 est pair, il faut inverser le dernier chiffre.
* si le nombre de 1 est impair, il faut localiser le 1 le plus à droite et inverser le chiffre situé à sa gauche.
Pour vous entraîner, essayez par vous-même avec 2, 3, voire 5.
===Les représentations ''one-hot'' et unaire===
La représentation ''one-hot'' et la représentation unaire sont deux représentations assez liées, mais légèrement différentes.
La '''représentation unaire''' est la représentation en base 1. Avec elle, le zéro est codé en mettant tous les bits du nombre à zéro, la valeur 1 est encodée avec la valeur 000...0001, le deux avec 000...0011, le trois avec 000...0111, etc. Pour résumer, le nombre N est encodé en mettant les N bits de poids faible à 1, à l'exception du zéro qui est encodé...par un zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! Unaire
|-
| 0 || 000 || 0000 0000
|-
| 1 || 001 || 0000 0001
|-
| 2 || 010 || 0000 0011
|-
| 3 || 011 || 0000 0111
|-
| 4 || 100 || 0000 1111
|-
| 5 || 101 || 0001 1111
|-
| 6 || 110 || 0011 1111
|-
| 7 || 111 || 0111 1111
|-
| 8 || 1000 || 1111 1111
|}
Pour convertir un nombre codé en unaire vers le binaire, il suffit de compter le nombre de 1. C'est ni plus ni moins ce que fait l'opération dite de ''population count'' dont nous avons parlé plus haut. La représentation unaire permet d'encoder N+1 valeurs sur N bits, car le zéro est encodé à part. Une valeur pour le zéro, plus une par bit.
Avec la '''représentation ''one-hot''''', un nombre est codé sur N bits, dont un seul bit est à 1. La position du 1 dans le nombre indique sa valeur. Précisément, la valeur encodée est égale au poids du bit à 1. En faisant cela, le zéro est codé en mettant le bit de poids faible à 1, la valeur 1 est codée avec les bits 00...00010, la valeur 2 est codée par les bits 00...000100, etc.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Elle permet d'encoder N valeurs différentes avec N bits, zéro inclus. On perd une valeur par rapport à l'unaire, car le zéro n'et pas encodé à part du reste. La traduction du binaire vers le ''one-hot'' est réalisé par un circuit nommé décodeur, que nous verrons d'ici quelques chapitres. De même, la traduction d'un nombre encodé en ''one-hot'' vers le binaire est réalisée par un circuit électronique appelé l'encodeur, qui est en quelque sorte l'inverse de l'encodeur. Les deux circuits sont très utilisés en électronique, ce sont des circuits de base, que nous n'aurons de cesse d'utiliser pour fabriquer d'autres circuits.
L'utilité de ces deux représentations n'est pas évidente. Mais sachez qu'elle le deviendra quand nous parlerons des circuits appelés les "compteurs", tout comme ce sera le cas pour le code Gray. Elles sont très utilisées dans des circuits appelés des machines à état, qui doivent incorporer des circuits compteurs efficients. Et ces représentations permettent d'avoir des circuits pour compter qui sont très simples, efficaces, rapides et économes en circuits électroniques. La représentation unaire sera aussi utile à la toute fin du cours, dans les chapitres liés à l'exécution dans le désordre. Il en sera fait référence quand nous parlerons de fenêtres d'instruction, d'émission dans l'ordre, de ''scoreboarding'', etc.
Il faut aussi noter qu'il a existé des ordinateur qui manipulaient des nombres codés en unaire. Enfin presque, la représentation utilisée était proche de l'unaire, mais ressemblait fortement. De tels ordinateurs étaient appelés des ''ordinateurs stochastiques'' et nous les aborderons à la toute fin de ce wikilivre, dans un chapitre annexe portant sur les ordinateurs ternaires (qui comptent en base 3) et unaires.
==Les encodages hybrides entre décimal et binaire==
Pour terminer, nous allons voir les représentations qui mixent binaire et décimal. Il s'agit en réalité d'encodage qui permettent de manipuler des nombres codés en décimal, mais les chiffres sont codés en utilisant des bits. L'encodage ''Binary Coded Decimal'' est le plus connu de cette catégorie, mais il y en a quelques autres, qui sont moins connus. De telles représentations étaient très utilisées au début de l'informatique, mais sont aujourd'hui tombées en désuétude. Pour désigner ces encodages, nous parlerons d''''encodages bit-décimaux''' : décimaux pour préciser qu'ils encodent des nombres codés en décimal, bit pour préciser que les chiffres sont codés avec des bits.
De tels encodages étaient utilisés sur les tous premiers ordinateurs pour faciliter le travail des programmeurs, mais aussi sur les premières calculettes. Ils sont utiles dans les applications où on doit manipuler chaque chiffre décimal séparément des autres. Un exemple classique est celui d'une horloge digitale. Ils ont aussi l'avantage de bien se marier avec les nombres à virgule. Les représentations des nombres à virgule fixe ou flottante ont le défaut que des arrondis peuvent survenir. Par exemple, la valeur 0,2 est codée comme suit en binaire normal : 0.00110011001100... et ainsi de suite jusqu’à l'infini. Avec les encodages bit-décimaux, on n'a pas ce problème, 0,2 étant codé 0000 , 0010.
Il a existé des ordinateurs qui travaillaient uniquement avec de tels encodages, appelés des '''ordinateurs décimaux'''. Ils étaient assez courants entre les années 60 et 70, même s'ils ne représentent pas la majorité des architectures de l'époque. Avec eux, la mémoire n'était pas organisée en octets, mais elle stockait des chiffres décimaux codés sur 5-6 bits. Le processeur faisait des calculs sur des chiffres bit-décimaux directement.
Leur grand avantage est leur très bonne performance dans les taches de bureautique, de comptabilité, et autres. Les processeurs de l'époque recevaient des entiers codés en bit-décimal de la part des entrées-sorties, et devaient les traiter. Les processeurs binaires devaient faire des conversions décimal-binaire pour communiquer avec les entrées-sorties, mais pas les processeurs décimaux. Le gain en performance pouvait être substantiel dans certaines applications.
Les ordinateurs décimaux se classent en deux sous-types bien précis. Les premiers gèrent des entiers qui ont un nombre de chiffres fixes. Par exemple, l'IBM 7070 gérait des entiers de 10 chiffres, plus un signe +/- pour les entiers signés. Le processeur faisait les calculs directement en bit-décimal, il gérait des entiers faisant environ 10-15 chiffres décimaux et savait faire des calculs avec de tels nombres. Le second sous-type effectue les calculs chiffre par chiffre et géraient des nombres de taille variable, sans limite de chiffres ! Nous reparlerons de ces derniers dans un chapitre ultérieur, quand nous parlerons de la différence entre byte et mot. Pour le moment, ne gardez à l'esprit que les processeurs gérant un nombre fixe de chiffres décimaux, plus simples à comprendre.
===Le ''Binary Coded Decimal''===
Le '''''Binary Coded Decimal''''', abrévié BCD, est une représentation qui mixe binaire et décimal. Avec cette représentation, les nombres sont écrits en décimal, comme nous en avons l'habitude dans la vie courante, sauf que chaque chiffre décimal est directement traduit en binaire sur 4 bits. Prenons l'exemple du nombre 624 : le 6, le 2 et le 4 sont codés en binaire séparément, ce qui donne 0110 0010 0100.
{| class="wikitable"
|+ Codage BCD
|-
! Nombre encodé (décimal)
! scope="col" colspan="4" | BCD
|-
| 0 || 0 || 0 || 0 || 0
|-
| 1 || 0 || 0 || 0 || 1
|-
| 2 || 0 || 0 || 1 || 0
|-
| 3 || 0 || 0 || 1 || 1
|-
| 4 || 0 || 1 || 0 || 0
|-
| 5 || 0 || 1 || 0 || 1
|-
| 6 || 0 || 1 || 1 || 0
|-
| 7 || 0 || 1 || 1 || 1
|-
| 8 || 1 || 0 || 0 || 0
|-
| 9 || 1 || 0 || 0 || 1
|}
On peut remarquer que 4 bits permettent de coder 16 valeurs, là où il n'y a que 10 chiffres. Dans le BCD proprement dit, les combinaisons de bits qui correspondent à 10, 11, 12, 13, 14 ou 15 ne sont tout simplement pas prises en compte. Sur quelques ordinateurs, ces combinaisons codent des chiffres décimaux en double : certains chiffres pouvaient être codés de deux manières différentes. Il est aussi possible d'utiliser ces valeurs pour coder quelque chose d'autre que des chiffres. Par exemple, il est possible de les utiliser pour coder un signe + ou -, afin de gérer les entiers relatifs. Une autre possibilité, complémentaire de la précédente, utilise ces valeurs en trop pour coder une virgule, afin de gérer les nombres non-entiers. Les possibilités sont nombreuses.
Le support du BCD implique souvent que le processeur supporte des '''opérations BCD''', à savoir des opérations capables de travailler sur des opérandes en BCD et de donner un résultat en BCD. Il faut bien faire la différence entre les opérations en binaire et les opérations en BCD. Par exemple, on n'effectue pas une addition de la même manière en binaire et en décimal/BCD, même si les grandes lignes sont presque identiques. Les différences font que les processeurs doivent avoir des opérations différentes pour les deux encodages, de la même manière que les processeurs gèrent les opérations sur les flottants et les entiers séparément.
Le support du codage BCD est abandonné de nos jours, c'est surtout quelque chose qu'on trouve sur les anciens processeurs. Les architectures 8 et 16 bits supportaient à la fois des opérations binaires et des opérations BCD. Mais il a aussi existé une méthode intermédiaire, qui utilisait des additions binaires normales sur des opérandes BCD. L'idée est que le résultat de l'addition est incorrect, car les valeurs 10 à 15 peuvent apparaître comme chiffre dans le résultat. Mais on peut le corriger pour obtenir le résultat exact en BCD. Pour résumé, les processeurs faisaient des additions en binaire, et corrigeaient le résultat avec une opération spécifique pour obtenir un résultat en BCD. Nous en reparlerons dans le chapitre sur le langage machine et l'assembleur, dans lequel nous étudierons les différentes opérations que supporte un processeur.
===Les encodages compacts du BCD===
Des variantes du BCD visent à réduire le nombre de bits utilisés pour encoder un nombre décimal. Nous allons les appeler les '''encodages BCD compacts'''. Avec certaines variantes, on peut utiliser seulement 7 bits pour coder deux chiffres décimaux, au lieu de 8 bits en BCD normal. Idem pour les nombres à 3 chiffres décimaux, qui prennent 10 bits au lieu de 12. Cette économie est réalisée par une variante du BCD assez compliquée, appelée l''''encodage Chen-Ho'''. Une alternative, appelée le ''Densely Packed Decimal'', arrive à une compression identique, mais avec quelques avantages au niveau de l'encodage. Ces encodages sont cependant assez compliqués à expliquer, surtout à ce niveau du cours, aussi je me contente de simplement mentionner leur existence.
Les encodages BCD compacts sont utilisés par les programmeurs pour stocker des données dans un fichier, en mémoire, mais guère plus. Ils ne sont pas gérés par le processeur directement, on ne peut pas faire de calculs avec, le processeur ne gére pas d'opération BCD supportant de tels encodages. C'est théoriquement possible, mais ça n'a jamais été implémenté dans un processeur, le cout en circuit n'en valait pas la chandelle. Pour faire des calculs sur des nombres en BCD compact, on doit les décompresser et les convertir en BCD normal ou en binaire, puis faire les calculs avec des opérations BCD/binaire usuelles.
===L'encodage ''Excess-3''===
La représentation '''''Excess-3''''' (XS-3) est une variante du BCD, qui a autrefois était utilisée sur d'anciens ordinateurs décimaux. Il s'agit d'une sorte d'hybride entre une représentation par excès et BCD. Chaque chiffre décimal est codé sur plusieurs bits en utilisant une représentation binaire par excès. Le biais est de 3 pour la représentation XS-3, il en existe des variantes avec un excès de 4, 5, mais elles sont moins utilisées. Avec elle, la conversion d'un chiffre décimal se fait comme suit : on prend le chiffre décimal, on ajoute 3, puis on traduit en binaire.
{| class="wikitable"
|+ Codage en ''Excess-3''
|-
! Décimal
! scope="col" colspan="4" | Binaire
|-
| -3 || 0 || 0 || 0 || 0
|-
| -2 || 0 || 0 || 0 || 1
|-
| -1 || 0 || 0 || 1 || 0
|-
| 0 || 0 || 0 || 1 || 1
|-
| 1 || 0 || 1 || 0 || 0
|-
| 2 || 0 || 1 || 0 || 1
|-
| 3 || 0 || 1 || 1 || 0
|-
| 4 || 0 || 1 || 1 || 1
|-
| 5 || 1 || 0 || 0 || 0
|-
| 6 || 1 || 0 || 0 || 1
|-
| 7 || 1 || 0 || 1 || 0
|-
| 8 || 1 || 0 || 1 || 1
|-
| 9 || 1 || 1 || 0 || 0
|-
| 10 || 1 || 1 || 0 || 1
|-
| 11 || 1 || 1 || 1 || 0
|-
| 12 || 1 || 1 || 1 || 1
|}
L'avantage de cette représentation est que l'on peut facilement calculer les soustractions en utilisant une méthode bien précise (celle du complément à 10, je ne détaille pas plus). Le défaut est que le calcul des additions est légèrement plus complexe.
L'XS-3 a été utilisé sur quelques ordinateurs décimaux assez anciens, notamment sur l'UNIVAC I et II.
===Les codes bi-quinaires===
Les '''codes bi-quinaires''' utilisent un mélange entre encodage binaire, encodage ''one-hot'' et encodage décimal. L'idée de base derrière ces codes est d'utiliser la représentation ''one-hot'' pour coder un chiffre BCD. Le problème est que cela demande d'utiliser 10 bits pour encoder un chiffre BCD, ce qui est beaucoup. L'encodage bi-quinaire optimise le tout de manière à diviser par deux le nombre de bits requis pour l'encodage.
Pour simplifier, un chiffre encodé en bi-quinaire est composé de deux parties : une partie binaire de un bit, suivie par d'une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits pour encoder les valeurs 0, 1, 2, 3 et 4. La partie binaire indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9 : 0 à 4 si la partie binaire vaut 0, 5 à 9 si elle vaut 1. La valeur du chiffre se calcule comme suit :
* si la partie binaire vaut 0, elle est égale à la partie quinaire ;
* si la partie binaire vaut 1, on ajoute 5 à la partie quinaire.
L'originalité de ce codage est qu'il permet de facilement compter sur ses doigts : on a deux mains, cinq doigts. De nombreuses langues utilisent ce système pour coder des nombres, comme le Wolof. Et ce système est utilisé dans certains pays pour compter sur ses doigts, voire dans la vie de tous les jours.
[[File:Code Biquinaer.svg|vignette|Code bi-quinaire où partie binaire et quinaire sont encodées en représentation ''one-hot''.]]
L'encodage abordé au-dessus est une version simplifiée de l'encodage bi-quinaire, il en existe d'autres variantes. Le bi-quinaire vu juste au-dessus encode la partie binaire en binaire, et la partie quinaire en ''one-hot''. Mais il est aussi possible d'inverser l'encodage, à savoir encoder la partie binaire avec une représentation ''one-hot'', alors que la partie bi-quinaire est encodée en binaire normal.
Cependant, la version la plus intéressante utilise l'encodage ''one-hot'' pour les deux ! Elle est illustrée ci-contre. Avec cette dernière, un chiffre BCD est encodé sur 7 bits, dont 2 sont obligatoirement à 1 et les 5 restants sont à 0. Cette propriété permet de détecter qu'un bit a été inversé suite à une erreur de transmission ou un problème de stockage. Si un bit est inversé, le résultat aura 1 bit à 1 et 6 à 0, ou alors 3 bits à 1 et 4 à 0, ce qui ne colle pas. Le résultat après inversion ne donnera pas un chiffre bi-quinaire valide.
Une autre variante du bi-quinaire a été utilisée sur l'IBM 1401. Avec elle, la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre encodé par la partie quinaire. L'avantage de ce système est que la gestion des retenues lors des additions était grandement simplifiée.
Le bi-quinaire et ses variantes ont été utilisés sur d'anciens ordinateurs, dont l'IBM 650, l'UNIVAC Solid State et le UNIVAC LARC. La plupart utilisaient des variantes de l'encodage bi-quinaire proprement dit. Pour rentrer vraiment dans le détail, voici les encodages utilisés sur ces machines. La lecture est facultative, il est possible de passer directement à la section suivante :
{| class="wikitable"
|-
! !! IBM 650 !! Remington Rand 409 !! UNIVAC Solid State !! UNIVAC LARC
|-
| Chiffre || 1357-9 bits || 05-01234 || p-5-421 bits || p-5-qqq bits
|-
| 0 || 10-10000 || 0000-0 || 1-0-000 || 1-0-000
|-
| 1 || 10-01000 || 1000-0 || 0-0-001 || 0-0-001
|-
| 2 || 10-00100 || 1000-1 || 0-0-010 || 1-0-011
|-
| 3 || 10-00010 || 0100-0 || 1-0-011 || 0-0-111
|-
| 4 || 10-00001 || 0100-1 || 0-0-100 || 1-0-110
|-
| 5 || 01-10000 || 0010-0 || 0-1-000 || 0-1-000
|-
| 6 || 01-01000 || 0010-1 || 1-1-001 || 1-1-001
|-
| 7 || 01-00100 || 0001-0 || 1-1-010|| 0-1-011
|-
| 8 || 01-00010 || 0001-1 || 0-1-011 || 1-1-111
|-
| 9 || 01-00001 || 0000-1 || 1-1-100|| 0-1-110
|}
==Annexe : pourquoi l'octet existe==
Nous avons vu plus haut que les nombres encodés en binaire sont découpés en octets, des paquets de 8 bits. Et les octets sont présents partout dans la conception d'un ordinateur. Par exemple, la mémoire RAM d'un ordinateur moderne est organisée en octets. Elle contient des cases mémoire, chacune contenant un octet. Les octets sont numérotés, ce qui permet de sélectionner un octet parmi tous les autres. La capacité de la mémoire est le nombre d'octets qu'elle contient, elle s'exprime en kilo-octets (1024 octets), mébi-octets (1024 kilo-octets), gibi-octets (1024 mébi-octets). Les octets sont donc présents partout dans l'informatique moderne.
{|class="wikitable"
|+ Contenu de la RAM, simplifié
|-
! Numéro
! Octet
|-
! 0
| 0000 1111
|-
! 1
| 0101 0101
|-
! 2
| 0111 0110
|-
! 3
| 0011 1010
|-
! 4
| 1001 1001
|-
! ...
| ...
|-
! 1023
| 0110 1010
|-
! 1024
| 0001 1110
|}
Mais pourquoi est-ce que les nombres encodés en binaire sont souvent découpés en octets ? Pourquoi ne pas utiliser des groupes de 3, 5, 6, 7, 9, 10 bits ? La question est d'autant plus légitime que d'anciens ordinateurs faisaient ainsi. Leurs mémoires étaient découpé en cases mémoire, mais chacune faisait 7 ou 9 bits, rien d'impossible à cela. À vrai dire, l'usage des octets au tout début de l'informatique était assez rare. Il y a eu un basculement progressif vers l'octet pendant les années 80-90. Reste à expliquer pourquoi. Si on devait résumer, les deux raisons principales sont le support du BCD et le support des caractères pour le texte. Voyons d'abord l'encodage des caractères en binaire.
===L'encodage du texte : ASCII et autres encodages===
Pour les caractères, les encodages se sont concentrés sur l'encodage des alphabets occidentaux. Ils demandaient d'encoder au minimum toutes les lettres, en majuscules et minuscules, plus les chiffres, une dizaine de caractères pour la ponctuation. À cela, il faut ajouter des symboles utiles comme %, µ, $, £, #, {} et bien d'autres. Au minimum, cela fait au minimum 52 lettres (26 lettres doublées pour les majuscules) + 10 chiffres + une dizaine de caractères pour la ponctuation : cela fait plus de 72 valeurs à encoder. Pour encoder cela en binaire, le minimum est de 7 bits, ce qui permet d'encoder 128 caractères : les 72 caractères minimum, plus quelques autres. L'encodage ASCII utilisait d'ailleurs 7 bits pour encoder des nombres.
On pourrait croire que c'était une raison suffisante pour utiliser des groupes de 7 bits. Mais il y a quelques problèmes avec cela. Premièrement, les encodages 7 bits pour le texte marchent bien pour gérer la langue anglaise et la plupart des langues occidentales, mais d'autres langues ont besoin d'encoder des caractères locaux. Les 128 caractères de l'ASCII ne sont pas suffisant, ce qui demande de passer de 7 à 8 bits. On retombe donc sur un octet ! D'ailleurs, les encodages ASCII améliorés utilisaient les 127 premières valeurs pour l'ASCII normal, mais les 128 suivantes encodaient d'autres caractères. Les encodages en question varient d'un pays ou d'une langue à l'autre, mais ils sont terriblement importants, ce qui est un argument pour l'usage des octets.
===Le support des données encodées sur un ''nibble''===
Une autre raison pour l'usage des octets est qu'il est facile de les découper en deux ''nibbles''. Pour rappel, les ''nibbles'' sont des groupes de 4 bits. Et il se trouve que de nombreuses données sont encodées sur 4 bits, ni plus ni moins. Les ''nibbles'' sont très important pour le support du BCD et de l’hexadécimal. Et c'était un avantage important au début de l'informatique, moins maintenant. Un octet regroupe deux ''nibbles'' : un ''nibble'' de poids faible, un ''nibble'' de poids fort.
[[File:Nibbles in a byte.PNG|centre|vignette|upright=2|Nibbles dans un octet.]]
Un chiffre BCD occupe un ''nibble'', ce qui fait que la taille idéale de l'unité mémoire doit être un multiple de 4 bits : 4 bits, 8 bits, 12 bits, 16 bits, etc. Un ''nibble'' de 4 bits est insuffisant pour encoder des caractères, 8 bits fonctionne parfaitement, pas besoin d'aller plus loin. De plus, l'usage des ''nibbles'' permet de gérer la représentation hexadécimale très facilement. Un chiffre hexadécimal est encodé sur exactement un ''nibble'', un octet regroupe deux chiffres hexadécimaux, deux octets 4 chiffres hexadécimaux, etc. Enfin, 8 bits est une puissance de deux, ce qui simplifie grandement l'encodage des entiers en binaire est est une source de nombreuses simplifications. Les circuits de l'ordinateur sont plus simple à concevoir quand ils manipulent des opérandes de 2, 4, 8, 16, 32, 64, 128 bits.
===L'influence sur la taille des opérandes===
Les anciens ordinateurs étaient conçus pour le calcul scientifique, ou du moins des applications similaires. Ils n'avaient pas besoin de supporter le BCD, ni même de gérer du texte. Ils ne faisaient que des calculs et utilisaient des opérandes flottantes. La taille des opérandes était dictée par des considérations calculatoires. La norme à l'époque était que les calculs scientifiques devaient gérer des opérandes pouvant aller jusqu'à 10 chiffres décimaux. Et cela demande d'utiliser des opérandes entiers de 35 bits, avec un bit en plus pour encoder des opérandes positives ou négatives. Les 36 bits étaient parfois découpés en 6 groupes de 6 bits chacun, au lieu des octets de 8 bits.
Mais ces groupes de 6 bits étaient souvent peu utilisés. Prenons l'exemple des mémoires RAM. Plus haut, j'ai dit qu'une mémoire RAM normale est composée de plusieurs octets, tous numérotés. Intuitivement, vous vous dites que les anciens ''mainframes'' remplaçaient ces octets par des groupes de 6 bits dans la RAM. Mais en réalité, l'équivalent des RAM de l'époque utilisait des groupes de 36 bits. les groupes de 36 bits étaient aussi numérotés, les numéros étant appelés des adresses mémoire. Par contre, les adresses/numéros étaient codés sur 12 ou 18 bits, c'est à dire sur 2 ou 3 groupes de 6 bits. La raison est que ces numéros sont manipulés par le processeur, qui gère des opérandes de 36 bits. L'idée est que le numéro ne prend que la moitié ou un tiers des 36 bits gérés par le processeur.
Avec l'apparition de l'informatique commerciale, les ordinateurs ont du s'adapter aux besoins des entreprises, pour gérer la comptabilité, des applications financières, des documents texte, etc. Et cela demandait de supporter à la fois le BCD et les caractères/lettres. Il a fallu trouver une unité mémoire capable de gérer à la fois nombres entiers, chiffres BCD et caractères. L'octet était parfait pour ça. Les ''mainframes'' sont adaptés en supportant des caractères codés sur 6, 7 ou 8 bits. Mais le support demandait d'utiliser des circuits qui découpaient un groupe de 36 bits en plusieurs caractères ou en plusieurs chiffres BCD. Le tout demandait des circuits de décalage et de masquage assez complexes. Les ordinateurs 8 et 16 bits manipulaient des octets nativement : leur mémoire RAM gérait des octets, le processeur traitait des opérandes de 8, 16 ou 32 bits. Ils n'avaient pas besoin de matériel complexe pour extraire un caractère ou un chiffre BCD d'un octet/doublet/multiplet. C'était un avantage que les anciens ''mainframes'' n'avaient pas.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'encodage des données
| prevText=L'encodage des données
| next=Les codes de détection/correction d'erreur
| nextText=Les codes de détection/correction d'erreur
}}
</noinclude>
gdgqccd2lyzsqs2aj2aom50fdxyla21
775648
775647
2026-11-02T20:00:03Z
Mewtow
31375
/* Les nombres flottants */
775648
wikitext
text/x-wiki
Dans le chapitre précédent, nous avons vu que les ordinateurs actuels utilisent un codage binaire. Ce codage binaire ne vous est peut-être pas familier. Aussi, dans ce chapitre, nous allons apprendre comment coder des nombres en binaire. Nous allons commencer par le cas le plus simple : les nombres positifs. Par la suite, nous aborderons les nombres négatifs. Et nous terminerons par les nombres à virgules, appelés aussi nombres flottants.
==Le codage des nombres entiers positifs==
Pour coder des nombres entiers positifs, il existe plusieurs méthodes : le binaire, l’hexadécimal, le code Gray, le décimal codé binaire et bien d'autres encore. La plus connue est certainement le binaire, secondée par l'hexadécimal, les autres étant plus anecdotiques. Pour comprendre ce qu'est le binaire, il nous faut faire un rappel sur les nombres entiers tel que vous les avez appris en primaire, à savoir les entiers écrits en décimal. Prenons un nombre écrit en décimal : le chiffre le plus à droite est le chiffre des unités, celui à côté est pour les dizaines, suivi du chiffre des centaines, et ainsi de suite. Dans un tel nombre :
* on utilise une dizaine de chiffres, de 0 à 9 ;
* chaque chiffre est multiplié par une puissance de 10 : 1, 10, 100, 1000, etc. ;
* la position d'un chiffre dans le nombre indique par quelle puissance de 10 il faut le multiplier : le chiffre des unités doit être multiplié par 1, celui des dizaines par 10, celui des centaines par 100, et ainsi de suite.
Exemple avec le nombre 1337 :
<math>1337 = 1 \times 1000 + 3 \times 100 + 3 \times 10 + 7 \times 1</math>
Pour résumer, un nombre en décimal s'écrit comme la somme de produits, chaque produit multipliant un chiffre par une puissance de 10. On dit alors que le nombre est en base 10.
<math>1337 = 1 \times 10^3 + 3 \times 10^2 + 3 \times 10^1 + 7 \times 10^0</math>
Ce qui peut être fait avec des puissances de 10 peut être fait avec des puissances de 2, 3, 4, 125, etc : n'importe quel nombre entier strictement positif peut servir de '''base'''. En informatique, on utilise rarement la base 10 à laquelle nous sommes tant habitués. On utilise à la place deux autres bases :
* La base 2 (système binaire) : les chiffres utilisés sont 0 et 1 ;
* La base 16 (système hexadécimal) : les chiffres utilisés sont 0, 1, 2, 3, 4, 5, 6, 7, 8 et 9 ; auxquels s'ajoutent les six premières lettres de notre alphabet : A, B, C, D, E et F.
===Le système binaire===
En binaire, on compte en base 2. Cela veut dire qu'au lieu d'utiliser des puissances de 10 comme en décimal, on utilise des puissances de deux : n'importe quel nombre entier peut être écrit sous la forme d'une somme de puissances de 2. Par exemple 6 s'écrira donc 0110 en binaire : <math>0 \times 2^3 + 1 \times 2^2 + 1 \times 2^1 + 0 \times 2^0 = 6</math>. On peut remarquer que le binaire n'autorise que deux chiffres, à savoir 0 ou 1 : ces chiffres binaires sont appelés des '''bits''' (abréviation de Binary Digit). Pour simplifier, on peut dire qu'un bit est un truc qui vaut 0 ou 1. Pour résumer, tout nombre en binaire s'écrit sous la forme d'un produit entre bits et puissances de deux de la forme :
<math>a_0 \times 2^0 + a_1 \times 2^1 + a_2 \times 2^2 + a_3 \times 2^3 + a_4 \times 2^4 +...+ a_n \times 2^n</math>
Les coefficients <math>a_0, a_1, a_2</math> sont les bits, l'exposant n qui correspond à un bit <math>a_n</math> est appelé le '''poids''' du bit.
====La terminologie du binaire====
En informatique, il est rare que l'on code une information sur un seul bit. Dans la plupart des cas, l'ordinateur manipule des nombres codés sur plusieurs bits. Les informaticiens ont donné des noms aux groupes de bits suivant leur taille. Le plus connu est certainement l''''octet''', qui désigne un groupe de 8 bits. Moins connu, on parle de '''''nibble''''' pour un groupe de 4 bits (un demi-octet), de '''doublet''' pour un groupe de 16 bits (deux octets) et de '''quadruplet''' pour un groupe de 32 bits (quatre octets).
Précisons qu'en anglais, le terme ''byte'' n'est pas synonyme d'octet. En réalité, le terme ''octet'' marche aussi bien en français qu'en anglais. Quant au terme ''byte'', il désigne un concept complètement différent, que nous aborderons plus tard (c'est la plus petite unité de mémoire que le processeur peut adresser). Il a existé dans le passé des ordinateurs où le ''byte'' faisait 4, 7, 9, 16, voire 48 bits, par exemple. Il a même existé des ordinateurs où le ''byte'' faisait exactement 1 bit ! Mais sur presque tous les ordinateurs modernes, un ''byte'' fait effectivement 8 bits, ce qui fait que le terme ''byte'' est parfois utilisé en lieu et place d'''octet''. Mais c'est un abus de langage, attention aux confusions ! Dans ce cours, nous parlerons d'octet pour désigner un groupe de 8 bits, en réservant le terme ''byte'' pour sa véritable signification.
À l'intérieur d'un nombre, le '''bit de poids faible''' est celui qui est le plus à droite du nombre, alors que le '''bit de poids fort''' est celui non nul qui est placé le plus à gauche, comme illustré dans le schéma ci-dessous.
C'est le même principe que l'écriture des nombres en base décimale : le chiffre le plus significatif à gauche, et le moins significatif à droite (l'unité ou les décimales après la virgule).
[[File:Msb.svg|centre|vignette|upright=2|Bit de poids fort.]]
[[File:Lsb.svg|centre|vignette|upright=2|Bit de poids faible.]]
Cette terminologie s'applique aussi pour les bits à l'intérieur d'un octet, d'un ''nibble'', d'un doublet ou d'un quadruplet. Pour un nombre codés sur plusieurs octets, on peut aussi parler de l'octet de poids fort et de l'octet de poids faible, du doublet de poids fort ou de poids faible, etc.
====La traduction binaire→décimal====
Pour traduire un nombre binaire en décimal, il faut juste se rappeler que la position d'un bit indique par quelle puissance il faut le multiplier. Ainsi, le chiffre le plus à droite est le chiffre des unités : il doit être multiplié par 1 (<math>2^0</math>). Le chiffre situé immédiatement à gauche du chiffre des unités doit être multiplié par 2 (<math>2^1</math>). Le chiffre encore à gauche doit être multiplié par 4 (<math>2^2</math>), et ainsi de suite. Mathématiquement, on peut dire que le énième bit en partant de la droite doit être multiplié par <math>2^{n-1}</math>. Par exemple, la valeur du nombre noté 1011 en binaire est de <math>8 \times 1 + 4 \times 0 + 2 \times 1 + 1 \times 1 = 11</math>.
[[File:Value of digits in the Binary numeral system.svg|centre|vignette|upright=2|Valeur des chffres dans le système de numération binaire.]]
====La traduction décimal→binaire====
La traduction inverse, du décimal au binaire, demande d'effectuer des divisions successives par deux. Les divisions en question sont des divisions euclidiennes, avec un reste et un quotient. En lisant les restes des divisions dans un certain sens, on obtient le nombre en binaire. Voici comment il faut procéder, pour traduire le nombre 34 :
[[File:Exemple d'illustration de la méthode de conversion décimal vers binaire.png|centre|vignette|upright=2|Exemple d'illustration de la méthode de conversion décimal vers binaire.]]
====Quelques opérations en binaire====
Maintenant que l'on sait coder des nombres en binaire normal, il est utile de savoir comment faire quelques opérations usuelles en binaire. Nous utiliserons les acquis de cette section dans la suite du chapitre, bien que de manière assez marginale.
La première opération est assez spécifique au binaire. Il s'agit d'une opération qui inverse les bits d'un nombre : les 0 deviennent des 1 et réciproquement. Par exemple, le nombre 0001 1001 devient 1110 0110. Elle porte plusieurs noms : opération NOT, opération NON, complémentation, etc. Nous parlerons de '''complémentation''' ou d'opération NOT dans ce qui suit. Beaucoup d'ordinateurs gèrent cette opération, ils savent la faire en un seul calcul. Il faut dire que c'est une opération assez utile, bien que nous ne pouvons pas encore expliquer pourquoi.
[[File:Binary Addition Demonstration.svg|vignette|Exemple d'addition en binaire.]]
La seconde opération à aborder est l'addition. Elle se fait en binaire de la même manière qu'en décimal : Pour faire une addition en binaire, on additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. Sauf que l'on additionne des bits. Heureusement, la table d'addition est très simple en binaire :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
La troisième opération est une variante de l'addition appelée l''''opération XOR''', notée <math>\oplus</math>. Il s'agit d'une addition dans laquelle on ne propage pas les retenues. L'addition des deux bits des opérandes se fait normalement, mais les retenues sont simplement oubliées, on n'en tient pas compte. Le résultat est que l'addition se résume à appliquer la table d'addition précédente :
* 0 <math>\oplus</math> 0 = 0 ;
* 0 <math>\oplus</math> 1 = 1 ;
* 1 <math>\oplus</math> 0 = 1 ;
* 1 <math>\oplus</math> 1 = 0.
Pour résumer, le résultat vaut 1 si les deux bits sont différents, 0 s'ils sont identiques. L'opération XOR sera utilisée rapidement dans le chapitre suivant, quand nous parlerons rapidement du mot de parité. Et elle sera beaucoup utilisée dans la suite du cours, nous en feront fortement usage. Pour le moment, mémorisez juste cette opération, elle n'a rien de compliqué.
Une dernière opération est l'opération de '''''population count'''''. Il s'agit ni plus ni moins que de compter le nombre de bits qui sont à 1 dans un nombre. Par exemple, pour le nombre 0110 0010 1101 1110, elle donne pour résultat 9. Elle est utilisée dans certaines applications, comme le calcul de certains codes correcteurs d'erreur, comme on le verra dans le chapitre suivant. Elle est supportée sur de nombreux ordinateurs, encore que cela dépende du processeur considéré. Il s'agit cependant d'une opération assez courante, supportée par les processeurs ARM, les processeurs x86 modernes (ceux qui gèrent le SSE), et quelques autres.
===L'hexadécimal===
L’hexadécimal est basé sur le même principe que le binaire, sauf qu'il utilise les 16 chiffres suivants :
{| class="wikitable"
|- style="color:#8080ffff; text-align:center;"
! Chiffre hexadécimal
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || A || B || C || D || E || F
|- style="text-align:center;"
! Nombre décimal correspondant
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 10 || 11 || 12 || 13 || 14 || 15
|- style="text-align:center;"
! Notation binaire
| 0000 || 0001 || 0010 || 0011 || 0100 || 0101 || 0110 || 0111 || 1000 || 1001 || 1010 || 1011 || 1100 || 1101 || 1110 || 1111
|}
Dans les textes, afin de différencier les nombres décimaux des nombres hexadécimaux, les nombres hexadécimaux sont suivis par un petit h, indiqué en indice. Si cette notation n'existait pas, des nombres comme 2546 seraient ambigus : on ne saurait pas dire sans autre indication s'ils sont écrits en décimal ou en hexadécimal. Avec la notation, on sait de suite que 2546 est en décimal et que 2546h est en hexadécimal.
Dans les codes sources des programmes, la notation diffère selon le langage de programmation.
Certains supportent le suffixe <code>h</code> pour les nombres hexadécimaux, d'autres utilisent un préfixe <code>0x</code> ou <code>0h</code>.
Pour convertir un nombre hexadécimal en décimal, il suffit de multiplier chaque chiffre par la puissance de 16 qui lui est attribuée. Là encore, la position d'un chiffre indique par quelle puissance celui-ci doit être multiplié : le chiffre le plus à droite est celui des unités, le second chiffre le plus à droite doit être multiplié par 16, le troisième chiffre en partant de la droite doit être multiplié par 256 (16 * 16) et ainsi de suite. La technique pour convertir un nombre décimal vers de l’hexadécimal est similaire à celle utilisée pour traduire un nombre du décimal vers le binaire. On retrouve une suite de divisions successives, mais cette fois-ci les divisions ne sont pas des divisions par 2 : ce sont des divisions par 16.
La conversion inverse, de l'hexadécimal vers le binaire est très simple, nettement plus simple que les autres conversions. Pour passer de l'hexadécimal au binaire, il suffit de traduire chaque chiffre en sa valeur binaire, celle indiquée dans le tableau au tout début du paragraphe nommé « Hexadécimal ». Une fois cela fait, il suffit de faire le remplacement. La traduction inverse est tout aussi simple : il suffit de grouper les bits du nombre par 4, en commençant par la droite (si un groupe est incomplet, on le remplit avec des zéros). Il suffit alors de remplacer le groupe de 4 bits par le chiffre hexadécimal qui correspond.
==Interlude propédeutique : la capacité d'un entier et les débordements d'entiers==
Dans la section précédente, nous avons vu comment coder des entiers positifs en binaire ou dans des représentations proches. La logique voudrait que l'on aborde ensuite le codage des entiers négatifs. Mais nous allons déroger à cette logique simple, pour des raisons pédagogiques. Nous allons faire un interlude qui introduira des notions utiles pour la suite du chapitre. De plus, ces concepts seront abordés de nombreuses fois dans ce wikilivre et l'introduire ici est de loin la solution idéale.
===Les ordinateurs manipulent des nombres codés sur un nombre fixe de bits===
Vous avez certainement déjà entendu parler de processeurs 32 ou 64 bits. Et si vous avez joué aux jeux vidéos durant votre jeunesse et êtes assez agé, vous avez entendu parler de consoles de jeu 8 bits, 16 bits, 32 bits, voire 64 bits (pour la Jaguar, et c'était un peu trompeur). Derrière cette appellation qu'on retrouvait autrefois comme argument commercial dans la presse se cache un concept simple. Tout ordinateur manipule des nombres entiers dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Une console 16 bits manipulait des entiers codés en binaire sur 16 bits, pas un de plus, pas un de moins. Pareil pour les anciens ordinateurs 32 bits, qui manipulaient des nombres entiers codés sur 32 bits.
Aujourd'hui, les ordinateurs modernes utilisent presque un nombre de bits qui est une puissance de 2 : 8, 16, 32, 64, 128, 256, voire 512 bits. Mais cette règle souffre évidemment d'exceptions. Aux tout débuts de l'informatique, certaines machines utilisaient 3, 7, 13, 17, 23, 36 et 48 bits ; mais elles sont aujourd'hui tombées en désuétude. De nos jours, il ne reste que les processeurs dédiés au traitement de signal audio, que l'on trouve dans les chaînes HIFI, les décodeurs TNT, les lecteurs DVD, etc. Ceux-ci utilisent des nombres entiers de 24 bits, car l'information audio est souvent codée par des nombres de 24 bits.
Anecdote amusante, il a existé des ordinateurs de 1 bit, qui sont capables de manipuler des nombres codés sur 1 bit, pas plus. Un exemple est le Motorola MC14500B, commercialisé de 1976.
====Le lien entre nombre de bits et valeurs codables====
Évidemment, on ne peut pas coder tous les entiers possibles et imaginables avec seulement 8 bits, ou 16 bits. Et il parait intuitif que l'on ait plus de valeurs codables sur 16 bits qu'avec 8 bits, par exemple. Plus le nombre de bits est important, plus on pourra coder de valeurs entières différentes. Mais combien de plus ? Par exemple, si je passe de 8 bits à 16 bits, est-ce que le nombre de valeurs que l'on peut coder double, quadruple, pentuple ? De même, combien de valeurs différentes on peut coder avec <math>n</math> bits. Par exemple, combien de nombres différents peut-on coder avec 4, 8 ou 16 bits ? La section précédente vous l'expliquer.
Avec <math>n</math> bits, on peut coder <math>2^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>2^n - 1</math>. N'oubliez pas cette formule : elle sera assez utile dans la suite de ce tutoriel. Pour exemple, on peut coder 16 valeurs avec 4 bits, qui vont de 0 à 15. De même, on peut coder 256 valeurs avec un octet, qui vont de 0 à 255. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de bits
! Nombre de valeurs codables
|-
! 4
| 16
|-
! 8
| 256
|-
! 16
| 65 536
|-
! 32
| 4 294 967 296
|-
! 64
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien de bits il faut pour coder une valeur quelconque, que nous noterons N. Pour cela, il faut utiliser la formule précédente, mais à l'envers. On cherche alors <math>n</math> tel que <math>N = 2^n</math>. L'opération qui donne <math>n</math> est appelée le logarithme, et plus précisément un logarithme en base 2, noté <math>\log_2{(X)}</math>. Le problème est que le résultat du logarithme ne tombe juste que si le nombre X est une puissance de 2. Si ce n'est pas le cas, le résultat est un nombre à virgule, ce qui n'a pas de sens pratique. Par exemple, la formule nous dit que pour coder le nombre 13, on a besoin de 3,70043971814 bits, ce qui est impossible. Pour que le résultat ait un sens, il faut arrondir à l'entier supérieur. Pour l'exemple précédent, les 3,70043971814 bits s'arrondissent en 4 bits.
====Le lien entre nombre de chiffres hexadécimaux et valeurs codables====
Maintenant, voyons combien de valeurs peut-on coder avec <math>n</math> chiffres hexadécimaux. La réponse n'est pas très différente de celle obtenue en binaire, si ce n'est qu'il faut remplacer le 2 par un 16 dans la formule précédente. Avec <math>n</math> chiffres hexadécimaux, on peut coder <math>16^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>16^n - 1</math>. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de chiffres héxadécimaux
! Nombre de valeurs codables
|-
! 1 (4 bits)
| 16
|-
! 2 (8 bits)
| 256
|-
! 4 (16 bits)
| 65 536
|-
! 8 (32 bits)
| 4 294 967 296
|-
! 16 (64 bits)
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien faut-il de chiffres hexadécimaux pour coder une valeur quelconque en hexadécimal. La formule est là encore la même qu'en binaire, sauf qu'on remplace le 2 par un 16. Pour trouver le nombre de chiffres hexadécimaux pour encoder un nombre X, il faut calculer <math>\log_{16}{(X)}</math>. Notons que le logarithme utilisé est un logarithme en base 16, et non un logarithme en base 2, comme pour le binaire. Là encore, le résultat ne tombe juste que si le nombre X est une puissance de 16 et il faut arrondir à l'entier supérieur si ce n'est pas le cas.
Une propriété mathématique des logarithmes nous dit que l'on peut passer d'un logarithme en base X et à un logarithme en base Y avec une simple division, en utilisant la formule suivante :
: <math>\log_X{(N)} = {\log_{Y}{(N)} \over \log_Y(X)}</math>
Dans le cas qui nous intéresse, on a Y = 2 et X = 16, ce qui donne :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over \log_2(16)}</math>
Or, <math>\log_2(16)</math> est tout simplement égal à 4, car il faut 4 bits pour coder la valeur 16. On a donc :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over 4}</math>
En clair, il faut quatre fois moins de chiffres hexadécimaux que de bits, ce qui est assez intuitif vu qu'il faut 4 bits pour coder un chiffre hexadécimal.
===Les débordements d'entier===
On vient de voir que tout ordinateur manipule des nombres dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Et cela limite les valeurs qu'il peut encoder, qui sont comprises dans un intervalle bien précis. Mais que ce passe-t-il si jamais le résultat d'un calcul ne rentre pas dans cet intervalle ? Par exemple, pour du binaire normal, que faire si le résultat d'un calcul atteint ou dépasse <math>2^n</math> ? Dans ce cas, le résultat ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
On peut imaginer d'autres codages pour lesquels les entiers ne commencent pas à zéro ou ne terminent pas à <math>2^n - 1</math>. On peut prendre le cas où l'ordinateur gère les nombres négatifs, par exemple. Dans le cas général, l'ordinateur peut coder les valeurs comprises dans un intervalle, qui va de la valeur la plus basse <math>N_\text{min}</math> à la valeur la plus grande <math>N_\text{max}</math>. Et encore une fois, si un résultat de calcul sort de cet intervalle, on fait face à un débordement d'entier.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math>, avec <math>N_\text{max}</math> la plus grande valeur codable par l'ordinateur.
On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur basse de débordement vaut <math>N_\text{min} - 1</math>, avec <math>N_\text{min}</math> la plus petite valeur codable par l'ordinateur.
===La gestion des débordements d'entiers===
Face à un débordement d'entier, l'ordinateur peut utiliser deux méthodes : l'arithmétique saturée ou l'arithmétique modulaire.
L''''arithmétique saturée''' consiste à arrondir le résultat pour prendre la plus grande ou la plus petite valeur. Si le résultat d'un calcul dépasse la valeur haute de débordement, le résultat est remplacé par le plus grand entier supporté par l'ordinateur. La même chose est possible quand le résultat est inférieur à la plus petite valeur possible, par exemple lors d'une soustraction : l'ordinateur arrondit au plus petit entier possible.
Pour donner un exemple, voici ce que cela donne avec des entiers codés sur 4 bits, qui codent des nombres de 0 à 15. Si je fais le calcul 8 + 9, le résultat normal vaut 17, ce qui ne rentre pas dans l'intervalle. Le résultat est alors arrondi à 15. Inversement, si je fais le calcul 8 - 9, le résultat sera de -1, ce qui ne rentre pas dans l'intervalle : le résultat est alors arrondi à 0.
[[File:Odometer rollover.jpg|vignette|Exemple de débordement d'entier sur un compteur mécanique quelconque. L'image montre clairement que le compteur revient à zéro une fois la valeur maximale dépassée.]]
L''''arithmétique modulaire''' est plus compliquée et c'est elle qui va nous intéresser dans ce qui suit. Pour simplifier, imaginons que l'on décompte à partir de zéro. Quand on arrive à la valeur haute de débordement, on recommence à compter à partir de zéro. L'arithmétique modulaire n'est pas si contre-intuitive et vous l'utilisez sans doute au quotidien. Après tout, c'est comme cela que l'on compte les heures, les minutes et les secondes. Quand on compte les minutes, on revient à 0 au bout de 60 minutes. Pareil pour les heures : on revient à zéro quand on arrive à 24 heures. Divers compteurs mécaniques fonctionnent sur le même principe et reviennent à zéro quand ils dépassent la plus grande valeur possible, l'image ci-contre en montrant un exemple.
Mathématiquement, l'arithmétique modulaire implique des divisions euclidiennes, celles qui donnent un quotient et un reste. Lors d'un débordement, le résultat s'obtient comme suit : on divise le nombre qui déborde par la valeur haute de débordement, et que l'on conserve le reste de la division. Au passage, l'opération qui consiste à faire une division et à garder le reste au lieu du quotient est appelée le ''modulo''. Prenons l'exemple où l'ordinateur peut coder tous les nombres entre 0 et 1023, soit une valeur haute de débordement de 1024. Pour coder le nombre 4563, on fait le calcul 4563 / 1024. On obtient : <math>4563 = 4 \times 1024 + 467</math>. Le reste de la division est de 467 et c'est lui qui sera utilisé pour coder la valeur de départ, 4563. Le nombre 4563 est donc codé par la valeur 467 dans un tel ordinateur en arithmétique modulaire. Au passage, la valeur haute de débordement est toujours codée par un zéro dans ce genre d'arithmétique.
Les ordinateurs utilisent le plus souvent une valeur haute de débordement de <math>2^n</math>, avec n le nombre de bits utilisé pour coder les nombres entiers positifs. En faisant cela, l'opération modulo devient très simple et revient à éliminer les bits de poids forts au-delà du énième bit. Par exemple, reprenons l'exemple d'un ordinateur qui code ses nombres sur 4 bits. Imaginons qu'il fasse le calcul <math>13 + 3</math>, soit 1101 + 0011 = 1 0000 en binaire. Le résultat entraîne un débordement d'entier et l'ordinateur ne conserve que les 4 bits de poids faible. Cela donne : 1101 + 0011 = 0000. Comme autre exemple l'addition 1111 + 0010 ne donnera pas 17 (1 0001), mais 1 (0001).
L'avantage est que les calculs sont beaucoup plus simples avec cette méthode qu'avec les autres. L'ordinateur a juste à ne pas calculer les bits de poids fort. Pas besoin de faire une division pour calculer un modulo, pas besoin de corriger le résultat pour faire de l'arithmétique saturée.
===Aparté : quelques valeurs particulières en binaire===
Plus haut, on a dit qu'avec n bits, on peut encoder toutes les valeurs allant de <math>0</math> à <math>2^n - 1</math>. Ce simple fait permet de déterminer quelle est la valeur de certains entiers à vue d’œil. Dans ce qui va suivre, nous allons poser quelques bases que nous réutiliserons dans la suite du chapitre. Il s'agit de quelques trivias qui sont cependant assez utiles.
Le premier trivia concerne la valeur maximale <math>2^n - 1</math> : elle est encodée par un nombre dont tous les bits sont à 1.
Le deuxième trivia concerne les nombres de la forme 000...000 1111 1111, à savoir des nombres dont les x bits de poids faible sont à 1 et tous les autres valent 0. Par définition, de tels nombres codent la plus grande valeur possible sur x bits, ce qui fait qu'ils valent <math>2^x - 1</math>.
Le troisième trivia concerne les nombres de la forme 11111...0000. En clair, des nombres où on a une suite consécutive de 1 dans les bits de poids fort et les x bits de poids faibles à 0. De tels nombres valent : <math>2^n - 2^x</math>. La preuve est assez simple : ils s'obtiennent en prenant la valeur maximale <math>2^n - 1</math>, et en soustrayant un nombre de la forme <math>2^x - 1</math>.
[[File:Valeurs particulières en binaire.png|centre|vignette|upright=2|Valeurs particulières en binaire]]
Si on utilise l'arithmétique modulaire, la valeur <math>2^n</math> n'est autre que la valeur de débordement haute pour les nombres stockés sur n bits, et se code comme le zéro (il faudrait 1 bit de plus pour stocker le 1 de poids fort de <math>2^n</math>). Les 1er et 3ème nombres évoqués dans les paragraphes précédents peuvent donc se passer de <math>2^n</math> dans leur expression :
* <math>- 1</math> est codé 1111111111111111...111 (n bits à 1)
* <math>- 2^x</math> est codé 11111...111000..000000 (x bits à 0 précédés de n-x bits à 1)
==Les nombres entiers négatifs==
<noinclude>[[File:Binary Representations.svg|vignette|Représentations signées, exemples sur 4 bits.]]</noinclude>
Passons maintenant aux entiers négatifs en binaire : comment représenter le signe moins ("-") avec des 0 et des 1 ? Eh bien, il existe plusieurs méthodes :
* la représentation en signe-valeur absolue ;
* la représentation en complément à un ;
* la représentation en complément à deux;
* la représentation par excès ;
* la représentation dans une base négative ;
* d'autres représentations encore moins utilisées que les autres.
===La représentation en signe-valeur absolue===
La solution la plus simple pour représenter un entier négatif consiste à coder sa valeur absolue en binaire, et rajouter un bit qui précise si c'est un entier positif ou un entier négatif. Par convention, ce bit de signe vaut 0 si le nombre est positif et 1 s'il est négatif. On parle alors de représentation en signe-valeur absolue, aussi appelée '''représentation en signe-magnitude'''.
Avec cette technique, il y autant de nombres positifs que négatifs. Mieux : pour chaque nombre représentable en représentation signe-valeur absolue, son inverse l'est aussi. Ce qui fait qu'avec cette méthode, le zéro est codé deux fois : on a un -0, et un +0. Cela pose des problèmes lorsqu'on demande à notre ordinateur d'effectuer des calculs ou des comparaisons avec zéro.
[[File:Codage sur 4 bits en signe-valeur absolue.png|centre|vignette|upright=2|Codage sur 4 bits en signe-valeur absolue]]
===La représentation en complément à un===
La représentation en '''complément à un''' peut être vue, en première approximation, comme une variante de la représentation en signe-magnitude. Et je dis bien : "en première approximation", car il y a beaucoup à dire sur la représentation en complément à un, mais nous verrons cela dans la section suivante sur le complément à deux. Conceptuellement, la représentation en signe-magnitude et celle du complément à un sont drastiquement différentes et sont basées sur des concepts mathématiques totalement différents. Mais elles ont des ressemblances de surface, qui font que faire la comparaison entre les deux est assez utile pédagogiquement parlant.
En complément à un, les nombres sont codés en utilisant un bit de signe qui indique si le nombre de positif ou négatif, couplé à une valeur absolue. Par contre, la valeur absolue est codée différemment en binaire. Pour les nombres positifs, la valeur absolue est codée en binaire normal, pas de changement comparé aux autres représentations. Mais pour les valeurs négatives, la valeur absolue est codée en binaire normal, puis tous les bits sont inversés : les 0 deviennent des 1 et réciproquement. En clair, on utilise une opération de complémentation pour les nombres négatifs.
Prenons un exemple avec un nombre codé sur 4 bits, avec un cinquième bit de signe. La valeur 5 est codée comme suit : 0 pour le bit de signe, 5 donne 0101 en binaire, le résultat est 0 0101. Pour la valeur -5, le bit de signe est 1, 5 donne 0101 en binaire, on inverse les bits ce qui donne 1010 : cela donne 11010. Notez qu'on peut passer d'un résultat à l'autre avec une opération de complémentation, à savoir en inversant les bits de l'autre et réciproquement.
Il s'agit là d'une propriété générale avec le complément à 1 : l'opposé d'un nombre, à savoir passer de sa valeur positive à sa valeur négative ou inversement, se calcule avec une opération NOT, une opération de complémentation (d'où son nom). L'avantage est que les ordinateurs gèrent naturellement d'opération de complémentation. Par contre, en signe-magnitude, inverser le bit de signe est une opération spécifique et ne sert qu'à ça. Il faut donc rajouter une opération en plus pour calculer l'opposé d'un nombre.
La représentation en complément à un garde les défauts de la représentation en signe-magnitude. Le zéro est codé deux fois, avec un zéro positif et un zéro négatif. La différence est que les valeurs négatives sont dans l'ordre inverse, il y a une symétrie un peu meilleure. Les deux zéros sont d'ailleurs totalement éloignés, ils correspondent aux valeurs extrêmes encodables : le zéro positif a tous ses bits à 0, le zéro négatif a tous ses bits à 1. Le résultat est que l'implémentation des comparaisons et de certains calculs est plus complexe qu'avec le signe-magnitude, mais de peu.
[[File:Codage sur 4 bits en complément à 1.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 1.]]
===La représentation par excès===
La représentation par excès consiste à ajouter un biais aux nombres à encoder afin de les encoder par un entier positif. Pour encoder tous les nombres compris entre -X et Y en représentation par excès, il suffit de prendre la valeur du nombre à encoder, et de lui ajouter un biais égal à X. Ainsi, la valeur -X sera encodée par zéro, et toutes les autres valeurs le seront par un entier positif, le zéro sera encodé par X, 1, par X+1, etc. Par exemple, prenons des nombres compris entre -127 et 128. On va devoir ajouter un biais égal à 127, ce qui donne :
{|class="wikitable"
|-
!Valeur avant encodage!!Valeur après encodage
|-
||-127||0
|-
||-126||1
|-
||-125||2
|-
||…||…
|-
||0||127
|-
||…||…
|-
||127||254
|-
||128||255
|}
===La représentation en complément à deux===
La représentation en '''complément à deux''' est basée sur les mêmes mathématiques que le complément à un, mais fonctionne très différemment en pratique.
Si on regarde de loin, son principe est assez différent : il n'y a pas de bit de signe ni quoi que ce soit d'autre. À la place, un nombre en complément à deux est encodé comme en binaire normal, à un point près : le bit de poids fort est soustrait, et non additionné aux autres. Il a une valeur négative : on soustrait la puissance de deux associée s'il vaut 1, on ne la tient pas en compte s'il vaut 0.
Par exemple, la valeur du nombre noté 111001 en complément à deux s'obtient comme suit :
{|class="wikitable" style="margin:auto;"
|-
! -32 !! 16 !! 8 !! 4 !! 2 !! 1
|-
| 1 || 1 || 1 || 0 || 0 || 1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(8×1)+(4×0)+(2×1)+(1×1) = −32+16+8+1 = -7.
: ''Avec le complément à deux, comme avec le complément à un, le bit de poids fort vaut 0 pour les nombres positifs et 1 pour les négatifs.''
L'avantage de cette représentation est qu'elle n'a pas de double zéro : le zéro n'est encodé que par une seule valeur. Par contre, la valeur autrefois prise par le zéro négatif est réutilisée pour encoder une valeur négative. La conséquence est que l'on a un nombre négatif en plus d'encodé, il n'y a plus le même nombre de valeurs strictement positives et de valeurs négatives encodées. Le nombre négatif en question est appelé le '''nombre le plus négatif''', ce nom trahit le fait que c'est celui qui a la plus petite valeur (la plus grande valeur absolue). Il est impossible de coder l'entier positif associé, sa valeur absolue.
[[File:Codage sur 4 bits en complément à 2.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 2.]]
====Le calcul du complément à deux : première méthode====
Les représentations en complément à un et en complément à deux sont basées sur le même principe mathématique. Leur idée est de remplacer chaque nombre négatif par un nombre positif équivalent, appelé le ''complément''. Par équivalent, on veut dire que tout calcul donne le même résultat si on remplace un nombre négatif par son complément (idem avec un nombre positif, son complément aura un signe inverse). Et pour faire cela, elles se basent sur les débordements d'entier pour fonctionner, et plus précisément sur l'arithmétique modulaire abordée plus haut.
Si on fait les calculs avec le complément, les résultats du calcul entraînent un débordement d'entier, qui sera résolu par un '''modulo''' : l'ordinateur ne conserve que les bits de poids faible du résultat, les autres bits sont oubliés. Par exemple, prenons l'addition 15 + 2, 1111 + 0010 en binaire : le résultat ne sera pas 17 (10001), vu qu'on n'a pas assez de bits pour encoder le résultat, mais 1 (0001). Et le résultat après modulo sera identique au résultat qu'on aurait obtenu avec le nombre négatif sans modulo. En clair, c'est la gestion des débordements qui permet de corriger le résultat de manière à ce que l'opération avec le complément donne le même résultat qu'avec le nombre négatif voulu. Ainsi, on peut coder un nombre négatif en utilisant son complément positif.
: Cela ressemble beaucoup à la méthode de soustraction basée sur un complément à 9 pour ceux qui connaissent, sauf que c'est une version binaire qui nous intéresse ici.
Prenons un exemple, qui permettra d'introduire la suite. Encore une fois, on utilise un codage sur 4 bits dont la valeur haute de débordement est de 16. Prenons l'addition de 13 + 3 = 16. Avec l'arithmétique modulaire, 16 est équivalent à 0, ce qui donne : 13 + 3 = 0 ! On peut aussi reformuler en disant que 13 = -3, ou encore que 3 = -13. Dit autrement, 3 est le complément de -13 pour ce codage. Et ne croyez pas que ça marche uniquement dans cet exemple : cela se généralise assez rapidement à tout nombre négatif.
Prenons un nombre N dont un veut calculer le complément à deux K. Dans le cas général, on a :
: <math>N + K = 2^n</math>, vu que <math>2^n = 0</math> en utilisant le modulo.
En réorganisant très légèrement les termes pour isoler K, on a :
: <math>K = 2^n - N</math>
La formule précédent permet de calculer la complément à deux assez simplement, en faisant le calcul à la main.
Avant de poursuivre, prenons un exemple très intéressant : le cas où N = -1. Son complément à deux vaut donc :
: <math>K = 2^n - 1</math>
Le terme <math>2^n - 1</math> devrait vous rappeler quelque chose : il s'agit d'un nombre dont tous les bits sont à 1. En clair, le complément à deux de -1 est un nombre de la forme 1111...111. Et cela vaut quel que soit le nombre de bits n. La représentation de -1 est similaire, peu importe que l'on utilise des nombres de 16 bits, 32 bits, 64 bits, etc.
Voyons maintenant le cas des puissance de deux, par exemple 2, 4, 8, 16, etc. Leur complément à deux vaut :
: <math>K = 2^n - 2^x</math>
Nous avions vu précédemment dans le chapitre que ces nombres sont de la forme 11111...0000. En clair, des nombres dont les x bits de poids faible sont à 0, et tous les autres bits sont à 1. Par exemple, le complément à deux de -2 est un nombre dont les bits sont à 1, sauf le bit de poids faible. De même, le complément à deux de -4 a tous ses bits à 1, sauf les deux bits de poids faible. Et le complément à deux de -8 a tous ses bits à 1, sauf les trois bits de poids faible. Pour résumer, tout nombre de la forme <math>- 2^x</math> a tous ses bits à 1, sauf les x bits de poids faible qui sont à 0.
{|class="wikitable"
|+ Exemple avec des nombres de 8 bits
|-
! -1
| 1111 1111
|-
! -2
| 1111 1110
|-
! -4
| 1111 1100
|-
! -8
| 1111 1000
|-
! -16
| 1111 0000
|-
! -32
| 1110 0000
|-
! -64
| 1100 0000
|-
! -128
| 1000 0000
|-
! 0 / - 256
| 0000 0000
|}
====Le calcul du complément à deux : seconde méthode====
Il existe une seconde méthode pour calculer le complément à deux d'un nombre, la voici. Pour les nombres positifs, encodez-les comme en binaire normale. Pour les nombres négatifs, faites pareil, puis inversez tous les bits, avant d'ajouter un. La procédure est identique à celle du complément à un, sauf que l'on incrémente le résultat final. Et ce n'est pas une coïncidence, comme nous allons le voir immédiatement.
Pour comprendre pourquoi la méthode marche, repartons de la formule précédente <math>K = 2^n - N</math>. Elle peut se reformuler comme suit :
: <math>K = (2^n - 1 + 1) - N</math>
: <math>K = (2^n - 1) - N + 1</math>
La valeur <math>(2^n - 1)</math> est par définition un nombre dont tous les bits sont à 1. À cette valeur, on soustrait un nombre <math>|N|</math> dont certains bits sont à 1 et d'autres à 0. En clair, pour chaque colonne, on a deux possibilités : soit on doit faire la soustraction <math>1-1</math>, soit la soustraction <math>1-0</math>. Or, les règles de l'arithmétique binaire disent que <math>1-1=0</math> et <math>1-0=1</math>. En regardant attentivement, on se rend compte que le bit du résultat est l'inverse du bit de départ. De plus, les deux cas ne donnent pas de retenue : le calcul pour chaque bit n'influence pas les bits voisins.
Le terme <math>(2^n - 1) - N</math> est donc le complément à un du nombre N, un nombre égal à sa représentation en binaire dont tous les bits sont inversés. Notons <math>\overline{|N|}</math> le nombre formé en inversant tous les bits de N. On a alors :
: <math>K = \overline{N} + 1</math>
En clair, le complément à deux s'obtient en prenant le complément à un et en ajoutant 1. Dit autrement, il faut prendre le nombre N, en inverser tous les bits et ajouter 1.
Une autre manière équivalente consiste à faire le calcul suivant :
: <math>K = \overline{N - 1}</math>
On prend le nombre dont on veut le complément, on soustrait 1 et on inverse les bits.
: Notons que tout ce qui a été dit plus haut marche aussi pour le complément à un, avec cependant une petite différence : la valeur haute de débordement n'est pas la même, ce qui change les calculs. Pour des nombres codés sur <math>n</math> bits, la valeur haute de débordement est égale à <math>2^n</math> en complément à deux, alors qu'elle est de <math>2^n - 1</math> en complément à un. De ce fait, la gestion des débordements est plus simple en complément à deux.
====L'extension de signe====
Dans les ordinateurs, tous les nombres sont codés sur un nombre fixé et constant de bits. Ainsi, les circuits d'un ordinateur ne peuvent manipuler que des nombres de 4, 8, 12, 16, 32, 48, 64 bits, suivant la machine. Si l'on veut utiliser un entier codé sur 16 bits et que l'ordinateur ne peut manipuler que des nombres de 32 bits, il faut bien trouver un moyen de convertir le nombre de 16 bits en un nombre de 32 bits, sans changer sa valeur et en conservant son signe. Cette conversion d'un entier en un entier plus grand, qui conserve valeur et signe s'appelle l''''extension de signe'''.
L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
L'explication plus simple tient dans la manière de coder le bit de poids fort. Prenons l'exemple de la conversion d'un entier de 5 bits en un entier de 8bits. Les 4 bits de poids faible ont un poids positif (on les additionne), alors que le bit de poids fort a un poids négatif. Le nombre encodé vaut : <math>\text{Bit de poids fort} \times -16 + \text{Valeur encodée sur les 4 bits de poids faible}</math>. La valeur encodée sur 4 bits reste la même après extension de signe, car les poids des bits de poids faible ne changent pas. Par contre, le bit de poids fort change. Sur 8 bits, la valeur -16 est encodée par un nombre de la forme 1111 0000. En remplaçant le bit de poids fort par sa valeur calculée sur plus de bits, on remarque que les bits de poids fort ont été remplacés par des 1.
===La représentation négabinaire===
Enfin, il existe une dernière méthode, assez simple à comprendre, appelée '''représentation négabinaire'''. Dans cette méthode, les nombres sont codés non en base 2, mais en base -2. Oui, vous avez bien lu : la base est un nombre négatif. Dans les faits, la base -2 est similaire à la base 2 : il y a toujours deux chiffres (0 et 1), et la position dans un chiffre indique toujours par quelle puissance de 2 il faut multiplier, sauf qu'il faudra ajouter un signe moins une fois sur 2. Concrètement, les puissances de -2 sont les suivantes : 1, -2, 4, -8, 16, -32, 64, etc. En effet, un nombre négatif multiplié par un nombre négatif donne un nombre positif, ce qui fait qu'une puissance sur deux est négative, alors que les autres sont positives. Ainsi, on peut représenter des nombres négatifs, mais aussi des nombres positifs dans une puissance négative.
Par exemple, la valeur du nombre noté 11011 en base -2 s'obtient comme suit :
{|class="wikitable"
|-
!…
!-32
!16
!-8
!4
!-2
!1
|-
|…
|1
|1
|1
|0
|1
|1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(−8×1)+(4×0)+(−2×1)+(1×1)=−32+16−8−2+1=−25.
==Les nombres à virgule fixe==
On sait donc comment sont stockés nos nombres entiers dans un ordinateur. Néanmoins, les nombres entiers ne sont pas les seuls nombres que l'on utilise au quotidien : il nous arrive d'en utiliser à virgule. Notre ordinateur n'est pas en reste : il est lui aussi capable de manipuler de tels nombres. Dans les grandes lignes, il peut utiliser deux méthodes pour coder des nombres à virgule en binaire : La virgule fixe et la virgule flottante. Parlons d'abord de la virgule fixe, plus simple à comprendre.
===La virgule fixe : des entiers cachés===
La méthode de la virgule fixe consiste à émuler les nombres à virgule à partir de nombres entiers. Un nombre à virgule fixe est codé par un nombre entier proportionnel au nombre à virgule fixe. Pour obtenir la valeur de notre nombre à virgule fixe, il suffit de diviser l'entier servant à le représenter par le facteur de proportionnalité. Par exemple, pour coder 1,23 en virgule fixe, on peut choisir comme « facteur de conversion » 1000, ce qui donne l'entier 1230.
Généralement, les informaticiens utilisent une puissance de deux comme facteur de conversion, pour simplifier les calculs. En faisant cela, on peut écrire les nombres en binaire et les traduire en décimal facilement. Pour l'exemple, cela permet d'écrire des nombres à virgule en binaire comme ceci : 1011101,1011001. Et ces nombres peuvent se traduire en décimal avec la même méthode que des nombres entier, modulo une petite différence. Comme pour les chiffres situés à gauche de la virgule, chaque bit situé à droite de la virgule doit être multiplié par la puissance de deux adéquate. La différence, c'est que les chiffres situés à droite de la virgule sont multipliés par une puissance négative de deux, c'est à dire par <math>1 \over 2</math>, <math>1 \over 4</math>, <math>1 \over 8</math>, <math>1 \over 16</math>, <math>1 \over 32</math>, ...
[[File:Fixed-point binary representation.svg|centre|vignette|upright=2.5|Nombres à virgule fixe.]]
Cette méthode est assez peu utilisée de nos jours, quoiqu'elle puisse avoir quelques rares applications relativement connue. Un bon exemple est celui des banques : les sommes d'argent déposées sur les comptes ou transférées sont codés en virgule fixe. Les sommes manipulées par les ordinateurs ne sont pas exprimées en euros, mais en centimes d'euros. Et c'est une forme de codage en virgule fixe dont le facteur de conversion est égal à 100. La raison de ce choix est que les autres méthodes de codage des nombres à virgule peuvent donner des résultats imprécis : il se peut que les résultats doivent être tronqués ou arrondis, suivant les opérandes. Cela n'arrive jamais en virgule fixe, du moins quand on se limite aux additions et soustractions.
===Les nombres à virgule non-représentables en binaire===
Quel que soit la façon de représenter les nombres à virgules, il existe des nombres qui ne peuvent être représentés de manière exacte, à savoir avec un nombre fini de décimales après la virgule. En soi, ce n'est pas spécifique au binaire, on a la même chose en décimal. Par exemple, la fraction 1/3 en décimal s'écrit 0.3333333..., avec une infinité de 3. La même chose existe en binaire, mais pour des nombres différents.
Déjà, évacuons le cas des nombres irrationnels, à savoir les nombres qui ne peuvent pas s'écrire sous la forme d'une fraction, comme <math>pi</math> ou <math>e</math>. Ils ont une infinité de décimales que ce soit en binaire, en décimal, en hexadécimal, ou autre. Ils ne sont pas représentables avec un nombre fini de décimales quelle que soit la base utilisée. Concentrons-nous sur des nombres qui ne sont pas dans ce cas, et qui ont un nombre fini ou infini de décimales.
Le passage de la base 10 à la base 2 change le nombre de décimales, et peut faire passer d'un nombre fini de décimales à un nombre infini. Par exemple, <math>\frac{1}{5} = 0.2</math> est représenté en binaire avec une séquence infinie de bits : <math>0.001100110011...</math>. Les nombres étant en base binaire et représenté avec un nombre limité de bits, il existe certains nombres décimaux triviaux qui ne sont pas représentables avec un nombre fini de décimales.
Et la réciproque n'est pas vraie : tout nombre binaire avec un nombre fini de décimales en binaire est représentable avec un nombre fini de décimales en base 10. Ceci est lié à la décomposition en facteur premier des bases utilisées :
* <math>2 = 2^1</math>
* <math>10 = 2^1 \times 5^1</math>
Le nombre 10 possède tous les facteurs premiers de 2, mais 2 n'a pas de 5 dans sa décomposition.
==Les nombres flottants==
Les nombres à virgule fixe ont aujourd'hui été remplacés par les '''nombres à virgule flottante''', où le nombre de chiffres après la virgule est variable. Le codage d'un nombre flottant est basée sur son ''écriture scientifique''. Pour rappel, en décimal, l’écriture scientifique d'un nombre consiste à écrire celui-ci comme un produit entre un nombre <math>a</math> et une puissance de 10, avec <math>a</math> étant compris entre 1 et 9. Ce qui donne :
: <math>a \times 10^e</math>, avec <math>a \in [1, 10 [</math>
Le nombre <math>a</math> est appelé le ''significande'' et il est compris entre 1 (inclus) et 10 (exclu). Cette contrainte garantit que l'écriture scientifique d'un nombre est unique, qu'il n'y a qu'une seule façon d'écrire un nombre en notation scientifique. Pour cela, on impose le nombre de chiffre à gauche de la virgule et le plus simple est que celui-ci soit égal à 1. Mais il faut aussi que celui-ci ne soit pas nul. En effet, si on autorise de mettre un 0 à gauche de la virgule, il y a plusieurs manières équivalentes d'écrire un nombre. Ces deux contraintes font que le significande doit être égal ou plus grand que 1, mais strictement inférieur à 10. Par contre, on peut mettre autant de décimales que l'on veut.
En binaire, c'est la même chose, mais avec une puissance de deux. Cela implique de modifier la puissance utilisée : au lieu d'utiliser une puissance de 10, on utilise une puissance de 2.
: <math>a \times 2^e</math>, avec <math>a \in [1, 2 [</math>
Le significande est aussi altéré, au même titre que la puissance, même si les contraintes sont similaires à celles en base 10. En effet, le nombre <math>a</math> ne possède toujours qu'un seul chiffre à gauche de la virgule, comme en base 10. Vu que seuls deux chiffres sont possibles (0 et 1) en binaire, on s'attend à ce que le chiffre situé à gauche de la virgule soit un zéro ou un 1. Mais rappelons que le chiffre à gauche doit être non-nul, pour les mêmes raisons qu'en décimal. En clair, le significande a forcément un bit à 1 à gauche de la virgule. Pour récapituler, l'écriture scientifique binaire d'un nombre consiste à écrire celui-ci sous la forme :
: <math>1,m \times 2^e</math>, avec <math>m \in [0, 1 [</math>
La partie fractionnaire du nombre <math>a</math>, qu'on appelle la '''mantisse'''.
[[File:Mantissa.svg|centre|vignette|Écriture scientifique (anglais).]]
Pour déterminer l'écriture scientifique en binaire d'un nombre quelconque, la procédure dépend de la valeur du nombre en question. Tout dépend s'il est dans l'intervalle <math>[1, 2 [</math>, au-delà de 2 ou en-dessous de 1.
* Pour un nombre entre 1 (inclus) et 2 (exclu), il suffit de le traduire en binaire. Son exposant est 0.
* Pour un nombre au-delà de 2, il faut le diviser par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant est alors le nombre de fois qu'il a fallu diviser par 2.
* Pour un nombre plus petit que 1, il faut le multiplier par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant se calcule en prenant le nombre de fois qu'il a fallu multiplier par 2, et en prenant l'opposé (en mettant un signe - devant le résultat).
===Le codage des nombres flottants et la norme IEEE 754===
La '''virgule flottante''' encode l'écriture scientifique d'un nombre/ Elle utilise pour cela trois données : la mantisse, l'exposant et un bit de signe. Les trois sont concaténés dans un seul "nombre", plus précisément une suite de bits qui est découpée en un bit de signe, un entier qui encode l'exposant, et un entier pour la mantisse.
[[File:General floating point frac.svg|centre|vignette|upright=2|Format Général d'un nombre flottant.]]
[[File:General floating point.svg|centre|vignette|upright=2|Format Général d'un nombre flottant, plus détaillé.]]
Voici à peu-près comment les nombres flottants sont répartis sur une ligne numérique. Vous remarquerez que les flottants sont de plus en plus éloignés au fur et à mesure qu'on s'éloigne de zéro. La raison est l'encodage avec un exposant et une mantisse. L'écart entre deux flottants reste stable pour un même exposant, mais il y a une cassure quand on passe d'un exposant au suivant.
[[File:FloatingPointPrecisionAugmented.png|centre|vignette|upright=3|Floating Point Precision.]]
Le standard actuel pour la virgule flottante est la '''norme IEEE 754'''. Cette norme va (entre autres) définit plusieurs formats de flottants différents, qui utilisent des exposants/mantisses de taille différente. Les deux formats les plus courants sont les flottants '''simple précision''' et '''double précision'''. Les premiers sont codés sur 32 bits, les seconds sur 64 bits. Il y a aussi des formats de '''précision étendus''', codés sur 80 bits ou plus.
[[File:Single double extended2.gif|centre|vignette|upright=2.5|Formats des nombres flottants IEEE754.]]
Mais il existe d'autres formats de flottants, qui suivent des normes différentes. Par exemple, il existe des '''flottants demi-précision''' codés sur 16 bits. Ils sont très utilisés dans le domaine du rendu 3D, dans l'intelligence artificielle, et quelques autres domaines. La norme IEEE754 décrit des flottants demi-précision avec un exposant codé sur 5 bits et une mantisse codée sur 10 bits. Il existe aussi le format '''bfloat16 ''', qui utilise un exposant sur 8 bits et une mantisse sur 7 bits.
[[File:IEEE 754r Half Floating Point Format.svg|centre|vignette|upright=1.5|Flottant demi-precision IEEE754.]]
[[File:Bfloat16 format.svg|centre|vignette|upright=1.5|Flottant bfloat16.]]
Il existe aussi des '''minifloats'''', codés sur 8 ou 4 bits. Ils sont surtout utilisés pour les tâches d'intelligence artificielle, mais assez peu ailleurs.
Les '''nombres flottants logarithmiques''' sont des nombres flottants composés d'un bit de signe et d'un exposant, sans mantisse. La mantisse est totalement implicite : tous les flottants logarithmiques ont la même mantisse, qui vaut 1. Il ne reste que l'exposant, qui est tout simplement le logarithme en base 2 du nombre encodé, d'où le nom de codage flottant logarithmique donné à cette méthode. Attention toutefois : l'exposant est ici un nombre fractionnaire, codé en virgule fixe. Le choix d'un exposant fractionnaire permet de représenter pas mal de nombres de taille diverses.
{|class="wikitable"
|-
!
! Bit de signe
! Exposant
|-
! Représentation binaire
| 0
| 01110010101111
|-
! Représentation décimale
| +
| 1040,13245464
|}
L'utilité de cette représentation est de simplifier certains calculs, comme les multiplications, divisions, puissances, etc. En effet, les mathématiques nous disent que le logarithme d'un produit est égal à la somme des logarithmes : <math>\ln{(a \times b)} = \ln{a} + \ln{b}</math>. Or, il se trouve que les ordinateurs sont plus rapides pour faire des additions/soustractions que pour faire des multiplications/divisions. Donc, la représentation logarithmique permet de remplacer les multiplications/divisions par des additions/soustractions, plus simples et plus rapides pour l'ordinateur. Évidemment, les applications des flottants logarithmiques sont rares, limitées à quelques situations bien précises (traitement d'image, calcul scientifique spécifiques).
===L'encodage de la mantisse et de l'exposant===
L'exposant peut être aussi bien positif que négatif (pour permettre de coder des nombres très petits). Pour cela, il est encodé en représentation par excès. Pour un exposant de n bits, le biais est égal à <math>2^{n-1} - 1</math>.
L'encodage de la mantisse a lui aussi ses subtilités. En effet, la mantisse est un nombre à virgule, avec une partie entière et une partie fractionnaire. Le tout est encodé en virgule fixe, reste à choisir son encodage exact. Pour simplifier l'encodage, l'exposant est choisir de manière à ce qu'il y ait un seul chiffre avant la virgule, ce qui permet de coder la partie entière sur 1 bit. En clair, il faut encoder le bit à gauche de la virgule, puis ceux à droite. Les anciens PC utilisaient cette solution, du temps de la FPU x87. Ils géraient nativement des nombres flottants de 80 bits, appelés '''flottants étendus x87'''. Ils utilisaient une mantisse codée sur 64 bits, dont un bit pour la partie entière, et 63 pour la partie fractionnaire.
[[File:X86 Extended Floating Point Format.svg|centre|vignette|upright=2|Flottants étendus des anciens CPU X86.]]
Une simplification plus importante choisir l'exposant de manière à ce que la partie entière vaut 1, systématiquement. En faisant cela, on n'a plus à encoder la partie entière, pas besoin de lui réserver un bit. La mantisse encode donc uniquement la partie fractionnaire, la partie entière n'est pas encodée. Une telle méthode est donc plus efficace. A quelques détails près : de nombreuses valeurs ne peuvent pas être encodées facilement. Par exemple, le zéro ne peut pas être encodé avec cette technique. Et c'est là un des gros défaut de cette technique : ne pas pouvoir encoder le zéro est un énorme problème !
Pour éviter ce défaut, les nombres flottants utilisent une technique : la partie entière n'est pas codée directement. La partie entière vaut 0 pour certains flottants, elle vaut 1 pour d'autres. Vu que la partie entière fait un bit, mais n'est pas encodée, elle est aussi appelé le '''bit implicite'''. Déterminer le bit implicite demande de regarder l'exposant . Typiquement, si l'exposant vaut zéro, alors la partie entière vaut 0. Sinon, elle vaut 1.
Une telle solution permet d'encoder un zéro, mais aussi des nombres flottants très petits. Ces derniers ne sont pas codés en respectant les règles pour le significande et ont un 0 à gauche de la virgule. Ils sont appelés les '''dénormaux''', aussi nommés flottants dénormalisés. Les dénormaux sont des nombres flottants où l'exposant est le plus petit possible. Le '''zéro''' est un dénormal particulier dont la mantisse est nulle. Au fait, remarquez que le zéro est codé deux fois à cause du bit de signe : on se retrouve avec un -0 et un +0.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur minimale (0 en binaire) || Mantisse différente de zéro (dénormal) ou égale à zéro (zéro)
|}
===Les spécificités de la norme IEEE754===
Les nombres flottants IEE754 ont quelques spécificités qu'il vaut mieux voir à part. Premièrement, ils n'encodent pas la partie entière d'un flottant, la mantisse encode uniquement la partie fractionnaire. La technique précédente, qui utilise l'exposant pour déterminer le bit implicite, est utilisée. Les flottants IEE754 ont une partie entière de la mantisse qui vaut 1, sauf pour quelques rares dénormaux qui font exceptions. Les dénormaux sont des nombres flottants où l'exposant est le plus petit possible.
Le '''zéro''' est un dénormal particulier dont la mantisse est nulle. Au fait, remarquez que le zéro est codé deux fois à cause du bit de signe : on se retrouve avec un -0 et un +0.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur minimale (0 en binaire) || Mantisse différente de zéro (dénormal) ou égale à zéro (zéro)
|}
Fait étrange, la norme IEEE754 permet de représenter '''l'infini''', aussi bien en positif qu'en négatif. Celui-ci est codé en mettant l'exposant à sa valeur maximale et la mantisse à zéro. Et le pire, c'est qu'on peut effectuer des calculs sur ces flottants infinis. Mais cela a peu d'utilité.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse égale à zéro
|}
Mais malheureusement, l'invention des flottants infinis n'a pas réglé tous les problèmes. Par exemple, quel est le résultat de <math>\infty - \infty</math> ? Ou encore <math>0 - \infty</math> ? Autant prévenir tout de suite : mathématiquement, on ne peut pas savoir quel est le résultat de ces opérations. Pour pouvoir résoudre ces calculs, il a fallu inventer un nombre flottant qui signifie « je ne sais pas quel est le résultat de ton calcul pourri ». Ce nombre, c'est '''NaN'''.
NaN est l'abréviation de ''Not A Number'', ce qui signifie : n'est pas un nombre. Un NaN a un exposant dont la valeur est maximale, mais une mantisse différente de zéro. Pour être plus précis, il existe différents types de NaN, qui diffèrent par la valeur de leur mantisse, ainsi que par les effets qu'ils peuvent avoir. Malgré son nom explicite, on peut faire des opérations avec NaN, mais cela ne sert pas vraiment à grand chose : une opération arithmétique appliquée avec un NaN aura un résultat toujours égal à NaN.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse différente de zéro
|}
Au-delà du support de quelques flottants spéciaux, la norme impose une gestion des arrondis ou erreurs, qui arrivent lors de calculs particuliers. En voici la liste :
{|class="wikitable"
|-
! Nom de l’exception
! Description
|-
! Invalid operation
| Opération qui produit un NAN. Elle est levée dans le cas de calculs ayant un résultat qui est un nombre complexe, ou quand le calcul est une forme indéterminée. Pour ceux qui ne savent pas ce que sont les formes indéterminées, voici en exclusivité la liste des calculs qui retournent NaN : <math>\infty - \infty</math>, <math>0 \times \infty</math>, <math>0^0</math>, <math>1^{\infty}</math>, <math>\infty^0</math>.
|-
! Overflow
| Résultat trop grand pour être stocké dans un flottant. Le plus souvent, on traite l'erreur en arrondissant le résultat en vue de la taille de la mantisse;
|-
! Underflow
| Pareil que le précédent, mais avec un résultat trop petit. Le plus souvent, on traite l'erreur en arrondissant le résultat vers 0.
|-
! Division par zéro
| Le nom parle de lui-même. La réponse la plus courante est de répondre + ou - l'infini.
|-
! '''Inexact'''
| Le résultat ne peut être représenté par un flottant et on doit l'arrondir.
|}
La dernière exception, ''Inexact'', survient quand le résultat d'une opération flottante donne un résultat qui ne peut être codé par un flottant. On est alors obligé d'arrondir ou de tronquer le résultat de façon à le faire rentrer dans un flottant. Pour éviter que des ordinateurs différents utilisent des méthodes d'arrondis différentes, la norme IEEEE754 impose le support de quatre modes d'arrondis :
* Arrondir vers + l'infini ;
* vers - l'infini ;
* vers zéro ;
* vers le nombre flottant le plus proche.
==Les encodages binaires alternatifs==
Outre le binaire que nous venons de voir, il existe d'autres manières de coder des nombres avec des bits. Et nous allons les aborder dans cette section. Parmi celle-ci, nous parlerons du code Gray, de la représentation ''one hot'', du unaire et du ternaire. Nous en parlons car elles seront utiles dans la suite du cours, bien que de manière assez limitée. Autant nous passerons notre temps à parler du binaire normal, autant les représentations que nous allons voir sont aujourd'hui utilisées dans des cas assez spécifiques. Et elles sont plus courantes que vous ne le pensez.
===Le code Gray===
Le '''code Gray''' est un encodage binaire qui a une particularité très intéressante : deux nombres consécutifs n'ont qu'un seul bit de différence. Pour exemple, voici ce que donne le codage des 8 premiers entiers sur 3 bits :
{|class="wikitable"
! Décimal
! Binaire naturel
! Codage Gray
|-
|0||000||000
|-
|1||001||001
|-
|2||010||011
|-
|3||011||010
|-
|4||100||110
|-
|5||101||111
|-
|6||110||101
|-
|7||111||100
|}
Les utilisations du code Gray sont assez nombreuses, bien qu'on n'en croise pas tous les jours. Un exemple : le code Gray est très utile dans certains circuits appelés les compteurs, qui mémorisent un nombre et l'incrémentent (+1) ou le décrémentent (-1) suivant les besoins de l'utilisateur. Il est aussi utilisé dans des scénarios difficiles à expliquer ici (des codes correcteurs d'erreur ou des histoires de passages de domaines d'horloge). Mais le point important est que ce code sera absolument nécessaire dans quelques chapitres, quand nous parlerons des tables de Karnaugh, un concept important pour la conception de circuits électroniques. Ne passez pas à côté de cette section.
Pour construire ce code Gray, on peut procéder en suivant plusieurs méthodes, les deux plus connues étant la méthode du miroir et la méthode de l'inversion.
[[File:Binary-reflected Gray code construction.svg|vignette|Construction d'un code gray par la méthode du miroir.]]
La méthode du miroir est relativement simple. Pour connaître le code Gray des nombres codés sur n bits, il faut :
* partir du code Gray sur n-1 bits ;
* symétriser verticalement les nombres déjà obtenus (comme une réflexion dans un miroir) ;
* rajouter un 0 au début des anciens nombres, et un 1 au début des nouveaux nombres.
Il suffit de connaître le code Gray sur 1 bit pour appliquer la méthode : 0 est codé par le bit 0 et 1 par le bit 1.
Une autre méthode pour construire la suite des nombres en code Gray sur n bits est la méthode de l'inversion. Celle-ci permet de connaître le codage du nombre n à partir du codage du nombre n-1, comme la méthode du dessus. On part du nombre 0, systématiquement codé avec uniquement des zéros. Par la suite, on décide quel est le bit à inverser pour obtenir le nombre suivant, avec la règle suivante :
* si le nombre de 1 est pair, il faut inverser le dernier chiffre.
* si le nombre de 1 est impair, il faut localiser le 1 le plus à droite et inverser le chiffre situé à sa gauche.
Pour vous entraîner, essayez par vous-même avec 2, 3, voire 5.
===Les représentations ''one-hot'' et unaire===
La représentation ''one-hot'' et la représentation unaire sont deux représentations assez liées, mais légèrement différentes.
La '''représentation unaire''' est la représentation en base 1. Avec elle, le zéro est codé en mettant tous les bits du nombre à zéro, la valeur 1 est encodée avec la valeur 000...0001, le deux avec 000...0011, le trois avec 000...0111, etc. Pour résumer, le nombre N est encodé en mettant les N bits de poids faible à 1, à l'exception du zéro qui est encodé...par un zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! Unaire
|-
| 0 || 000 || 0000 0000
|-
| 1 || 001 || 0000 0001
|-
| 2 || 010 || 0000 0011
|-
| 3 || 011 || 0000 0111
|-
| 4 || 100 || 0000 1111
|-
| 5 || 101 || 0001 1111
|-
| 6 || 110 || 0011 1111
|-
| 7 || 111 || 0111 1111
|-
| 8 || 1000 || 1111 1111
|}
Pour convertir un nombre codé en unaire vers le binaire, il suffit de compter le nombre de 1. C'est ni plus ni moins ce que fait l'opération dite de ''population count'' dont nous avons parlé plus haut. La représentation unaire permet d'encoder N+1 valeurs sur N bits, car le zéro est encodé à part. Une valeur pour le zéro, plus une par bit.
Avec la '''représentation ''one-hot''''', un nombre est codé sur N bits, dont un seul bit est à 1. La position du 1 dans le nombre indique sa valeur. Précisément, la valeur encodée est égale au poids du bit à 1. En faisant cela, le zéro est codé en mettant le bit de poids faible à 1, la valeur 1 est codée avec les bits 00...00010, la valeur 2 est codée par les bits 00...000100, etc.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Elle permet d'encoder N valeurs différentes avec N bits, zéro inclus. On perd une valeur par rapport à l'unaire, car le zéro n'et pas encodé à part du reste. La traduction du binaire vers le ''one-hot'' est réalisé par un circuit nommé décodeur, que nous verrons d'ici quelques chapitres. De même, la traduction d'un nombre encodé en ''one-hot'' vers le binaire est réalisée par un circuit électronique appelé l'encodeur, qui est en quelque sorte l'inverse de l'encodeur. Les deux circuits sont très utilisés en électronique, ce sont des circuits de base, que nous n'aurons de cesse d'utiliser pour fabriquer d'autres circuits.
L'utilité de ces deux représentations n'est pas évidente. Mais sachez qu'elle le deviendra quand nous parlerons des circuits appelés les "compteurs", tout comme ce sera le cas pour le code Gray. Elles sont très utilisées dans des circuits appelés des machines à état, qui doivent incorporer des circuits compteurs efficients. Et ces représentations permettent d'avoir des circuits pour compter qui sont très simples, efficaces, rapides et économes en circuits électroniques. La représentation unaire sera aussi utile à la toute fin du cours, dans les chapitres liés à l'exécution dans le désordre. Il en sera fait référence quand nous parlerons de fenêtres d'instruction, d'émission dans l'ordre, de ''scoreboarding'', etc.
Il faut aussi noter qu'il a existé des ordinateur qui manipulaient des nombres codés en unaire. Enfin presque, la représentation utilisée était proche de l'unaire, mais ressemblait fortement. De tels ordinateurs étaient appelés des ''ordinateurs stochastiques'' et nous les aborderons à la toute fin de ce wikilivre, dans un chapitre annexe portant sur les ordinateurs ternaires (qui comptent en base 3) et unaires.
==Les encodages hybrides entre décimal et binaire==
Pour terminer, nous allons voir les représentations qui mixent binaire et décimal. Il s'agit en réalité d'encodage qui permettent de manipuler des nombres codés en décimal, mais les chiffres sont codés en utilisant des bits. L'encodage ''Binary Coded Decimal'' est le plus connu de cette catégorie, mais il y en a quelques autres, qui sont moins connus. De telles représentations étaient très utilisées au début de l'informatique, mais sont aujourd'hui tombées en désuétude. Pour désigner ces encodages, nous parlerons d''''encodages bit-décimaux''' : décimaux pour préciser qu'ils encodent des nombres codés en décimal, bit pour préciser que les chiffres sont codés avec des bits.
De tels encodages étaient utilisés sur les tous premiers ordinateurs pour faciliter le travail des programmeurs, mais aussi sur les premières calculettes. Ils sont utiles dans les applications où on doit manipuler chaque chiffre décimal séparément des autres. Un exemple classique est celui d'une horloge digitale. Ils ont aussi l'avantage de bien se marier avec les nombres à virgule. Les représentations des nombres à virgule fixe ou flottante ont le défaut que des arrondis peuvent survenir. Par exemple, la valeur 0,2 est codée comme suit en binaire normal : 0.00110011001100... et ainsi de suite jusqu’à l'infini. Avec les encodages bit-décimaux, on n'a pas ce problème, 0,2 étant codé 0000 , 0010.
Il a existé des ordinateurs qui travaillaient uniquement avec de tels encodages, appelés des '''ordinateurs décimaux'''. Ils étaient assez courants entre les années 60 et 70, même s'ils ne représentent pas la majorité des architectures de l'époque. Avec eux, la mémoire n'était pas organisée en octets, mais elle stockait des chiffres décimaux codés sur 5-6 bits. Le processeur faisait des calculs sur des chiffres bit-décimaux directement.
Leur grand avantage est leur très bonne performance dans les taches de bureautique, de comptabilité, et autres. Les processeurs de l'époque recevaient des entiers codés en bit-décimal de la part des entrées-sorties, et devaient les traiter. Les processeurs binaires devaient faire des conversions décimal-binaire pour communiquer avec les entrées-sorties, mais pas les processeurs décimaux. Le gain en performance pouvait être substantiel dans certaines applications.
Les ordinateurs décimaux se classent en deux sous-types bien précis. Les premiers gèrent des entiers qui ont un nombre de chiffres fixes. Par exemple, l'IBM 7070 gérait des entiers de 10 chiffres, plus un signe +/- pour les entiers signés. Le processeur faisait les calculs directement en bit-décimal, il gérait des entiers faisant environ 10-15 chiffres décimaux et savait faire des calculs avec de tels nombres. Le second sous-type effectue les calculs chiffre par chiffre et géraient des nombres de taille variable, sans limite de chiffres ! Nous reparlerons de ces derniers dans un chapitre ultérieur, quand nous parlerons de la différence entre byte et mot. Pour le moment, ne gardez à l'esprit que les processeurs gérant un nombre fixe de chiffres décimaux, plus simples à comprendre.
===Le ''Binary Coded Decimal''===
Le '''''Binary Coded Decimal''''', abrévié BCD, est une représentation qui mixe binaire et décimal. Avec cette représentation, les nombres sont écrits en décimal, comme nous en avons l'habitude dans la vie courante, sauf que chaque chiffre décimal est directement traduit en binaire sur 4 bits. Prenons l'exemple du nombre 624 : le 6, le 2 et le 4 sont codés en binaire séparément, ce qui donne 0110 0010 0100.
{| class="wikitable"
|+ Codage BCD
|-
! Nombre encodé (décimal)
! scope="col" colspan="4" | BCD
|-
| 0 || 0 || 0 || 0 || 0
|-
| 1 || 0 || 0 || 0 || 1
|-
| 2 || 0 || 0 || 1 || 0
|-
| 3 || 0 || 0 || 1 || 1
|-
| 4 || 0 || 1 || 0 || 0
|-
| 5 || 0 || 1 || 0 || 1
|-
| 6 || 0 || 1 || 1 || 0
|-
| 7 || 0 || 1 || 1 || 1
|-
| 8 || 1 || 0 || 0 || 0
|-
| 9 || 1 || 0 || 0 || 1
|}
On peut remarquer que 4 bits permettent de coder 16 valeurs, là où il n'y a que 10 chiffres. Dans le BCD proprement dit, les combinaisons de bits qui correspondent à 10, 11, 12, 13, 14 ou 15 ne sont tout simplement pas prises en compte. Sur quelques ordinateurs, ces combinaisons codent des chiffres décimaux en double : certains chiffres pouvaient être codés de deux manières différentes. Il est aussi possible d'utiliser ces valeurs pour coder quelque chose d'autre que des chiffres. Par exemple, il est possible de les utiliser pour coder un signe + ou -, afin de gérer les entiers relatifs. Une autre possibilité, complémentaire de la précédente, utilise ces valeurs en trop pour coder une virgule, afin de gérer les nombres non-entiers. Les possibilités sont nombreuses.
Le support du BCD implique souvent que le processeur supporte des '''opérations BCD''', à savoir des opérations capables de travailler sur des opérandes en BCD et de donner un résultat en BCD. Il faut bien faire la différence entre les opérations en binaire et les opérations en BCD. Par exemple, on n'effectue pas une addition de la même manière en binaire et en décimal/BCD, même si les grandes lignes sont presque identiques. Les différences font que les processeurs doivent avoir des opérations différentes pour les deux encodages, de la même manière que les processeurs gèrent les opérations sur les flottants et les entiers séparément.
Le support du codage BCD est abandonné de nos jours, c'est surtout quelque chose qu'on trouve sur les anciens processeurs. Les architectures 8 et 16 bits supportaient à la fois des opérations binaires et des opérations BCD. Mais il a aussi existé une méthode intermédiaire, qui utilisait des additions binaires normales sur des opérandes BCD. L'idée est que le résultat de l'addition est incorrect, car les valeurs 10 à 15 peuvent apparaître comme chiffre dans le résultat. Mais on peut le corriger pour obtenir le résultat exact en BCD. Pour résumé, les processeurs faisaient des additions en binaire, et corrigeaient le résultat avec une opération spécifique pour obtenir un résultat en BCD. Nous en reparlerons dans le chapitre sur le langage machine et l'assembleur, dans lequel nous étudierons les différentes opérations que supporte un processeur.
===Les encodages compacts du BCD===
Des variantes du BCD visent à réduire le nombre de bits utilisés pour encoder un nombre décimal. Nous allons les appeler les '''encodages BCD compacts'''. Avec certaines variantes, on peut utiliser seulement 7 bits pour coder deux chiffres décimaux, au lieu de 8 bits en BCD normal. Idem pour les nombres à 3 chiffres décimaux, qui prennent 10 bits au lieu de 12. Cette économie est réalisée par une variante du BCD assez compliquée, appelée l''''encodage Chen-Ho'''. Une alternative, appelée le ''Densely Packed Decimal'', arrive à une compression identique, mais avec quelques avantages au niveau de l'encodage. Ces encodages sont cependant assez compliqués à expliquer, surtout à ce niveau du cours, aussi je me contente de simplement mentionner leur existence.
Les encodages BCD compacts sont utilisés par les programmeurs pour stocker des données dans un fichier, en mémoire, mais guère plus. Ils ne sont pas gérés par le processeur directement, on ne peut pas faire de calculs avec, le processeur ne gére pas d'opération BCD supportant de tels encodages. C'est théoriquement possible, mais ça n'a jamais été implémenté dans un processeur, le cout en circuit n'en valait pas la chandelle. Pour faire des calculs sur des nombres en BCD compact, on doit les décompresser et les convertir en BCD normal ou en binaire, puis faire les calculs avec des opérations BCD/binaire usuelles.
===L'encodage ''Excess-3''===
La représentation '''''Excess-3''''' (XS-3) est une variante du BCD, qui a autrefois était utilisée sur d'anciens ordinateurs décimaux. Il s'agit d'une sorte d'hybride entre une représentation par excès et BCD. Chaque chiffre décimal est codé sur plusieurs bits en utilisant une représentation binaire par excès. Le biais est de 3 pour la représentation XS-3, il en existe des variantes avec un excès de 4, 5, mais elles sont moins utilisées. Avec elle, la conversion d'un chiffre décimal se fait comme suit : on prend le chiffre décimal, on ajoute 3, puis on traduit en binaire.
{| class="wikitable"
|+ Codage en ''Excess-3''
|-
! Décimal
! scope="col" colspan="4" | Binaire
|-
| -3 || 0 || 0 || 0 || 0
|-
| -2 || 0 || 0 || 0 || 1
|-
| -1 || 0 || 0 || 1 || 0
|-
| 0 || 0 || 0 || 1 || 1
|-
| 1 || 0 || 1 || 0 || 0
|-
| 2 || 0 || 1 || 0 || 1
|-
| 3 || 0 || 1 || 1 || 0
|-
| 4 || 0 || 1 || 1 || 1
|-
| 5 || 1 || 0 || 0 || 0
|-
| 6 || 1 || 0 || 0 || 1
|-
| 7 || 1 || 0 || 1 || 0
|-
| 8 || 1 || 0 || 1 || 1
|-
| 9 || 1 || 1 || 0 || 0
|-
| 10 || 1 || 1 || 0 || 1
|-
| 11 || 1 || 1 || 1 || 0
|-
| 12 || 1 || 1 || 1 || 1
|}
L'avantage de cette représentation est que l'on peut facilement calculer les soustractions en utilisant une méthode bien précise (celle du complément à 10, je ne détaille pas plus). Le défaut est que le calcul des additions est légèrement plus complexe.
L'XS-3 a été utilisé sur quelques ordinateurs décimaux assez anciens, notamment sur l'UNIVAC I et II.
===Les codes bi-quinaires===
Les '''codes bi-quinaires''' utilisent un mélange entre encodage binaire, encodage ''one-hot'' et encodage décimal. L'idée de base derrière ces codes est d'utiliser la représentation ''one-hot'' pour coder un chiffre BCD. Le problème est que cela demande d'utiliser 10 bits pour encoder un chiffre BCD, ce qui est beaucoup. L'encodage bi-quinaire optimise le tout de manière à diviser par deux le nombre de bits requis pour l'encodage.
Pour simplifier, un chiffre encodé en bi-quinaire est composé de deux parties : une partie binaire de un bit, suivie par d'une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits pour encoder les valeurs 0, 1, 2, 3 et 4. La partie binaire indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9 : 0 à 4 si la partie binaire vaut 0, 5 à 9 si elle vaut 1. La valeur du chiffre se calcule comme suit :
* si la partie binaire vaut 0, elle est égale à la partie quinaire ;
* si la partie binaire vaut 1, on ajoute 5 à la partie quinaire.
L'originalité de ce codage est qu'il permet de facilement compter sur ses doigts : on a deux mains, cinq doigts. De nombreuses langues utilisent ce système pour coder des nombres, comme le Wolof. Et ce système est utilisé dans certains pays pour compter sur ses doigts, voire dans la vie de tous les jours.
[[File:Code Biquinaer.svg|vignette|Code bi-quinaire où partie binaire et quinaire sont encodées en représentation ''one-hot''.]]
L'encodage abordé au-dessus est une version simplifiée de l'encodage bi-quinaire, il en existe d'autres variantes. Le bi-quinaire vu juste au-dessus encode la partie binaire en binaire, et la partie quinaire en ''one-hot''. Mais il est aussi possible d'inverser l'encodage, à savoir encoder la partie binaire avec une représentation ''one-hot'', alors que la partie bi-quinaire est encodée en binaire normal.
Cependant, la version la plus intéressante utilise l'encodage ''one-hot'' pour les deux ! Elle est illustrée ci-contre. Avec cette dernière, un chiffre BCD est encodé sur 7 bits, dont 2 sont obligatoirement à 1 et les 5 restants sont à 0. Cette propriété permet de détecter qu'un bit a été inversé suite à une erreur de transmission ou un problème de stockage. Si un bit est inversé, le résultat aura 1 bit à 1 et 6 à 0, ou alors 3 bits à 1 et 4 à 0, ce qui ne colle pas. Le résultat après inversion ne donnera pas un chiffre bi-quinaire valide.
Une autre variante du bi-quinaire a été utilisée sur l'IBM 1401. Avec elle, la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre encodé par la partie quinaire. L'avantage de ce système est que la gestion des retenues lors des additions était grandement simplifiée.
Le bi-quinaire et ses variantes ont été utilisés sur d'anciens ordinateurs, dont l'IBM 650, l'UNIVAC Solid State et le UNIVAC LARC. La plupart utilisaient des variantes de l'encodage bi-quinaire proprement dit. Pour rentrer vraiment dans le détail, voici les encodages utilisés sur ces machines. La lecture est facultative, il est possible de passer directement à la section suivante :
{| class="wikitable"
|-
! !! IBM 650 !! Remington Rand 409 !! UNIVAC Solid State !! UNIVAC LARC
|-
| Chiffre || 1357-9 bits || 05-01234 || p-5-421 bits || p-5-qqq bits
|-
| 0 || 10-10000 || 0000-0 || 1-0-000 || 1-0-000
|-
| 1 || 10-01000 || 1000-0 || 0-0-001 || 0-0-001
|-
| 2 || 10-00100 || 1000-1 || 0-0-010 || 1-0-011
|-
| 3 || 10-00010 || 0100-0 || 1-0-011 || 0-0-111
|-
| 4 || 10-00001 || 0100-1 || 0-0-100 || 1-0-110
|-
| 5 || 01-10000 || 0010-0 || 0-1-000 || 0-1-000
|-
| 6 || 01-01000 || 0010-1 || 1-1-001 || 1-1-001
|-
| 7 || 01-00100 || 0001-0 || 1-1-010|| 0-1-011
|-
| 8 || 01-00010 || 0001-1 || 0-1-011 || 1-1-111
|-
| 9 || 01-00001 || 0000-1 || 1-1-100|| 0-1-110
|}
==Annexe : pourquoi l'octet existe==
Nous avons vu plus haut que les nombres encodés en binaire sont découpés en octets, des paquets de 8 bits. Et les octets sont présents partout dans la conception d'un ordinateur. Par exemple, la mémoire RAM d'un ordinateur moderne est organisée en octets. Elle contient des cases mémoire, chacune contenant un octet. Les octets sont numérotés, ce qui permet de sélectionner un octet parmi tous les autres. La capacité de la mémoire est le nombre d'octets qu'elle contient, elle s'exprime en kilo-octets (1024 octets), mébi-octets (1024 kilo-octets), gibi-octets (1024 mébi-octets). Les octets sont donc présents partout dans l'informatique moderne.
{|class="wikitable"
|+ Contenu de la RAM, simplifié
|-
! Numéro
! Octet
|-
! 0
| 0000 1111
|-
! 1
| 0101 0101
|-
! 2
| 0111 0110
|-
! 3
| 0011 1010
|-
! 4
| 1001 1001
|-
! ...
| ...
|-
! 1023
| 0110 1010
|-
! 1024
| 0001 1110
|}
Mais pourquoi est-ce que les nombres encodés en binaire sont souvent découpés en octets ? Pourquoi ne pas utiliser des groupes de 3, 5, 6, 7, 9, 10 bits ? La question est d'autant plus légitime que d'anciens ordinateurs faisaient ainsi. Leurs mémoires étaient découpé en cases mémoire, mais chacune faisait 7 ou 9 bits, rien d'impossible à cela. À vrai dire, l'usage des octets au tout début de l'informatique était assez rare. Il y a eu un basculement progressif vers l'octet pendant les années 80-90. Reste à expliquer pourquoi. Si on devait résumer, les deux raisons principales sont le support du BCD et le support des caractères pour le texte. Voyons d'abord l'encodage des caractères en binaire.
===L'encodage du texte : ASCII et autres encodages===
Pour les caractères, les encodages se sont concentrés sur l'encodage des alphabets occidentaux. Ils demandaient d'encoder au minimum toutes les lettres, en majuscules et minuscules, plus les chiffres, une dizaine de caractères pour la ponctuation. À cela, il faut ajouter des symboles utiles comme %, µ, $, £, #, {} et bien d'autres. Au minimum, cela fait au minimum 52 lettres (26 lettres doublées pour les majuscules) + 10 chiffres + une dizaine de caractères pour la ponctuation : cela fait plus de 72 valeurs à encoder. Pour encoder cela en binaire, le minimum est de 7 bits, ce qui permet d'encoder 128 caractères : les 72 caractères minimum, plus quelques autres. L'encodage ASCII utilisait d'ailleurs 7 bits pour encoder des nombres.
On pourrait croire que c'était une raison suffisante pour utiliser des groupes de 7 bits. Mais il y a quelques problèmes avec cela. Premièrement, les encodages 7 bits pour le texte marchent bien pour gérer la langue anglaise et la plupart des langues occidentales, mais d'autres langues ont besoin d'encoder des caractères locaux. Les 128 caractères de l'ASCII ne sont pas suffisant, ce qui demande de passer de 7 à 8 bits. On retombe donc sur un octet ! D'ailleurs, les encodages ASCII améliorés utilisaient les 127 premières valeurs pour l'ASCII normal, mais les 128 suivantes encodaient d'autres caractères. Les encodages en question varient d'un pays ou d'une langue à l'autre, mais ils sont terriblement importants, ce qui est un argument pour l'usage des octets.
===Le support des données encodées sur un ''nibble''===
Une autre raison pour l'usage des octets est qu'il est facile de les découper en deux ''nibbles''. Pour rappel, les ''nibbles'' sont des groupes de 4 bits. Et il se trouve que de nombreuses données sont encodées sur 4 bits, ni plus ni moins. Les ''nibbles'' sont très important pour le support du BCD et de l’hexadécimal. Et c'était un avantage important au début de l'informatique, moins maintenant. Un octet regroupe deux ''nibbles'' : un ''nibble'' de poids faible, un ''nibble'' de poids fort.
[[File:Nibbles in a byte.PNG|centre|vignette|upright=2|Nibbles dans un octet.]]
Un chiffre BCD occupe un ''nibble'', ce qui fait que la taille idéale de l'unité mémoire doit être un multiple de 4 bits : 4 bits, 8 bits, 12 bits, 16 bits, etc. Un ''nibble'' de 4 bits est insuffisant pour encoder des caractères, 8 bits fonctionne parfaitement, pas besoin d'aller plus loin. De plus, l'usage des ''nibbles'' permet de gérer la représentation hexadécimale très facilement. Un chiffre hexadécimal est encodé sur exactement un ''nibble'', un octet regroupe deux chiffres hexadécimaux, deux octets 4 chiffres hexadécimaux, etc. Enfin, 8 bits est une puissance de deux, ce qui simplifie grandement l'encodage des entiers en binaire est est une source de nombreuses simplifications. Les circuits de l'ordinateur sont plus simple à concevoir quand ils manipulent des opérandes de 2, 4, 8, 16, 32, 64, 128 bits.
===L'influence sur la taille des opérandes===
Les anciens ordinateurs étaient conçus pour le calcul scientifique, ou du moins des applications similaires. Ils n'avaient pas besoin de supporter le BCD, ni même de gérer du texte. Ils ne faisaient que des calculs et utilisaient des opérandes flottantes. La taille des opérandes était dictée par des considérations calculatoires. La norme à l'époque était que les calculs scientifiques devaient gérer des opérandes pouvant aller jusqu'à 10 chiffres décimaux. Et cela demande d'utiliser des opérandes entiers de 35 bits, avec un bit en plus pour encoder des opérandes positives ou négatives. Les 36 bits étaient parfois découpés en 6 groupes de 6 bits chacun, au lieu des octets de 8 bits.
Mais ces groupes de 6 bits étaient souvent peu utilisés. Prenons l'exemple des mémoires RAM. Plus haut, j'ai dit qu'une mémoire RAM normale est composée de plusieurs octets, tous numérotés. Intuitivement, vous vous dites que les anciens ''mainframes'' remplaçaient ces octets par des groupes de 6 bits dans la RAM. Mais en réalité, l'équivalent des RAM de l'époque utilisait des groupes de 36 bits. les groupes de 36 bits étaient aussi numérotés, les numéros étant appelés des adresses mémoire. Par contre, les adresses/numéros étaient codés sur 12 ou 18 bits, c'est à dire sur 2 ou 3 groupes de 6 bits. La raison est que ces numéros sont manipulés par le processeur, qui gère des opérandes de 36 bits. L'idée est que le numéro ne prend que la moitié ou un tiers des 36 bits gérés par le processeur.
Avec l'apparition de l'informatique commerciale, les ordinateurs ont du s'adapter aux besoins des entreprises, pour gérer la comptabilité, des applications financières, des documents texte, etc. Et cela demandait de supporter à la fois le BCD et les caractères/lettres. Il a fallu trouver une unité mémoire capable de gérer à la fois nombres entiers, chiffres BCD et caractères. L'octet était parfait pour ça. Les ''mainframes'' sont adaptés en supportant des caractères codés sur 6, 7 ou 8 bits. Mais le support demandait d'utiliser des circuits qui découpaient un groupe de 36 bits en plusieurs caractères ou en plusieurs chiffres BCD. Le tout demandait des circuits de décalage et de masquage assez complexes. Les ordinateurs 8 et 16 bits manipulaient des octets nativement : leur mémoire RAM gérait des octets, le processeur traitait des opérandes de 8, 16 ou 32 bits. Ils n'avaient pas besoin de matériel complexe pour extraire un caractère ou un chiffre BCD d'un octet/doublet/multiplet. C'était un avantage que les anciens ''mainframes'' n'avaient pas.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'encodage des données
| prevText=L'encodage des données
| next=Les codes de détection/correction d'erreur
| nextText=Les codes de détection/correction d'erreur
}}
</noinclude>
6p53w95olvn5q4uwovopo10itgm572h
775649
775648
2026-11-02T20:00:26Z
Mewtow
31375
/* Le codage des nombres flottants et la norme IEEE 754 */
775649
wikitext
text/x-wiki
Dans le chapitre précédent, nous avons vu que les ordinateurs actuels utilisent un codage binaire. Ce codage binaire ne vous est peut-être pas familier. Aussi, dans ce chapitre, nous allons apprendre comment coder des nombres en binaire. Nous allons commencer par le cas le plus simple : les nombres positifs. Par la suite, nous aborderons les nombres négatifs. Et nous terminerons par les nombres à virgules, appelés aussi nombres flottants.
==Le codage des nombres entiers positifs==
Pour coder des nombres entiers positifs, il existe plusieurs méthodes : le binaire, l’hexadécimal, le code Gray, le décimal codé binaire et bien d'autres encore. La plus connue est certainement le binaire, secondée par l'hexadécimal, les autres étant plus anecdotiques. Pour comprendre ce qu'est le binaire, il nous faut faire un rappel sur les nombres entiers tel que vous les avez appris en primaire, à savoir les entiers écrits en décimal. Prenons un nombre écrit en décimal : le chiffre le plus à droite est le chiffre des unités, celui à côté est pour les dizaines, suivi du chiffre des centaines, et ainsi de suite. Dans un tel nombre :
* on utilise une dizaine de chiffres, de 0 à 9 ;
* chaque chiffre est multiplié par une puissance de 10 : 1, 10, 100, 1000, etc. ;
* la position d'un chiffre dans le nombre indique par quelle puissance de 10 il faut le multiplier : le chiffre des unités doit être multiplié par 1, celui des dizaines par 10, celui des centaines par 100, et ainsi de suite.
Exemple avec le nombre 1337 :
<math>1337 = 1 \times 1000 + 3 \times 100 + 3 \times 10 + 7 \times 1</math>
Pour résumer, un nombre en décimal s'écrit comme la somme de produits, chaque produit multipliant un chiffre par une puissance de 10. On dit alors que le nombre est en base 10.
<math>1337 = 1 \times 10^3 + 3 \times 10^2 + 3 \times 10^1 + 7 \times 10^0</math>
Ce qui peut être fait avec des puissances de 10 peut être fait avec des puissances de 2, 3, 4, 125, etc : n'importe quel nombre entier strictement positif peut servir de '''base'''. En informatique, on utilise rarement la base 10 à laquelle nous sommes tant habitués. On utilise à la place deux autres bases :
* La base 2 (système binaire) : les chiffres utilisés sont 0 et 1 ;
* La base 16 (système hexadécimal) : les chiffres utilisés sont 0, 1, 2, 3, 4, 5, 6, 7, 8 et 9 ; auxquels s'ajoutent les six premières lettres de notre alphabet : A, B, C, D, E et F.
===Le système binaire===
En binaire, on compte en base 2. Cela veut dire qu'au lieu d'utiliser des puissances de 10 comme en décimal, on utilise des puissances de deux : n'importe quel nombre entier peut être écrit sous la forme d'une somme de puissances de 2. Par exemple 6 s'écrira donc 0110 en binaire : <math>0 \times 2^3 + 1 \times 2^2 + 1 \times 2^1 + 0 \times 2^0 = 6</math>. On peut remarquer que le binaire n'autorise que deux chiffres, à savoir 0 ou 1 : ces chiffres binaires sont appelés des '''bits''' (abréviation de Binary Digit). Pour simplifier, on peut dire qu'un bit est un truc qui vaut 0 ou 1. Pour résumer, tout nombre en binaire s'écrit sous la forme d'un produit entre bits et puissances de deux de la forme :
<math>a_0 \times 2^0 + a_1 \times 2^1 + a_2 \times 2^2 + a_3 \times 2^3 + a_4 \times 2^4 +...+ a_n \times 2^n</math>
Les coefficients <math>a_0, a_1, a_2</math> sont les bits, l'exposant n qui correspond à un bit <math>a_n</math> est appelé le '''poids''' du bit.
====La terminologie du binaire====
En informatique, il est rare que l'on code une information sur un seul bit. Dans la plupart des cas, l'ordinateur manipule des nombres codés sur plusieurs bits. Les informaticiens ont donné des noms aux groupes de bits suivant leur taille. Le plus connu est certainement l''''octet''', qui désigne un groupe de 8 bits. Moins connu, on parle de '''''nibble''''' pour un groupe de 4 bits (un demi-octet), de '''doublet''' pour un groupe de 16 bits (deux octets) et de '''quadruplet''' pour un groupe de 32 bits (quatre octets).
Précisons qu'en anglais, le terme ''byte'' n'est pas synonyme d'octet. En réalité, le terme ''octet'' marche aussi bien en français qu'en anglais. Quant au terme ''byte'', il désigne un concept complètement différent, que nous aborderons plus tard (c'est la plus petite unité de mémoire que le processeur peut adresser). Il a existé dans le passé des ordinateurs où le ''byte'' faisait 4, 7, 9, 16, voire 48 bits, par exemple. Il a même existé des ordinateurs où le ''byte'' faisait exactement 1 bit ! Mais sur presque tous les ordinateurs modernes, un ''byte'' fait effectivement 8 bits, ce qui fait que le terme ''byte'' est parfois utilisé en lieu et place d'''octet''. Mais c'est un abus de langage, attention aux confusions ! Dans ce cours, nous parlerons d'octet pour désigner un groupe de 8 bits, en réservant le terme ''byte'' pour sa véritable signification.
À l'intérieur d'un nombre, le '''bit de poids faible''' est celui qui est le plus à droite du nombre, alors que le '''bit de poids fort''' est celui non nul qui est placé le plus à gauche, comme illustré dans le schéma ci-dessous.
C'est le même principe que l'écriture des nombres en base décimale : le chiffre le plus significatif à gauche, et le moins significatif à droite (l'unité ou les décimales après la virgule).
[[File:Msb.svg|centre|vignette|upright=2|Bit de poids fort.]]
[[File:Lsb.svg|centre|vignette|upright=2|Bit de poids faible.]]
Cette terminologie s'applique aussi pour les bits à l'intérieur d'un octet, d'un ''nibble'', d'un doublet ou d'un quadruplet. Pour un nombre codés sur plusieurs octets, on peut aussi parler de l'octet de poids fort et de l'octet de poids faible, du doublet de poids fort ou de poids faible, etc.
====La traduction binaire→décimal====
Pour traduire un nombre binaire en décimal, il faut juste se rappeler que la position d'un bit indique par quelle puissance il faut le multiplier. Ainsi, le chiffre le plus à droite est le chiffre des unités : il doit être multiplié par 1 (<math>2^0</math>). Le chiffre situé immédiatement à gauche du chiffre des unités doit être multiplié par 2 (<math>2^1</math>). Le chiffre encore à gauche doit être multiplié par 4 (<math>2^2</math>), et ainsi de suite. Mathématiquement, on peut dire que le énième bit en partant de la droite doit être multiplié par <math>2^{n-1}</math>. Par exemple, la valeur du nombre noté 1011 en binaire est de <math>8 \times 1 + 4 \times 0 + 2 \times 1 + 1 \times 1 = 11</math>.
[[File:Value of digits in the Binary numeral system.svg|centre|vignette|upright=2|Valeur des chffres dans le système de numération binaire.]]
====La traduction décimal→binaire====
La traduction inverse, du décimal au binaire, demande d'effectuer des divisions successives par deux. Les divisions en question sont des divisions euclidiennes, avec un reste et un quotient. En lisant les restes des divisions dans un certain sens, on obtient le nombre en binaire. Voici comment il faut procéder, pour traduire le nombre 34 :
[[File:Exemple d'illustration de la méthode de conversion décimal vers binaire.png|centre|vignette|upright=2|Exemple d'illustration de la méthode de conversion décimal vers binaire.]]
====Quelques opérations en binaire====
Maintenant que l'on sait coder des nombres en binaire normal, il est utile de savoir comment faire quelques opérations usuelles en binaire. Nous utiliserons les acquis de cette section dans la suite du chapitre, bien que de manière assez marginale.
La première opération est assez spécifique au binaire. Il s'agit d'une opération qui inverse les bits d'un nombre : les 0 deviennent des 1 et réciproquement. Par exemple, le nombre 0001 1001 devient 1110 0110. Elle porte plusieurs noms : opération NOT, opération NON, complémentation, etc. Nous parlerons de '''complémentation''' ou d'opération NOT dans ce qui suit. Beaucoup d'ordinateurs gèrent cette opération, ils savent la faire en un seul calcul. Il faut dire que c'est une opération assez utile, bien que nous ne pouvons pas encore expliquer pourquoi.
[[File:Binary Addition Demonstration.svg|vignette|Exemple d'addition en binaire.]]
La seconde opération à aborder est l'addition. Elle se fait en binaire de la même manière qu'en décimal : Pour faire une addition en binaire, on additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. Sauf que l'on additionne des bits. Heureusement, la table d'addition est très simple en binaire :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
La troisième opération est une variante de l'addition appelée l''''opération XOR''', notée <math>\oplus</math>. Il s'agit d'une addition dans laquelle on ne propage pas les retenues. L'addition des deux bits des opérandes se fait normalement, mais les retenues sont simplement oubliées, on n'en tient pas compte. Le résultat est que l'addition se résume à appliquer la table d'addition précédente :
* 0 <math>\oplus</math> 0 = 0 ;
* 0 <math>\oplus</math> 1 = 1 ;
* 1 <math>\oplus</math> 0 = 1 ;
* 1 <math>\oplus</math> 1 = 0.
Pour résumer, le résultat vaut 1 si les deux bits sont différents, 0 s'ils sont identiques. L'opération XOR sera utilisée rapidement dans le chapitre suivant, quand nous parlerons rapidement du mot de parité. Et elle sera beaucoup utilisée dans la suite du cours, nous en feront fortement usage. Pour le moment, mémorisez juste cette opération, elle n'a rien de compliqué.
Une dernière opération est l'opération de '''''population count'''''. Il s'agit ni plus ni moins que de compter le nombre de bits qui sont à 1 dans un nombre. Par exemple, pour le nombre 0110 0010 1101 1110, elle donne pour résultat 9. Elle est utilisée dans certaines applications, comme le calcul de certains codes correcteurs d'erreur, comme on le verra dans le chapitre suivant. Elle est supportée sur de nombreux ordinateurs, encore que cela dépende du processeur considéré. Il s'agit cependant d'une opération assez courante, supportée par les processeurs ARM, les processeurs x86 modernes (ceux qui gèrent le SSE), et quelques autres.
===L'hexadécimal===
L’hexadécimal est basé sur le même principe que le binaire, sauf qu'il utilise les 16 chiffres suivants :
{| class="wikitable"
|- style="color:#8080ffff; text-align:center;"
! Chiffre hexadécimal
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || A || B || C || D || E || F
|- style="text-align:center;"
! Nombre décimal correspondant
| 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 10 || 11 || 12 || 13 || 14 || 15
|- style="text-align:center;"
! Notation binaire
| 0000 || 0001 || 0010 || 0011 || 0100 || 0101 || 0110 || 0111 || 1000 || 1001 || 1010 || 1011 || 1100 || 1101 || 1110 || 1111
|}
Dans les textes, afin de différencier les nombres décimaux des nombres hexadécimaux, les nombres hexadécimaux sont suivis par un petit h, indiqué en indice. Si cette notation n'existait pas, des nombres comme 2546 seraient ambigus : on ne saurait pas dire sans autre indication s'ils sont écrits en décimal ou en hexadécimal. Avec la notation, on sait de suite que 2546 est en décimal et que 2546h est en hexadécimal.
Dans les codes sources des programmes, la notation diffère selon le langage de programmation.
Certains supportent le suffixe <code>h</code> pour les nombres hexadécimaux, d'autres utilisent un préfixe <code>0x</code> ou <code>0h</code>.
Pour convertir un nombre hexadécimal en décimal, il suffit de multiplier chaque chiffre par la puissance de 16 qui lui est attribuée. Là encore, la position d'un chiffre indique par quelle puissance celui-ci doit être multiplié : le chiffre le plus à droite est celui des unités, le second chiffre le plus à droite doit être multiplié par 16, le troisième chiffre en partant de la droite doit être multiplié par 256 (16 * 16) et ainsi de suite. La technique pour convertir un nombre décimal vers de l’hexadécimal est similaire à celle utilisée pour traduire un nombre du décimal vers le binaire. On retrouve une suite de divisions successives, mais cette fois-ci les divisions ne sont pas des divisions par 2 : ce sont des divisions par 16.
La conversion inverse, de l'hexadécimal vers le binaire est très simple, nettement plus simple que les autres conversions. Pour passer de l'hexadécimal au binaire, il suffit de traduire chaque chiffre en sa valeur binaire, celle indiquée dans le tableau au tout début du paragraphe nommé « Hexadécimal ». Une fois cela fait, il suffit de faire le remplacement. La traduction inverse est tout aussi simple : il suffit de grouper les bits du nombre par 4, en commençant par la droite (si un groupe est incomplet, on le remplit avec des zéros). Il suffit alors de remplacer le groupe de 4 bits par le chiffre hexadécimal qui correspond.
==Interlude propédeutique : la capacité d'un entier et les débordements d'entiers==
Dans la section précédente, nous avons vu comment coder des entiers positifs en binaire ou dans des représentations proches. La logique voudrait que l'on aborde ensuite le codage des entiers négatifs. Mais nous allons déroger à cette logique simple, pour des raisons pédagogiques. Nous allons faire un interlude qui introduira des notions utiles pour la suite du chapitre. De plus, ces concepts seront abordés de nombreuses fois dans ce wikilivre et l'introduire ici est de loin la solution idéale.
===Les ordinateurs manipulent des nombres codés sur un nombre fixe de bits===
Vous avez certainement déjà entendu parler de processeurs 32 ou 64 bits. Et si vous avez joué aux jeux vidéos durant votre jeunesse et êtes assez agé, vous avez entendu parler de consoles de jeu 8 bits, 16 bits, 32 bits, voire 64 bits (pour la Jaguar, et c'était un peu trompeur). Derrière cette appellation qu'on retrouvait autrefois comme argument commercial dans la presse se cache un concept simple. Tout ordinateur manipule des nombres entiers dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Une console 16 bits manipulait des entiers codés en binaire sur 16 bits, pas un de plus, pas un de moins. Pareil pour les anciens ordinateurs 32 bits, qui manipulaient des nombres entiers codés sur 32 bits.
Aujourd'hui, les ordinateurs modernes utilisent presque un nombre de bits qui est une puissance de 2 : 8, 16, 32, 64, 128, 256, voire 512 bits. Mais cette règle souffre évidemment d'exceptions. Aux tout débuts de l'informatique, certaines machines utilisaient 3, 7, 13, 17, 23, 36 et 48 bits ; mais elles sont aujourd'hui tombées en désuétude. De nos jours, il ne reste que les processeurs dédiés au traitement de signal audio, que l'on trouve dans les chaînes HIFI, les décodeurs TNT, les lecteurs DVD, etc. Ceux-ci utilisent des nombres entiers de 24 bits, car l'information audio est souvent codée par des nombres de 24 bits.
Anecdote amusante, il a existé des ordinateurs de 1 bit, qui sont capables de manipuler des nombres codés sur 1 bit, pas plus. Un exemple est le Motorola MC14500B, commercialisé de 1976.
====Le lien entre nombre de bits et valeurs codables====
Évidemment, on ne peut pas coder tous les entiers possibles et imaginables avec seulement 8 bits, ou 16 bits. Et il parait intuitif que l'on ait plus de valeurs codables sur 16 bits qu'avec 8 bits, par exemple. Plus le nombre de bits est important, plus on pourra coder de valeurs entières différentes. Mais combien de plus ? Par exemple, si je passe de 8 bits à 16 bits, est-ce que le nombre de valeurs que l'on peut coder double, quadruple, pentuple ? De même, combien de valeurs différentes on peut coder avec <math>n</math> bits. Par exemple, combien de nombres différents peut-on coder avec 4, 8 ou 16 bits ? La section précédente vous l'expliquer.
Avec <math>n</math> bits, on peut coder <math>2^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>2^n - 1</math>. N'oubliez pas cette formule : elle sera assez utile dans la suite de ce tutoriel. Pour exemple, on peut coder 16 valeurs avec 4 bits, qui vont de 0 à 15. De même, on peut coder 256 valeurs avec un octet, qui vont de 0 à 255. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de bits
! Nombre de valeurs codables
|-
! 4
| 16
|-
! 8
| 256
|-
! 16
| 65 536
|-
! 32
| 4 294 967 296
|-
! 64
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien de bits il faut pour coder une valeur quelconque, que nous noterons N. Pour cela, il faut utiliser la formule précédente, mais à l'envers. On cherche alors <math>n</math> tel que <math>N = 2^n</math>. L'opération qui donne <math>n</math> est appelée le logarithme, et plus précisément un logarithme en base 2, noté <math>\log_2{(X)}</math>. Le problème est que le résultat du logarithme ne tombe juste que si le nombre X est une puissance de 2. Si ce n'est pas le cas, le résultat est un nombre à virgule, ce qui n'a pas de sens pratique. Par exemple, la formule nous dit que pour coder le nombre 13, on a besoin de 3,70043971814 bits, ce qui est impossible. Pour que le résultat ait un sens, il faut arrondir à l'entier supérieur. Pour l'exemple précédent, les 3,70043971814 bits s'arrondissent en 4 bits.
====Le lien entre nombre de chiffres hexadécimaux et valeurs codables====
Maintenant, voyons combien de valeurs peut-on coder avec <math>n</math> chiffres hexadécimaux. La réponse n'est pas très différente de celle obtenue en binaire, si ce n'est qu'il faut remplacer le 2 par un 16 dans la formule précédente. Avec <math>n</math> chiffres hexadécimaux, on peut coder <math>16^n</math> valeurs différentes, dont le <math>0</math>, ce qui fait qu'on peut compter de <math>0</math> à <math>16^n - 1</math>. Le tableau ci-dessous donne quelques exemples communs.
{|class="wikitable"
|-
! Nombre de chiffres héxadécimaux
! Nombre de valeurs codables
|-
! 1 (4 bits)
| 16
|-
! 2 (8 bits)
| 256
|-
! 4 (16 bits)
| 65 536
|-
! 8 (32 bits)
| 4 294 967 296
|-
! 16 (64 bits)
| 18 446 744 073 709 551 615
|}
Inversement, on peut se demander combien faut-il de chiffres hexadécimaux pour coder une valeur quelconque en hexadécimal. La formule est là encore la même qu'en binaire, sauf qu'on remplace le 2 par un 16. Pour trouver le nombre de chiffres hexadécimaux pour encoder un nombre X, il faut calculer <math>\log_{16}{(X)}</math>. Notons que le logarithme utilisé est un logarithme en base 16, et non un logarithme en base 2, comme pour le binaire. Là encore, le résultat ne tombe juste que si le nombre X est une puissance de 16 et il faut arrondir à l'entier supérieur si ce n'est pas le cas.
Une propriété mathématique des logarithmes nous dit que l'on peut passer d'un logarithme en base X et à un logarithme en base Y avec une simple division, en utilisant la formule suivante :
: <math>\log_X{(N)} = {\log_{Y}{(N)} \over \log_Y(X)}</math>
Dans le cas qui nous intéresse, on a Y = 2 et X = 16, ce qui donne :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over \log_2(16)}</math>
Or, <math>\log_2(16)</math> est tout simplement égal à 4, car il faut 4 bits pour coder la valeur 16. On a donc :
: <math>\log_{16}{(N)} = {\log_{2}{(N)} \over 4}</math>
En clair, il faut quatre fois moins de chiffres hexadécimaux que de bits, ce qui est assez intuitif vu qu'il faut 4 bits pour coder un chiffre hexadécimal.
===Les débordements d'entier===
On vient de voir que tout ordinateur manipule des nombres dont le nombre de bits est toujours le même : on dit qu'ils sont de taille fixe. Et cela limite les valeurs qu'il peut encoder, qui sont comprises dans un intervalle bien précis. Mais que ce passe-t-il si jamais le résultat d'un calcul ne rentre pas dans cet intervalle ? Par exemple, pour du binaire normal, que faire si le résultat d'un calcul atteint ou dépasse <math>2^n</math> ? Dans ce cas, le résultat ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
On peut imaginer d'autres codages pour lesquels les entiers ne commencent pas à zéro ou ne terminent pas à <math>2^n - 1</math>. On peut prendre le cas où l'ordinateur gère les nombres négatifs, par exemple. Dans le cas général, l'ordinateur peut coder les valeurs comprises dans un intervalle, qui va de la valeur la plus basse <math>N_\text{min}</math> à la valeur la plus grande <math>N_\text{max}</math>. Et encore une fois, si un résultat de calcul sort de cet intervalle, on fait face à un débordement d'entier.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math>, avec <math>N_\text{max}</math> la plus grande valeur codable par l'ordinateur.
On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur basse de débordement vaut <math>N_\text{min} - 1</math>, avec <math>N_\text{min}</math> la plus petite valeur codable par l'ordinateur.
===La gestion des débordements d'entiers===
Face à un débordement d'entier, l'ordinateur peut utiliser deux méthodes : l'arithmétique saturée ou l'arithmétique modulaire.
L''''arithmétique saturée''' consiste à arrondir le résultat pour prendre la plus grande ou la plus petite valeur. Si le résultat d'un calcul dépasse la valeur haute de débordement, le résultat est remplacé par le plus grand entier supporté par l'ordinateur. La même chose est possible quand le résultat est inférieur à la plus petite valeur possible, par exemple lors d'une soustraction : l'ordinateur arrondit au plus petit entier possible.
Pour donner un exemple, voici ce que cela donne avec des entiers codés sur 4 bits, qui codent des nombres de 0 à 15. Si je fais le calcul 8 + 9, le résultat normal vaut 17, ce qui ne rentre pas dans l'intervalle. Le résultat est alors arrondi à 15. Inversement, si je fais le calcul 8 - 9, le résultat sera de -1, ce qui ne rentre pas dans l'intervalle : le résultat est alors arrondi à 0.
[[File:Odometer rollover.jpg|vignette|Exemple de débordement d'entier sur un compteur mécanique quelconque. L'image montre clairement que le compteur revient à zéro une fois la valeur maximale dépassée.]]
L''''arithmétique modulaire''' est plus compliquée et c'est elle qui va nous intéresser dans ce qui suit. Pour simplifier, imaginons que l'on décompte à partir de zéro. Quand on arrive à la valeur haute de débordement, on recommence à compter à partir de zéro. L'arithmétique modulaire n'est pas si contre-intuitive et vous l'utilisez sans doute au quotidien. Après tout, c'est comme cela que l'on compte les heures, les minutes et les secondes. Quand on compte les minutes, on revient à 0 au bout de 60 minutes. Pareil pour les heures : on revient à zéro quand on arrive à 24 heures. Divers compteurs mécaniques fonctionnent sur le même principe et reviennent à zéro quand ils dépassent la plus grande valeur possible, l'image ci-contre en montrant un exemple.
Mathématiquement, l'arithmétique modulaire implique des divisions euclidiennes, celles qui donnent un quotient et un reste. Lors d'un débordement, le résultat s'obtient comme suit : on divise le nombre qui déborde par la valeur haute de débordement, et que l'on conserve le reste de la division. Au passage, l'opération qui consiste à faire une division et à garder le reste au lieu du quotient est appelée le ''modulo''. Prenons l'exemple où l'ordinateur peut coder tous les nombres entre 0 et 1023, soit une valeur haute de débordement de 1024. Pour coder le nombre 4563, on fait le calcul 4563 / 1024. On obtient : <math>4563 = 4 \times 1024 + 467</math>. Le reste de la division est de 467 et c'est lui qui sera utilisé pour coder la valeur de départ, 4563. Le nombre 4563 est donc codé par la valeur 467 dans un tel ordinateur en arithmétique modulaire. Au passage, la valeur haute de débordement est toujours codée par un zéro dans ce genre d'arithmétique.
Les ordinateurs utilisent le plus souvent une valeur haute de débordement de <math>2^n</math>, avec n le nombre de bits utilisé pour coder les nombres entiers positifs. En faisant cela, l'opération modulo devient très simple et revient à éliminer les bits de poids forts au-delà du énième bit. Par exemple, reprenons l'exemple d'un ordinateur qui code ses nombres sur 4 bits. Imaginons qu'il fasse le calcul <math>13 + 3</math>, soit 1101 + 0011 = 1 0000 en binaire. Le résultat entraîne un débordement d'entier et l'ordinateur ne conserve que les 4 bits de poids faible. Cela donne : 1101 + 0011 = 0000. Comme autre exemple l'addition 1111 + 0010 ne donnera pas 17 (1 0001), mais 1 (0001).
L'avantage est que les calculs sont beaucoup plus simples avec cette méthode qu'avec les autres. L'ordinateur a juste à ne pas calculer les bits de poids fort. Pas besoin de faire une division pour calculer un modulo, pas besoin de corriger le résultat pour faire de l'arithmétique saturée.
===Aparté : quelques valeurs particulières en binaire===
Plus haut, on a dit qu'avec n bits, on peut encoder toutes les valeurs allant de <math>0</math> à <math>2^n - 1</math>. Ce simple fait permet de déterminer quelle est la valeur de certains entiers à vue d’œil. Dans ce qui va suivre, nous allons poser quelques bases que nous réutiliserons dans la suite du chapitre. Il s'agit de quelques trivias qui sont cependant assez utiles.
Le premier trivia concerne la valeur maximale <math>2^n - 1</math> : elle est encodée par un nombre dont tous les bits sont à 1.
Le deuxième trivia concerne les nombres de la forme 000...000 1111 1111, à savoir des nombres dont les x bits de poids faible sont à 1 et tous les autres valent 0. Par définition, de tels nombres codent la plus grande valeur possible sur x bits, ce qui fait qu'ils valent <math>2^x - 1</math>.
Le troisième trivia concerne les nombres de la forme 11111...0000. En clair, des nombres où on a une suite consécutive de 1 dans les bits de poids fort et les x bits de poids faibles à 0. De tels nombres valent : <math>2^n - 2^x</math>. La preuve est assez simple : ils s'obtiennent en prenant la valeur maximale <math>2^n - 1</math>, et en soustrayant un nombre de la forme <math>2^x - 1</math>.
[[File:Valeurs particulières en binaire.png|centre|vignette|upright=2|Valeurs particulières en binaire]]
Si on utilise l'arithmétique modulaire, la valeur <math>2^n</math> n'est autre que la valeur de débordement haute pour les nombres stockés sur n bits, et se code comme le zéro (il faudrait 1 bit de plus pour stocker le 1 de poids fort de <math>2^n</math>). Les 1er et 3ème nombres évoqués dans les paragraphes précédents peuvent donc se passer de <math>2^n</math> dans leur expression :
* <math>- 1</math> est codé 1111111111111111...111 (n bits à 1)
* <math>- 2^x</math> est codé 11111...111000..000000 (x bits à 0 précédés de n-x bits à 1)
==Les nombres entiers négatifs==
<noinclude>[[File:Binary Representations.svg|vignette|Représentations signées, exemples sur 4 bits.]]</noinclude>
Passons maintenant aux entiers négatifs en binaire : comment représenter le signe moins ("-") avec des 0 et des 1 ? Eh bien, il existe plusieurs méthodes :
* la représentation en signe-valeur absolue ;
* la représentation en complément à un ;
* la représentation en complément à deux;
* la représentation par excès ;
* la représentation dans une base négative ;
* d'autres représentations encore moins utilisées que les autres.
===La représentation en signe-valeur absolue===
La solution la plus simple pour représenter un entier négatif consiste à coder sa valeur absolue en binaire, et rajouter un bit qui précise si c'est un entier positif ou un entier négatif. Par convention, ce bit de signe vaut 0 si le nombre est positif et 1 s'il est négatif. On parle alors de représentation en signe-valeur absolue, aussi appelée '''représentation en signe-magnitude'''.
Avec cette technique, il y autant de nombres positifs que négatifs. Mieux : pour chaque nombre représentable en représentation signe-valeur absolue, son inverse l'est aussi. Ce qui fait qu'avec cette méthode, le zéro est codé deux fois : on a un -0, et un +0. Cela pose des problèmes lorsqu'on demande à notre ordinateur d'effectuer des calculs ou des comparaisons avec zéro.
[[File:Codage sur 4 bits en signe-valeur absolue.png|centre|vignette|upright=2|Codage sur 4 bits en signe-valeur absolue]]
===La représentation en complément à un===
La représentation en '''complément à un''' peut être vue, en première approximation, comme une variante de la représentation en signe-magnitude. Et je dis bien : "en première approximation", car il y a beaucoup à dire sur la représentation en complément à un, mais nous verrons cela dans la section suivante sur le complément à deux. Conceptuellement, la représentation en signe-magnitude et celle du complément à un sont drastiquement différentes et sont basées sur des concepts mathématiques totalement différents. Mais elles ont des ressemblances de surface, qui font que faire la comparaison entre les deux est assez utile pédagogiquement parlant.
En complément à un, les nombres sont codés en utilisant un bit de signe qui indique si le nombre de positif ou négatif, couplé à une valeur absolue. Par contre, la valeur absolue est codée différemment en binaire. Pour les nombres positifs, la valeur absolue est codée en binaire normal, pas de changement comparé aux autres représentations. Mais pour les valeurs négatives, la valeur absolue est codée en binaire normal, puis tous les bits sont inversés : les 0 deviennent des 1 et réciproquement. En clair, on utilise une opération de complémentation pour les nombres négatifs.
Prenons un exemple avec un nombre codé sur 4 bits, avec un cinquième bit de signe. La valeur 5 est codée comme suit : 0 pour le bit de signe, 5 donne 0101 en binaire, le résultat est 0 0101. Pour la valeur -5, le bit de signe est 1, 5 donne 0101 en binaire, on inverse les bits ce qui donne 1010 : cela donne 11010. Notez qu'on peut passer d'un résultat à l'autre avec une opération de complémentation, à savoir en inversant les bits de l'autre et réciproquement.
Il s'agit là d'une propriété générale avec le complément à 1 : l'opposé d'un nombre, à savoir passer de sa valeur positive à sa valeur négative ou inversement, se calcule avec une opération NOT, une opération de complémentation (d'où son nom). L'avantage est que les ordinateurs gèrent naturellement d'opération de complémentation. Par contre, en signe-magnitude, inverser le bit de signe est une opération spécifique et ne sert qu'à ça. Il faut donc rajouter une opération en plus pour calculer l'opposé d'un nombre.
La représentation en complément à un garde les défauts de la représentation en signe-magnitude. Le zéro est codé deux fois, avec un zéro positif et un zéro négatif. La différence est que les valeurs négatives sont dans l'ordre inverse, il y a une symétrie un peu meilleure. Les deux zéros sont d'ailleurs totalement éloignés, ils correspondent aux valeurs extrêmes encodables : le zéro positif a tous ses bits à 0, le zéro négatif a tous ses bits à 1. Le résultat est que l'implémentation des comparaisons et de certains calculs est plus complexe qu'avec le signe-magnitude, mais de peu.
[[File:Codage sur 4 bits en complément à 1.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 1.]]
===La représentation par excès===
La représentation par excès consiste à ajouter un biais aux nombres à encoder afin de les encoder par un entier positif. Pour encoder tous les nombres compris entre -X et Y en représentation par excès, il suffit de prendre la valeur du nombre à encoder, et de lui ajouter un biais égal à X. Ainsi, la valeur -X sera encodée par zéro, et toutes les autres valeurs le seront par un entier positif, le zéro sera encodé par X, 1, par X+1, etc. Par exemple, prenons des nombres compris entre -127 et 128. On va devoir ajouter un biais égal à 127, ce qui donne :
{|class="wikitable"
|-
!Valeur avant encodage!!Valeur après encodage
|-
||-127||0
|-
||-126||1
|-
||-125||2
|-
||…||…
|-
||0||127
|-
||…||…
|-
||127||254
|-
||128||255
|}
===La représentation en complément à deux===
La représentation en '''complément à deux''' est basée sur les mêmes mathématiques que le complément à un, mais fonctionne très différemment en pratique.
Si on regarde de loin, son principe est assez différent : il n'y a pas de bit de signe ni quoi que ce soit d'autre. À la place, un nombre en complément à deux est encodé comme en binaire normal, à un point près : le bit de poids fort est soustrait, et non additionné aux autres. Il a une valeur négative : on soustrait la puissance de deux associée s'il vaut 1, on ne la tient pas en compte s'il vaut 0.
Par exemple, la valeur du nombre noté 111001 en complément à deux s'obtient comme suit :
{|class="wikitable" style="margin:auto;"
|-
! -32 !! 16 !! 8 !! 4 !! 2 !! 1
|-
| 1 || 1 || 1 || 0 || 0 || 1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(8×1)+(4×0)+(2×1)+(1×1) = −32+16+8+1 = -7.
: ''Avec le complément à deux, comme avec le complément à un, le bit de poids fort vaut 0 pour les nombres positifs et 1 pour les négatifs.''
L'avantage de cette représentation est qu'elle n'a pas de double zéro : le zéro n'est encodé que par une seule valeur. Par contre, la valeur autrefois prise par le zéro négatif est réutilisée pour encoder une valeur négative. La conséquence est que l'on a un nombre négatif en plus d'encodé, il n'y a plus le même nombre de valeurs strictement positives et de valeurs négatives encodées. Le nombre négatif en question est appelé le '''nombre le plus négatif''', ce nom trahit le fait que c'est celui qui a la plus petite valeur (la plus grande valeur absolue). Il est impossible de coder l'entier positif associé, sa valeur absolue.
[[File:Codage sur 4 bits en complément à 2.png|centre|vignette|upright=2|Codage sur 4 bits en complément à 2.]]
====Le calcul du complément à deux : première méthode====
Les représentations en complément à un et en complément à deux sont basées sur le même principe mathématique. Leur idée est de remplacer chaque nombre négatif par un nombre positif équivalent, appelé le ''complément''. Par équivalent, on veut dire que tout calcul donne le même résultat si on remplace un nombre négatif par son complément (idem avec un nombre positif, son complément aura un signe inverse). Et pour faire cela, elles se basent sur les débordements d'entier pour fonctionner, et plus précisément sur l'arithmétique modulaire abordée plus haut.
Si on fait les calculs avec le complément, les résultats du calcul entraînent un débordement d'entier, qui sera résolu par un '''modulo''' : l'ordinateur ne conserve que les bits de poids faible du résultat, les autres bits sont oubliés. Par exemple, prenons l'addition 15 + 2, 1111 + 0010 en binaire : le résultat ne sera pas 17 (10001), vu qu'on n'a pas assez de bits pour encoder le résultat, mais 1 (0001). Et le résultat après modulo sera identique au résultat qu'on aurait obtenu avec le nombre négatif sans modulo. En clair, c'est la gestion des débordements qui permet de corriger le résultat de manière à ce que l'opération avec le complément donne le même résultat qu'avec le nombre négatif voulu. Ainsi, on peut coder un nombre négatif en utilisant son complément positif.
: Cela ressemble beaucoup à la méthode de soustraction basée sur un complément à 9 pour ceux qui connaissent, sauf que c'est une version binaire qui nous intéresse ici.
Prenons un exemple, qui permettra d'introduire la suite. Encore une fois, on utilise un codage sur 4 bits dont la valeur haute de débordement est de 16. Prenons l'addition de 13 + 3 = 16. Avec l'arithmétique modulaire, 16 est équivalent à 0, ce qui donne : 13 + 3 = 0 ! On peut aussi reformuler en disant que 13 = -3, ou encore que 3 = -13. Dit autrement, 3 est le complément de -13 pour ce codage. Et ne croyez pas que ça marche uniquement dans cet exemple : cela se généralise assez rapidement à tout nombre négatif.
Prenons un nombre N dont un veut calculer le complément à deux K. Dans le cas général, on a :
: <math>N + K = 2^n</math>, vu que <math>2^n = 0</math> en utilisant le modulo.
En réorganisant très légèrement les termes pour isoler K, on a :
: <math>K = 2^n - N</math>
La formule précédent permet de calculer la complément à deux assez simplement, en faisant le calcul à la main.
Avant de poursuivre, prenons un exemple très intéressant : le cas où N = -1. Son complément à deux vaut donc :
: <math>K = 2^n - 1</math>
Le terme <math>2^n - 1</math> devrait vous rappeler quelque chose : il s'agit d'un nombre dont tous les bits sont à 1. En clair, le complément à deux de -1 est un nombre de la forme 1111...111. Et cela vaut quel que soit le nombre de bits n. La représentation de -1 est similaire, peu importe que l'on utilise des nombres de 16 bits, 32 bits, 64 bits, etc.
Voyons maintenant le cas des puissance de deux, par exemple 2, 4, 8, 16, etc. Leur complément à deux vaut :
: <math>K = 2^n - 2^x</math>
Nous avions vu précédemment dans le chapitre que ces nombres sont de la forme 11111...0000. En clair, des nombres dont les x bits de poids faible sont à 0, et tous les autres bits sont à 1. Par exemple, le complément à deux de -2 est un nombre dont les bits sont à 1, sauf le bit de poids faible. De même, le complément à deux de -4 a tous ses bits à 1, sauf les deux bits de poids faible. Et le complément à deux de -8 a tous ses bits à 1, sauf les trois bits de poids faible. Pour résumer, tout nombre de la forme <math>- 2^x</math> a tous ses bits à 1, sauf les x bits de poids faible qui sont à 0.
{|class="wikitable"
|+ Exemple avec des nombres de 8 bits
|-
! -1
| 1111 1111
|-
! -2
| 1111 1110
|-
! -4
| 1111 1100
|-
! -8
| 1111 1000
|-
! -16
| 1111 0000
|-
! -32
| 1110 0000
|-
! -64
| 1100 0000
|-
! -128
| 1000 0000
|-
! 0 / - 256
| 0000 0000
|}
====Le calcul du complément à deux : seconde méthode====
Il existe une seconde méthode pour calculer le complément à deux d'un nombre, la voici. Pour les nombres positifs, encodez-les comme en binaire normale. Pour les nombres négatifs, faites pareil, puis inversez tous les bits, avant d'ajouter un. La procédure est identique à celle du complément à un, sauf que l'on incrémente le résultat final. Et ce n'est pas une coïncidence, comme nous allons le voir immédiatement.
Pour comprendre pourquoi la méthode marche, repartons de la formule précédente <math>K = 2^n - N</math>. Elle peut se reformuler comme suit :
: <math>K = (2^n - 1 + 1) - N</math>
: <math>K = (2^n - 1) - N + 1</math>
La valeur <math>(2^n - 1)</math> est par définition un nombre dont tous les bits sont à 1. À cette valeur, on soustrait un nombre <math>|N|</math> dont certains bits sont à 1 et d'autres à 0. En clair, pour chaque colonne, on a deux possibilités : soit on doit faire la soustraction <math>1-1</math>, soit la soustraction <math>1-0</math>. Or, les règles de l'arithmétique binaire disent que <math>1-1=0</math> et <math>1-0=1</math>. En regardant attentivement, on se rend compte que le bit du résultat est l'inverse du bit de départ. De plus, les deux cas ne donnent pas de retenue : le calcul pour chaque bit n'influence pas les bits voisins.
Le terme <math>(2^n - 1) - N</math> est donc le complément à un du nombre N, un nombre égal à sa représentation en binaire dont tous les bits sont inversés. Notons <math>\overline{|N|}</math> le nombre formé en inversant tous les bits de N. On a alors :
: <math>K = \overline{N} + 1</math>
En clair, le complément à deux s'obtient en prenant le complément à un et en ajoutant 1. Dit autrement, il faut prendre le nombre N, en inverser tous les bits et ajouter 1.
Une autre manière équivalente consiste à faire le calcul suivant :
: <math>K = \overline{N - 1}</math>
On prend le nombre dont on veut le complément, on soustrait 1 et on inverse les bits.
: Notons que tout ce qui a été dit plus haut marche aussi pour le complément à un, avec cependant une petite différence : la valeur haute de débordement n'est pas la même, ce qui change les calculs. Pour des nombres codés sur <math>n</math> bits, la valeur haute de débordement est égale à <math>2^n</math> en complément à deux, alors qu'elle est de <math>2^n - 1</math> en complément à un. De ce fait, la gestion des débordements est plus simple en complément à deux.
====L'extension de signe====
Dans les ordinateurs, tous les nombres sont codés sur un nombre fixé et constant de bits. Ainsi, les circuits d'un ordinateur ne peuvent manipuler que des nombres de 4, 8, 12, 16, 32, 48, 64 bits, suivant la machine. Si l'on veut utiliser un entier codé sur 16 bits et que l'ordinateur ne peut manipuler que des nombres de 32 bits, il faut bien trouver un moyen de convertir le nombre de 16 bits en un nombre de 32 bits, sans changer sa valeur et en conservant son signe. Cette conversion d'un entier en un entier plus grand, qui conserve valeur et signe s'appelle l''''extension de signe'''.
L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
L'explication plus simple tient dans la manière de coder le bit de poids fort. Prenons l'exemple de la conversion d'un entier de 5 bits en un entier de 8bits. Les 4 bits de poids faible ont un poids positif (on les additionne), alors que le bit de poids fort a un poids négatif. Le nombre encodé vaut : <math>\text{Bit de poids fort} \times -16 + \text{Valeur encodée sur les 4 bits de poids faible}</math>. La valeur encodée sur 4 bits reste la même après extension de signe, car les poids des bits de poids faible ne changent pas. Par contre, le bit de poids fort change. Sur 8 bits, la valeur -16 est encodée par un nombre de la forme 1111 0000. En remplaçant le bit de poids fort par sa valeur calculée sur plus de bits, on remarque que les bits de poids fort ont été remplacés par des 1.
===La représentation négabinaire===
Enfin, il existe une dernière méthode, assez simple à comprendre, appelée '''représentation négabinaire'''. Dans cette méthode, les nombres sont codés non en base 2, mais en base -2. Oui, vous avez bien lu : la base est un nombre négatif. Dans les faits, la base -2 est similaire à la base 2 : il y a toujours deux chiffres (0 et 1), et la position dans un chiffre indique toujours par quelle puissance de 2 il faut multiplier, sauf qu'il faudra ajouter un signe moins une fois sur 2. Concrètement, les puissances de -2 sont les suivantes : 1, -2, 4, -8, 16, -32, 64, etc. En effet, un nombre négatif multiplié par un nombre négatif donne un nombre positif, ce qui fait qu'une puissance sur deux est négative, alors que les autres sont positives. Ainsi, on peut représenter des nombres négatifs, mais aussi des nombres positifs dans une puissance négative.
Par exemple, la valeur du nombre noté 11011 en base -2 s'obtient comme suit :
{|class="wikitable"
|-
!…
!-32
!16
!-8
!4
!-2
!1
|-
|…
|1
|1
|1
|0
|1
|1
|}
Sa valeur est ainsi de (−32×1)+(16×1)+(−8×1)+(4×0)+(−2×1)+(1×1)=−32+16−8−2+1=−25.
==Les nombres à virgule fixe==
On sait donc comment sont stockés nos nombres entiers dans un ordinateur. Néanmoins, les nombres entiers ne sont pas les seuls nombres que l'on utilise au quotidien : il nous arrive d'en utiliser à virgule. Notre ordinateur n'est pas en reste : il est lui aussi capable de manipuler de tels nombres. Dans les grandes lignes, il peut utiliser deux méthodes pour coder des nombres à virgule en binaire : La virgule fixe et la virgule flottante. Parlons d'abord de la virgule fixe, plus simple à comprendre.
===La virgule fixe : des entiers cachés===
La méthode de la virgule fixe consiste à émuler les nombres à virgule à partir de nombres entiers. Un nombre à virgule fixe est codé par un nombre entier proportionnel au nombre à virgule fixe. Pour obtenir la valeur de notre nombre à virgule fixe, il suffit de diviser l'entier servant à le représenter par le facteur de proportionnalité. Par exemple, pour coder 1,23 en virgule fixe, on peut choisir comme « facteur de conversion » 1000, ce qui donne l'entier 1230.
Généralement, les informaticiens utilisent une puissance de deux comme facteur de conversion, pour simplifier les calculs. En faisant cela, on peut écrire les nombres en binaire et les traduire en décimal facilement. Pour l'exemple, cela permet d'écrire des nombres à virgule en binaire comme ceci : 1011101,1011001. Et ces nombres peuvent se traduire en décimal avec la même méthode que des nombres entier, modulo une petite différence. Comme pour les chiffres situés à gauche de la virgule, chaque bit situé à droite de la virgule doit être multiplié par la puissance de deux adéquate. La différence, c'est que les chiffres situés à droite de la virgule sont multipliés par une puissance négative de deux, c'est à dire par <math>1 \over 2</math>, <math>1 \over 4</math>, <math>1 \over 8</math>, <math>1 \over 16</math>, <math>1 \over 32</math>, ...
[[File:Fixed-point binary representation.svg|centre|vignette|upright=2.5|Nombres à virgule fixe.]]
Cette méthode est assez peu utilisée de nos jours, quoiqu'elle puisse avoir quelques rares applications relativement connue. Un bon exemple est celui des banques : les sommes d'argent déposées sur les comptes ou transférées sont codés en virgule fixe. Les sommes manipulées par les ordinateurs ne sont pas exprimées en euros, mais en centimes d'euros. Et c'est une forme de codage en virgule fixe dont le facteur de conversion est égal à 100. La raison de ce choix est que les autres méthodes de codage des nombres à virgule peuvent donner des résultats imprécis : il se peut que les résultats doivent être tronqués ou arrondis, suivant les opérandes. Cela n'arrive jamais en virgule fixe, du moins quand on se limite aux additions et soustractions.
===Les nombres à virgule non-représentables en binaire===
Quel que soit la façon de représenter les nombres à virgules, il existe des nombres qui ne peuvent être représentés de manière exacte, à savoir avec un nombre fini de décimales après la virgule. En soi, ce n'est pas spécifique au binaire, on a la même chose en décimal. Par exemple, la fraction 1/3 en décimal s'écrit 0.3333333..., avec une infinité de 3. La même chose existe en binaire, mais pour des nombres différents.
Déjà, évacuons le cas des nombres irrationnels, à savoir les nombres qui ne peuvent pas s'écrire sous la forme d'une fraction, comme <math>pi</math> ou <math>e</math>. Ils ont une infinité de décimales que ce soit en binaire, en décimal, en hexadécimal, ou autre. Ils ne sont pas représentables avec un nombre fini de décimales quelle que soit la base utilisée. Concentrons-nous sur des nombres qui ne sont pas dans ce cas, et qui ont un nombre fini ou infini de décimales.
Le passage de la base 10 à la base 2 change le nombre de décimales, et peut faire passer d'un nombre fini de décimales à un nombre infini. Par exemple, <math>\frac{1}{5} = 0.2</math> est représenté en binaire avec une séquence infinie de bits : <math>0.001100110011...</math>. Les nombres étant en base binaire et représenté avec un nombre limité de bits, il existe certains nombres décimaux triviaux qui ne sont pas représentables avec un nombre fini de décimales.
Et la réciproque n'est pas vraie : tout nombre binaire avec un nombre fini de décimales en binaire est représentable avec un nombre fini de décimales en base 10. Ceci est lié à la décomposition en facteur premier des bases utilisées :
* <math>2 = 2^1</math>
* <math>10 = 2^1 \times 5^1</math>
Le nombre 10 possède tous les facteurs premiers de 2, mais 2 n'a pas de 5 dans sa décomposition.
==Les nombres flottants==
Les nombres à virgule fixe ont aujourd'hui été remplacés par les '''nombres à virgule flottante''', où le nombre de chiffres après la virgule est variable. Le codage d'un nombre flottant est basée sur son ''écriture scientifique''. Pour rappel, en décimal, l’écriture scientifique d'un nombre consiste à écrire celui-ci comme un produit entre un nombre <math>a</math> et une puissance de 10, avec <math>a</math> étant compris entre 1 et 9. Ce qui donne :
: <math>a \times 10^e</math>, avec <math>a \in [1, 10 [</math>
Le nombre <math>a</math> est appelé le ''significande'' et il est compris entre 1 (inclus) et 10 (exclu). Cette contrainte garantit que l'écriture scientifique d'un nombre est unique, qu'il n'y a qu'une seule façon d'écrire un nombre en notation scientifique. Pour cela, on impose le nombre de chiffre à gauche de la virgule et le plus simple est que celui-ci soit égal à 1. Mais il faut aussi que celui-ci ne soit pas nul. En effet, si on autorise de mettre un 0 à gauche de la virgule, il y a plusieurs manières équivalentes d'écrire un nombre. Ces deux contraintes font que le significande doit être égal ou plus grand que 1, mais strictement inférieur à 10. Par contre, on peut mettre autant de décimales que l'on veut.
En binaire, c'est la même chose, mais avec une puissance de deux. Cela implique de modifier la puissance utilisée : au lieu d'utiliser une puissance de 10, on utilise une puissance de 2.
: <math>a \times 2^e</math>, avec <math>a \in [1, 2 [</math>
Le significande est aussi altéré, au même titre que la puissance, même si les contraintes sont similaires à celles en base 10. En effet, le nombre <math>a</math> ne possède toujours qu'un seul chiffre à gauche de la virgule, comme en base 10. Vu que seuls deux chiffres sont possibles (0 et 1) en binaire, on s'attend à ce que le chiffre situé à gauche de la virgule soit un zéro ou un 1. Mais rappelons que le chiffre à gauche doit être non-nul, pour les mêmes raisons qu'en décimal. En clair, le significande a forcément un bit à 1 à gauche de la virgule. Pour récapituler, l'écriture scientifique binaire d'un nombre consiste à écrire celui-ci sous la forme :
: <math>1,m \times 2^e</math>, avec <math>m \in [0, 1 [</math>
La partie fractionnaire du nombre <math>a</math>, qu'on appelle la '''mantisse'''.
[[File:Mantissa.svg|centre|vignette|Écriture scientifique (anglais).]]
Pour déterminer l'écriture scientifique en binaire d'un nombre quelconque, la procédure dépend de la valeur du nombre en question. Tout dépend s'il est dans l'intervalle <math>[1, 2 [</math>, au-delà de 2 ou en-dessous de 1.
* Pour un nombre entre 1 (inclus) et 2 (exclu), il suffit de le traduire en binaire. Son exposant est 0.
* Pour un nombre au-delà de 2, il faut le diviser par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant est alors le nombre de fois qu'il a fallu diviser par 2.
* Pour un nombre plus petit que 1, il faut le multiplier par 2 autant de fois qu'il faut pour qu'il rentre dans l’intervalle <math>a \in [1, 2 [</math>. L'exposant se calcule en prenant le nombre de fois qu'il a fallu multiplier par 2, et en prenant l'opposé (en mettant un signe - devant le résultat).
===Le codage des nombres flottants et la norme IEEE 754===
La '''virgule flottante''' encode l'écriture scientifique d'un nombre/ Elle utilise pour cela trois données : la mantisse, l'exposant et un bit de signe. Les trois sont concaténés dans un seul "nombre", plus précisément une suite de bits qui est découpée en un bit de signe, un entier qui encode l'exposant, et un entier pour la mantisse.
[[File:General floating point frac.svg|centre|vignette|upright=2|Format Général d'un nombre flottant.]]
[[File:General floating point.svg|centre|vignette|upright=2|Format Général d'un nombre flottant, plus détaillé.]]
Voici à peu-près comment les nombres flottants sont répartis sur une ligne numérique. Vous remarquerez que les flottants sont de plus en plus éloignés au fur et à mesure qu'on s'éloigne de zéro. La raison est l'encodage avec un exposant et une mantisse. L'écart entre deux flottants reste stable pour un même exposant, mais il y a une cassure quand on passe d'un exposant au suivant.
[[File:FloatingPointPrecisionAugmented.png|centre|vignette|upright=3|Floating Point Precision.]]
Le standard actuel pour la virgule flottante est la '''norme IEEE 754'''. Cette norme va (entre autres) définit plusieurs formats de flottants différents, qui utilisent des exposants/mantisses de taille différente. Les deux formats les plus courants sont les flottants '''simple précision''' et '''double précision'''. Les premiers sont codés sur 32 bits, les seconds sur 64 bits. Il y a aussi des formats de '''précision étendus''', codés sur 80 bits ou plus.
[[File:Single double extended2.gif|centre|vignette|upright=2.5|Formats des nombres flottants IEEE754.]]
Mais il existe d'autres formats de flottants, qui suivent des normes différentes. Par exemple, il existe des '''flottants demi-précision''' codés sur 16 bits. Ils sont très utilisés dans le domaine du rendu 3D, dans l'intelligence artificielle, et quelques autres domaines. La norme IEEE754 décrit des flottants demi-précision avec un exposant codé sur 5 bits et une mantisse codée sur 10 bits. Il existe aussi le format '''bfloat16 ''', qui utilise un exposant sur 8 bits et une mantisse sur 7 bits.
[[File:IEEE 754r Half Floating Point Format.svg|centre|vignette|upright=1.5|Flottant demi-precision IEEE754.]]
[[File:Bfloat16 format.svg|centre|vignette|upright=1.5|Flottant bfloat16.]]
Il existe aussi des '''minifloats''', codés sur 8 ou 4 bits. Ils sont surtout utilisés pour les tâches d'intelligence artificielle, mais assez peu ailleurs.
Les '''nombres flottants logarithmiques''' sont des nombres flottants composés d'un bit de signe et d'un exposant, sans mantisse. La mantisse est totalement implicite : tous les flottants logarithmiques ont la même mantisse, qui vaut 1. Il ne reste que l'exposant, qui est tout simplement le logarithme en base 2 du nombre encodé, d'où le nom de codage flottant logarithmique donné à cette méthode. Attention toutefois : l'exposant est ici un nombre fractionnaire, codé en virgule fixe. Le choix d'un exposant fractionnaire permet de représenter pas mal de nombres de taille diverses.
{|class="wikitable"
|-
!
! Bit de signe
! Exposant
|-
! Représentation binaire
| 0
| 01110010101111
|-
! Représentation décimale
| +
| 1040,13245464
|}
L'utilité de cette représentation est de simplifier certains calculs, comme les multiplications, divisions, puissances, etc. En effet, les mathématiques nous disent que le logarithme d'un produit est égal à la somme des logarithmes : <math>\ln{(a \times b)} = \ln{a} + \ln{b}</math>. Or, il se trouve que les ordinateurs sont plus rapides pour faire des additions/soustractions que pour faire des multiplications/divisions. Donc, la représentation logarithmique permet de remplacer les multiplications/divisions par des additions/soustractions, plus simples et plus rapides pour l'ordinateur. Évidemment, les applications des flottants logarithmiques sont rares, limitées à quelques situations bien précises (traitement d'image, calcul scientifique spécifiques).
===L'encodage de la mantisse et de l'exposant===
L'exposant peut être aussi bien positif que négatif (pour permettre de coder des nombres très petits). Pour cela, il est encodé en représentation par excès. Pour un exposant de n bits, le biais est égal à <math>2^{n-1} - 1</math>.
L'encodage de la mantisse a lui aussi ses subtilités. En effet, la mantisse est un nombre à virgule, avec une partie entière et une partie fractionnaire. Le tout est encodé en virgule fixe, reste à choisir son encodage exact. Pour simplifier l'encodage, l'exposant est choisir de manière à ce qu'il y ait un seul chiffre avant la virgule, ce qui permet de coder la partie entière sur 1 bit. En clair, il faut encoder le bit à gauche de la virgule, puis ceux à droite. Les anciens PC utilisaient cette solution, du temps de la FPU x87. Ils géraient nativement des nombres flottants de 80 bits, appelés '''flottants étendus x87'''. Ils utilisaient une mantisse codée sur 64 bits, dont un bit pour la partie entière, et 63 pour la partie fractionnaire.
[[File:X86 Extended Floating Point Format.svg|centre|vignette|upright=2|Flottants étendus des anciens CPU X86.]]
Une simplification plus importante choisir l'exposant de manière à ce que la partie entière vaut 1, systématiquement. En faisant cela, on n'a plus à encoder la partie entière, pas besoin de lui réserver un bit. La mantisse encode donc uniquement la partie fractionnaire, la partie entière n'est pas encodée. Une telle méthode est donc plus efficace. A quelques détails près : de nombreuses valeurs ne peuvent pas être encodées facilement. Par exemple, le zéro ne peut pas être encodé avec cette technique. Et c'est là un des gros défaut de cette technique : ne pas pouvoir encoder le zéro est un énorme problème !
Pour éviter ce défaut, les nombres flottants utilisent une technique : la partie entière n'est pas codée directement. La partie entière vaut 0 pour certains flottants, elle vaut 1 pour d'autres. Vu que la partie entière fait un bit, mais n'est pas encodée, elle est aussi appelé le '''bit implicite'''. Déterminer le bit implicite demande de regarder l'exposant . Typiquement, si l'exposant vaut zéro, alors la partie entière vaut 0. Sinon, elle vaut 1.
Une telle solution permet d'encoder un zéro, mais aussi des nombres flottants très petits. Ces derniers ne sont pas codés en respectant les règles pour le significande et ont un 0 à gauche de la virgule. Ils sont appelés les '''dénormaux''', aussi nommés flottants dénormalisés. Les dénormaux sont des nombres flottants où l'exposant est le plus petit possible. Le '''zéro''' est un dénormal particulier dont la mantisse est nulle. Au fait, remarquez que le zéro est codé deux fois à cause du bit de signe : on se retrouve avec un -0 et un +0.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur minimale (0 en binaire) || Mantisse différente de zéro (dénormal) ou égale à zéro (zéro)
|}
===Les spécificités de la norme IEEE754===
Les nombres flottants IEE754 ont quelques spécificités qu'il vaut mieux voir à part. Premièrement, ils n'encodent pas la partie entière d'un flottant, la mantisse encode uniquement la partie fractionnaire. La technique précédente, qui utilise l'exposant pour déterminer le bit implicite, est utilisée. Les flottants IEE754 ont une partie entière de la mantisse qui vaut 1, sauf pour quelques rares dénormaux qui font exceptions. Les dénormaux sont des nombres flottants où l'exposant est le plus petit possible.
Le '''zéro''' est un dénormal particulier dont la mantisse est nulle. Au fait, remarquez que le zéro est codé deux fois à cause du bit de signe : on se retrouve avec un -0 et un +0.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur minimale (0 en binaire) || Mantisse différente de zéro (dénormal) ou égale à zéro (zéro)
|}
Fait étrange, la norme IEEE754 permet de représenter '''l'infini''', aussi bien en positif qu'en négatif. Celui-ci est codé en mettant l'exposant à sa valeur maximale et la mantisse à zéro. Et le pire, c'est qu'on peut effectuer des calculs sur ces flottants infinis. Mais cela a peu d'utilité.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse égale à zéro
|}
Mais malheureusement, l'invention des flottants infinis n'a pas réglé tous les problèmes. Par exemple, quel est le résultat de <math>\infty - \infty</math> ? Ou encore <math>0 - \infty</math> ? Autant prévenir tout de suite : mathématiquement, on ne peut pas savoir quel est le résultat de ces opérations. Pour pouvoir résoudre ces calculs, il a fallu inventer un nombre flottant qui signifie « je ne sais pas quel est le résultat de ton calcul pourri ». Ce nombre, c'est '''NaN'''.
NaN est l'abréviation de ''Not A Number'', ce qui signifie : n'est pas un nombre. Un NaN a un exposant dont la valeur est maximale, mais une mantisse différente de zéro. Pour être plus précis, il existe différents types de NaN, qui diffèrent par la valeur de leur mantisse, ainsi que par les effets qu'ils peuvent avoir. Malgré son nom explicite, on peut faire des opérations avec NaN, mais cela ne sert pas vraiment à grand chose : une opération arithmétique appliquée avec un NaN aura un résultat toujours égal à NaN.
{|class="wikitable"
|-
! scope="col"| Bit de signe || Exposant || Mantisse
|-
| 0 ou 1 || Valeur maximale || Mantisse différente de zéro
|}
Au-delà du support de quelques flottants spéciaux, la norme impose une gestion des arrondis ou erreurs, qui arrivent lors de calculs particuliers. En voici la liste :
{|class="wikitable"
|-
! Nom de l’exception
! Description
|-
! Invalid operation
| Opération qui produit un NAN. Elle est levée dans le cas de calculs ayant un résultat qui est un nombre complexe, ou quand le calcul est une forme indéterminée. Pour ceux qui ne savent pas ce que sont les formes indéterminées, voici en exclusivité la liste des calculs qui retournent NaN : <math>\infty - \infty</math>, <math>0 \times \infty</math>, <math>0^0</math>, <math>1^{\infty}</math>, <math>\infty^0</math>.
|-
! Overflow
| Résultat trop grand pour être stocké dans un flottant. Le plus souvent, on traite l'erreur en arrondissant le résultat en vue de la taille de la mantisse;
|-
! Underflow
| Pareil que le précédent, mais avec un résultat trop petit. Le plus souvent, on traite l'erreur en arrondissant le résultat vers 0.
|-
! Division par zéro
| Le nom parle de lui-même. La réponse la plus courante est de répondre + ou - l'infini.
|-
! '''Inexact'''
| Le résultat ne peut être représenté par un flottant et on doit l'arrondir.
|}
La dernière exception, ''Inexact'', survient quand le résultat d'une opération flottante donne un résultat qui ne peut être codé par un flottant. On est alors obligé d'arrondir ou de tronquer le résultat de façon à le faire rentrer dans un flottant. Pour éviter que des ordinateurs différents utilisent des méthodes d'arrondis différentes, la norme IEEEE754 impose le support de quatre modes d'arrondis :
* Arrondir vers + l'infini ;
* vers - l'infini ;
* vers zéro ;
* vers le nombre flottant le plus proche.
==Les encodages binaires alternatifs==
Outre le binaire que nous venons de voir, il existe d'autres manières de coder des nombres avec des bits. Et nous allons les aborder dans cette section. Parmi celle-ci, nous parlerons du code Gray, de la représentation ''one hot'', du unaire et du ternaire. Nous en parlons car elles seront utiles dans la suite du cours, bien que de manière assez limitée. Autant nous passerons notre temps à parler du binaire normal, autant les représentations que nous allons voir sont aujourd'hui utilisées dans des cas assez spécifiques. Et elles sont plus courantes que vous ne le pensez.
===Le code Gray===
Le '''code Gray''' est un encodage binaire qui a une particularité très intéressante : deux nombres consécutifs n'ont qu'un seul bit de différence. Pour exemple, voici ce que donne le codage des 8 premiers entiers sur 3 bits :
{|class="wikitable"
! Décimal
! Binaire naturel
! Codage Gray
|-
|0||000||000
|-
|1||001||001
|-
|2||010||011
|-
|3||011||010
|-
|4||100||110
|-
|5||101||111
|-
|6||110||101
|-
|7||111||100
|}
Les utilisations du code Gray sont assez nombreuses, bien qu'on n'en croise pas tous les jours. Un exemple : le code Gray est très utile dans certains circuits appelés les compteurs, qui mémorisent un nombre et l'incrémentent (+1) ou le décrémentent (-1) suivant les besoins de l'utilisateur. Il est aussi utilisé dans des scénarios difficiles à expliquer ici (des codes correcteurs d'erreur ou des histoires de passages de domaines d'horloge). Mais le point important est que ce code sera absolument nécessaire dans quelques chapitres, quand nous parlerons des tables de Karnaugh, un concept important pour la conception de circuits électroniques. Ne passez pas à côté de cette section.
Pour construire ce code Gray, on peut procéder en suivant plusieurs méthodes, les deux plus connues étant la méthode du miroir et la méthode de l'inversion.
[[File:Binary-reflected Gray code construction.svg|vignette|Construction d'un code gray par la méthode du miroir.]]
La méthode du miroir est relativement simple. Pour connaître le code Gray des nombres codés sur n bits, il faut :
* partir du code Gray sur n-1 bits ;
* symétriser verticalement les nombres déjà obtenus (comme une réflexion dans un miroir) ;
* rajouter un 0 au début des anciens nombres, et un 1 au début des nouveaux nombres.
Il suffit de connaître le code Gray sur 1 bit pour appliquer la méthode : 0 est codé par le bit 0 et 1 par le bit 1.
Une autre méthode pour construire la suite des nombres en code Gray sur n bits est la méthode de l'inversion. Celle-ci permet de connaître le codage du nombre n à partir du codage du nombre n-1, comme la méthode du dessus. On part du nombre 0, systématiquement codé avec uniquement des zéros. Par la suite, on décide quel est le bit à inverser pour obtenir le nombre suivant, avec la règle suivante :
* si le nombre de 1 est pair, il faut inverser le dernier chiffre.
* si le nombre de 1 est impair, il faut localiser le 1 le plus à droite et inverser le chiffre situé à sa gauche.
Pour vous entraîner, essayez par vous-même avec 2, 3, voire 5.
===Les représentations ''one-hot'' et unaire===
La représentation ''one-hot'' et la représentation unaire sont deux représentations assez liées, mais légèrement différentes.
La '''représentation unaire''' est la représentation en base 1. Avec elle, le zéro est codé en mettant tous les bits du nombre à zéro, la valeur 1 est encodée avec la valeur 000...0001, le deux avec 000...0011, le trois avec 000...0111, etc. Pour résumer, le nombre N est encodé en mettant les N bits de poids faible à 1, à l'exception du zéro qui est encodé...par un zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! Unaire
|-
| 0 || 000 || 0000 0000
|-
| 1 || 001 || 0000 0001
|-
| 2 || 010 || 0000 0011
|-
| 3 || 011 || 0000 0111
|-
| 4 || 100 || 0000 1111
|-
| 5 || 101 || 0001 1111
|-
| 6 || 110 || 0011 1111
|-
| 7 || 111 || 0111 1111
|-
| 8 || 1000 || 1111 1111
|}
Pour convertir un nombre codé en unaire vers le binaire, il suffit de compter le nombre de 1. C'est ni plus ni moins ce que fait l'opération dite de ''population count'' dont nous avons parlé plus haut. La représentation unaire permet d'encoder N+1 valeurs sur N bits, car le zéro est encodé à part. Une valeur pour le zéro, plus une par bit.
Avec la '''représentation ''one-hot''''', un nombre est codé sur N bits, dont un seul bit est à 1. La position du 1 dans le nombre indique sa valeur. Précisément, la valeur encodée est égale au poids du bit à 1. En faisant cela, le zéro est codé en mettant le bit de poids faible à 1, la valeur 1 est codée avec les bits 00...00010, la valeur 2 est codée par les bits 00...000100, etc.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Elle permet d'encoder N valeurs différentes avec N bits, zéro inclus. On perd une valeur par rapport à l'unaire, car le zéro n'et pas encodé à part du reste. La traduction du binaire vers le ''one-hot'' est réalisé par un circuit nommé décodeur, que nous verrons d'ici quelques chapitres. De même, la traduction d'un nombre encodé en ''one-hot'' vers le binaire est réalisée par un circuit électronique appelé l'encodeur, qui est en quelque sorte l'inverse de l'encodeur. Les deux circuits sont très utilisés en électronique, ce sont des circuits de base, que nous n'aurons de cesse d'utiliser pour fabriquer d'autres circuits.
L'utilité de ces deux représentations n'est pas évidente. Mais sachez qu'elle le deviendra quand nous parlerons des circuits appelés les "compteurs", tout comme ce sera le cas pour le code Gray. Elles sont très utilisées dans des circuits appelés des machines à état, qui doivent incorporer des circuits compteurs efficients. Et ces représentations permettent d'avoir des circuits pour compter qui sont très simples, efficaces, rapides et économes en circuits électroniques. La représentation unaire sera aussi utile à la toute fin du cours, dans les chapitres liés à l'exécution dans le désordre. Il en sera fait référence quand nous parlerons de fenêtres d'instruction, d'émission dans l'ordre, de ''scoreboarding'', etc.
Il faut aussi noter qu'il a existé des ordinateur qui manipulaient des nombres codés en unaire. Enfin presque, la représentation utilisée était proche de l'unaire, mais ressemblait fortement. De tels ordinateurs étaient appelés des ''ordinateurs stochastiques'' et nous les aborderons à la toute fin de ce wikilivre, dans un chapitre annexe portant sur les ordinateurs ternaires (qui comptent en base 3) et unaires.
==Les encodages hybrides entre décimal et binaire==
Pour terminer, nous allons voir les représentations qui mixent binaire et décimal. Il s'agit en réalité d'encodage qui permettent de manipuler des nombres codés en décimal, mais les chiffres sont codés en utilisant des bits. L'encodage ''Binary Coded Decimal'' est le plus connu de cette catégorie, mais il y en a quelques autres, qui sont moins connus. De telles représentations étaient très utilisées au début de l'informatique, mais sont aujourd'hui tombées en désuétude. Pour désigner ces encodages, nous parlerons d''''encodages bit-décimaux''' : décimaux pour préciser qu'ils encodent des nombres codés en décimal, bit pour préciser que les chiffres sont codés avec des bits.
De tels encodages étaient utilisés sur les tous premiers ordinateurs pour faciliter le travail des programmeurs, mais aussi sur les premières calculettes. Ils sont utiles dans les applications où on doit manipuler chaque chiffre décimal séparément des autres. Un exemple classique est celui d'une horloge digitale. Ils ont aussi l'avantage de bien se marier avec les nombres à virgule. Les représentations des nombres à virgule fixe ou flottante ont le défaut que des arrondis peuvent survenir. Par exemple, la valeur 0,2 est codée comme suit en binaire normal : 0.00110011001100... et ainsi de suite jusqu’à l'infini. Avec les encodages bit-décimaux, on n'a pas ce problème, 0,2 étant codé 0000 , 0010.
Il a existé des ordinateurs qui travaillaient uniquement avec de tels encodages, appelés des '''ordinateurs décimaux'''. Ils étaient assez courants entre les années 60 et 70, même s'ils ne représentent pas la majorité des architectures de l'époque. Avec eux, la mémoire n'était pas organisée en octets, mais elle stockait des chiffres décimaux codés sur 5-6 bits. Le processeur faisait des calculs sur des chiffres bit-décimaux directement.
Leur grand avantage est leur très bonne performance dans les taches de bureautique, de comptabilité, et autres. Les processeurs de l'époque recevaient des entiers codés en bit-décimal de la part des entrées-sorties, et devaient les traiter. Les processeurs binaires devaient faire des conversions décimal-binaire pour communiquer avec les entrées-sorties, mais pas les processeurs décimaux. Le gain en performance pouvait être substantiel dans certaines applications.
Les ordinateurs décimaux se classent en deux sous-types bien précis. Les premiers gèrent des entiers qui ont un nombre de chiffres fixes. Par exemple, l'IBM 7070 gérait des entiers de 10 chiffres, plus un signe +/- pour les entiers signés. Le processeur faisait les calculs directement en bit-décimal, il gérait des entiers faisant environ 10-15 chiffres décimaux et savait faire des calculs avec de tels nombres. Le second sous-type effectue les calculs chiffre par chiffre et géraient des nombres de taille variable, sans limite de chiffres ! Nous reparlerons de ces derniers dans un chapitre ultérieur, quand nous parlerons de la différence entre byte et mot. Pour le moment, ne gardez à l'esprit que les processeurs gérant un nombre fixe de chiffres décimaux, plus simples à comprendre.
===Le ''Binary Coded Decimal''===
Le '''''Binary Coded Decimal''''', abrévié BCD, est une représentation qui mixe binaire et décimal. Avec cette représentation, les nombres sont écrits en décimal, comme nous en avons l'habitude dans la vie courante, sauf que chaque chiffre décimal est directement traduit en binaire sur 4 bits. Prenons l'exemple du nombre 624 : le 6, le 2 et le 4 sont codés en binaire séparément, ce qui donne 0110 0010 0100.
{| class="wikitable"
|+ Codage BCD
|-
! Nombre encodé (décimal)
! scope="col" colspan="4" | BCD
|-
| 0 || 0 || 0 || 0 || 0
|-
| 1 || 0 || 0 || 0 || 1
|-
| 2 || 0 || 0 || 1 || 0
|-
| 3 || 0 || 0 || 1 || 1
|-
| 4 || 0 || 1 || 0 || 0
|-
| 5 || 0 || 1 || 0 || 1
|-
| 6 || 0 || 1 || 1 || 0
|-
| 7 || 0 || 1 || 1 || 1
|-
| 8 || 1 || 0 || 0 || 0
|-
| 9 || 1 || 0 || 0 || 1
|}
On peut remarquer que 4 bits permettent de coder 16 valeurs, là où il n'y a que 10 chiffres. Dans le BCD proprement dit, les combinaisons de bits qui correspondent à 10, 11, 12, 13, 14 ou 15 ne sont tout simplement pas prises en compte. Sur quelques ordinateurs, ces combinaisons codent des chiffres décimaux en double : certains chiffres pouvaient être codés de deux manières différentes. Il est aussi possible d'utiliser ces valeurs pour coder quelque chose d'autre que des chiffres. Par exemple, il est possible de les utiliser pour coder un signe + ou -, afin de gérer les entiers relatifs. Une autre possibilité, complémentaire de la précédente, utilise ces valeurs en trop pour coder une virgule, afin de gérer les nombres non-entiers. Les possibilités sont nombreuses.
Le support du BCD implique souvent que le processeur supporte des '''opérations BCD''', à savoir des opérations capables de travailler sur des opérandes en BCD et de donner un résultat en BCD. Il faut bien faire la différence entre les opérations en binaire et les opérations en BCD. Par exemple, on n'effectue pas une addition de la même manière en binaire et en décimal/BCD, même si les grandes lignes sont presque identiques. Les différences font que les processeurs doivent avoir des opérations différentes pour les deux encodages, de la même manière que les processeurs gèrent les opérations sur les flottants et les entiers séparément.
Le support du codage BCD est abandonné de nos jours, c'est surtout quelque chose qu'on trouve sur les anciens processeurs. Les architectures 8 et 16 bits supportaient à la fois des opérations binaires et des opérations BCD. Mais il a aussi existé une méthode intermédiaire, qui utilisait des additions binaires normales sur des opérandes BCD. L'idée est que le résultat de l'addition est incorrect, car les valeurs 10 à 15 peuvent apparaître comme chiffre dans le résultat. Mais on peut le corriger pour obtenir le résultat exact en BCD. Pour résumé, les processeurs faisaient des additions en binaire, et corrigeaient le résultat avec une opération spécifique pour obtenir un résultat en BCD. Nous en reparlerons dans le chapitre sur le langage machine et l'assembleur, dans lequel nous étudierons les différentes opérations que supporte un processeur.
===Les encodages compacts du BCD===
Des variantes du BCD visent à réduire le nombre de bits utilisés pour encoder un nombre décimal. Nous allons les appeler les '''encodages BCD compacts'''. Avec certaines variantes, on peut utiliser seulement 7 bits pour coder deux chiffres décimaux, au lieu de 8 bits en BCD normal. Idem pour les nombres à 3 chiffres décimaux, qui prennent 10 bits au lieu de 12. Cette économie est réalisée par une variante du BCD assez compliquée, appelée l''''encodage Chen-Ho'''. Une alternative, appelée le ''Densely Packed Decimal'', arrive à une compression identique, mais avec quelques avantages au niveau de l'encodage. Ces encodages sont cependant assez compliqués à expliquer, surtout à ce niveau du cours, aussi je me contente de simplement mentionner leur existence.
Les encodages BCD compacts sont utilisés par les programmeurs pour stocker des données dans un fichier, en mémoire, mais guère plus. Ils ne sont pas gérés par le processeur directement, on ne peut pas faire de calculs avec, le processeur ne gére pas d'opération BCD supportant de tels encodages. C'est théoriquement possible, mais ça n'a jamais été implémenté dans un processeur, le cout en circuit n'en valait pas la chandelle. Pour faire des calculs sur des nombres en BCD compact, on doit les décompresser et les convertir en BCD normal ou en binaire, puis faire les calculs avec des opérations BCD/binaire usuelles.
===L'encodage ''Excess-3''===
La représentation '''''Excess-3''''' (XS-3) est une variante du BCD, qui a autrefois était utilisée sur d'anciens ordinateurs décimaux. Il s'agit d'une sorte d'hybride entre une représentation par excès et BCD. Chaque chiffre décimal est codé sur plusieurs bits en utilisant une représentation binaire par excès. Le biais est de 3 pour la représentation XS-3, il en existe des variantes avec un excès de 4, 5, mais elles sont moins utilisées. Avec elle, la conversion d'un chiffre décimal se fait comme suit : on prend le chiffre décimal, on ajoute 3, puis on traduit en binaire.
{| class="wikitable"
|+ Codage en ''Excess-3''
|-
! Décimal
! scope="col" colspan="4" | Binaire
|-
| -3 || 0 || 0 || 0 || 0
|-
| -2 || 0 || 0 || 0 || 1
|-
| -1 || 0 || 0 || 1 || 0
|-
| 0 || 0 || 0 || 1 || 1
|-
| 1 || 0 || 1 || 0 || 0
|-
| 2 || 0 || 1 || 0 || 1
|-
| 3 || 0 || 1 || 1 || 0
|-
| 4 || 0 || 1 || 1 || 1
|-
| 5 || 1 || 0 || 0 || 0
|-
| 6 || 1 || 0 || 0 || 1
|-
| 7 || 1 || 0 || 1 || 0
|-
| 8 || 1 || 0 || 1 || 1
|-
| 9 || 1 || 1 || 0 || 0
|-
| 10 || 1 || 1 || 0 || 1
|-
| 11 || 1 || 1 || 1 || 0
|-
| 12 || 1 || 1 || 1 || 1
|}
L'avantage de cette représentation est que l'on peut facilement calculer les soustractions en utilisant une méthode bien précise (celle du complément à 10, je ne détaille pas plus). Le défaut est que le calcul des additions est légèrement plus complexe.
L'XS-3 a été utilisé sur quelques ordinateurs décimaux assez anciens, notamment sur l'UNIVAC I et II.
===Les codes bi-quinaires===
Les '''codes bi-quinaires''' utilisent un mélange entre encodage binaire, encodage ''one-hot'' et encodage décimal. L'idée de base derrière ces codes est d'utiliser la représentation ''one-hot'' pour coder un chiffre BCD. Le problème est que cela demande d'utiliser 10 bits pour encoder un chiffre BCD, ce qui est beaucoup. L'encodage bi-quinaire optimise le tout de manière à diviser par deux le nombre de bits requis pour l'encodage.
Pour simplifier, un chiffre encodé en bi-quinaire est composé de deux parties : une partie binaire de un bit, suivie par d'une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits pour encoder les valeurs 0, 1, 2, 3 et 4. La partie binaire indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9 : 0 à 4 si la partie binaire vaut 0, 5 à 9 si elle vaut 1. La valeur du chiffre se calcule comme suit :
* si la partie binaire vaut 0, elle est égale à la partie quinaire ;
* si la partie binaire vaut 1, on ajoute 5 à la partie quinaire.
L'originalité de ce codage est qu'il permet de facilement compter sur ses doigts : on a deux mains, cinq doigts. De nombreuses langues utilisent ce système pour coder des nombres, comme le Wolof. Et ce système est utilisé dans certains pays pour compter sur ses doigts, voire dans la vie de tous les jours.
[[File:Code Biquinaer.svg|vignette|Code bi-quinaire où partie binaire et quinaire sont encodées en représentation ''one-hot''.]]
L'encodage abordé au-dessus est une version simplifiée de l'encodage bi-quinaire, il en existe d'autres variantes. Le bi-quinaire vu juste au-dessus encode la partie binaire en binaire, et la partie quinaire en ''one-hot''. Mais il est aussi possible d'inverser l'encodage, à savoir encoder la partie binaire avec une représentation ''one-hot'', alors que la partie bi-quinaire est encodée en binaire normal.
Cependant, la version la plus intéressante utilise l'encodage ''one-hot'' pour les deux ! Elle est illustrée ci-contre. Avec cette dernière, un chiffre BCD est encodé sur 7 bits, dont 2 sont obligatoirement à 1 et les 5 restants sont à 0. Cette propriété permet de détecter qu'un bit a été inversé suite à une erreur de transmission ou un problème de stockage. Si un bit est inversé, le résultat aura 1 bit à 1 et 6 à 0, ou alors 3 bits à 1 et 4 à 0, ce qui ne colle pas. Le résultat après inversion ne donnera pas un chiffre bi-quinaire valide.
Une autre variante du bi-quinaire a été utilisée sur l'IBM 1401. Avec elle, la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre encodé par la partie quinaire. L'avantage de ce système est que la gestion des retenues lors des additions était grandement simplifiée.
Le bi-quinaire et ses variantes ont été utilisés sur d'anciens ordinateurs, dont l'IBM 650, l'UNIVAC Solid State et le UNIVAC LARC. La plupart utilisaient des variantes de l'encodage bi-quinaire proprement dit. Pour rentrer vraiment dans le détail, voici les encodages utilisés sur ces machines. La lecture est facultative, il est possible de passer directement à la section suivante :
{| class="wikitable"
|-
! !! IBM 650 !! Remington Rand 409 !! UNIVAC Solid State !! UNIVAC LARC
|-
| Chiffre || 1357-9 bits || 05-01234 || p-5-421 bits || p-5-qqq bits
|-
| 0 || 10-10000 || 0000-0 || 1-0-000 || 1-0-000
|-
| 1 || 10-01000 || 1000-0 || 0-0-001 || 0-0-001
|-
| 2 || 10-00100 || 1000-1 || 0-0-010 || 1-0-011
|-
| 3 || 10-00010 || 0100-0 || 1-0-011 || 0-0-111
|-
| 4 || 10-00001 || 0100-1 || 0-0-100 || 1-0-110
|-
| 5 || 01-10000 || 0010-0 || 0-1-000 || 0-1-000
|-
| 6 || 01-01000 || 0010-1 || 1-1-001 || 1-1-001
|-
| 7 || 01-00100 || 0001-0 || 1-1-010|| 0-1-011
|-
| 8 || 01-00010 || 0001-1 || 0-1-011 || 1-1-111
|-
| 9 || 01-00001 || 0000-1 || 1-1-100|| 0-1-110
|}
==Annexe : pourquoi l'octet existe==
Nous avons vu plus haut que les nombres encodés en binaire sont découpés en octets, des paquets de 8 bits. Et les octets sont présents partout dans la conception d'un ordinateur. Par exemple, la mémoire RAM d'un ordinateur moderne est organisée en octets. Elle contient des cases mémoire, chacune contenant un octet. Les octets sont numérotés, ce qui permet de sélectionner un octet parmi tous les autres. La capacité de la mémoire est le nombre d'octets qu'elle contient, elle s'exprime en kilo-octets (1024 octets), mébi-octets (1024 kilo-octets), gibi-octets (1024 mébi-octets). Les octets sont donc présents partout dans l'informatique moderne.
{|class="wikitable"
|+ Contenu de la RAM, simplifié
|-
! Numéro
! Octet
|-
! 0
| 0000 1111
|-
! 1
| 0101 0101
|-
! 2
| 0111 0110
|-
! 3
| 0011 1010
|-
! 4
| 1001 1001
|-
! ...
| ...
|-
! 1023
| 0110 1010
|-
! 1024
| 0001 1110
|}
Mais pourquoi est-ce que les nombres encodés en binaire sont souvent découpés en octets ? Pourquoi ne pas utiliser des groupes de 3, 5, 6, 7, 9, 10 bits ? La question est d'autant plus légitime que d'anciens ordinateurs faisaient ainsi. Leurs mémoires étaient découpé en cases mémoire, mais chacune faisait 7 ou 9 bits, rien d'impossible à cela. À vrai dire, l'usage des octets au tout début de l'informatique était assez rare. Il y a eu un basculement progressif vers l'octet pendant les années 80-90. Reste à expliquer pourquoi. Si on devait résumer, les deux raisons principales sont le support du BCD et le support des caractères pour le texte. Voyons d'abord l'encodage des caractères en binaire.
===L'encodage du texte : ASCII et autres encodages===
Pour les caractères, les encodages se sont concentrés sur l'encodage des alphabets occidentaux. Ils demandaient d'encoder au minimum toutes les lettres, en majuscules et minuscules, plus les chiffres, une dizaine de caractères pour la ponctuation. À cela, il faut ajouter des symboles utiles comme %, µ, $, £, #, {} et bien d'autres. Au minimum, cela fait au minimum 52 lettres (26 lettres doublées pour les majuscules) + 10 chiffres + une dizaine de caractères pour la ponctuation : cela fait plus de 72 valeurs à encoder. Pour encoder cela en binaire, le minimum est de 7 bits, ce qui permet d'encoder 128 caractères : les 72 caractères minimum, plus quelques autres. L'encodage ASCII utilisait d'ailleurs 7 bits pour encoder des nombres.
On pourrait croire que c'était une raison suffisante pour utiliser des groupes de 7 bits. Mais il y a quelques problèmes avec cela. Premièrement, les encodages 7 bits pour le texte marchent bien pour gérer la langue anglaise et la plupart des langues occidentales, mais d'autres langues ont besoin d'encoder des caractères locaux. Les 128 caractères de l'ASCII ne sont pas suffisant, ce qui demande de passer de 7 à 8 bits. On retombe donc sur un octet ! D'ailleurs, les encodages ASCII améliorés utilisaient les 127 premières valeurs pour l'ASCII normal, mais les 128 suivantes encodaient d'autres caractères. Les encodages en question varient d'un pays ou d'une langue à l'autre, mais ils sont terriblement importants, ce qui est un argument pour l'usage des octets.
===Le support des données encodées sur un ''nibble''===
Une autre raison pour l'usage des octets est qu'il est facile de les découper en deux ''nibbles''. Pour rappel, les ''nibbles'' sont des groupes de 4 bits. Et il se trouve que de nombreuses données sont encodées sur 4 bits, ni plus ni moins. Les ''nibbles'' sont très important pour le support du BCD et de l’hexadécimal. Et c'était un avantage important au début de l'informatique, moins maintenant. Un octet regroupe deux ''nibbles'' : un ''nibble'' de poids faible, un ''nibble'' de poids fort.
[[File:Nibbles in a byte.PNG|centre|vignette|upright=2|Nibbles dans un octet.]]
Un chiffre BCD occupe un ''nibble'', ce qui fait que la taille idéale de l'unité mémoire doit être un multiple de 4 bits : 4 bits, 8 bits, 12 bits, 16 bits, etc. Un ''nibble'' de 4 bits est insuffisant pour encoder des caractères, 8 bits fonctionne parfaitement, pas besoin d'aller plus loin. De plus, l'usage des ''nibbles'' permet de gérer la représentation hexadécimale très facilement. Un chiffre hexadécimal est encodé sur exactement un ''nibble'', un octet regroupe deux chiffres hexadécimaux, deux octets 4 chiffres hexadécimaux, etc. Enfin, 8 bits est une puissance de deux, ce qui simplifie grandement l'encodage des entiers en binaire est est une source de nombreuses simplifications. Les circuits de l'ordinateur sont plus simple à concevoir quand ils manipulent des opérandes de 2, 4, 8, 16, 32, 64, 128 bits.
===L'influence sur la taille des opérandes===
Les anciens ordinateurs étaient conçus pour le calcul scientifique, ou du moins des applications similaires. Ils n'avaient pas besoin de supporter le BCD, ni même de gérer du texte. Ils ne faisaient que des calculs et utilisaient des opérandes flottantes. La taille des opérandes était dictée par des considérations calculatoires. La norme à l'époque était que les calculs scientifiques devaient gérer des opérandes pouvant aller jusqu'à 10 chiffres décimaux. Et cela demande d'utiliser des opérandes entiers de 35 bits, avec un bit en plus pour encoder des opérandes positives ou négatives. Les 36 bits étaient parfois découpés en 6 groupes de 6 bits chacun, au lieu des octets de 8 bits.
Mais ces groupes de 6 bits étaient souvent peu utilisés. Prenons l'exemple des mémoires RAM. Plus haut, j'ai dit qu'une mémoire RAM normale est composée de plusieurs octets, tous numérotés. Intuitivement, vous vous dites que les anciens ''mainframes'' remplaçaient ces octets par des groupes de 6 bits dans la RAM. Mais en réalité, l'équivalent des RAM de l'époque utilisait des groupes de 36 bits. les groupes de 36 bits étaient aussi numérotés, les numéros étant appelés des adresses mémoire. Par contre, les adresses/numéros étaient codés sur 12 ou 18 bits, c'est à dire sur 2 ou 3 groupes de 6 bits. La raison est que ces numéros sont manipulés par le processeur, qui gère des opérandes de 36 bits. L'idée est que le numéro ne prend que la moitié ou un tiers des 36 bits gérés par le processeur.
Avec l'apparition de l'informatique commerciale, les ordinateurs ont du s'adapter aux besoins des entreprises, pour gérer la comptabilité, des applications financières, des documents texte, etc. Et cela demandait de supporter à la fois le BCD et les caractères/lettres. Il a fallu trouver une unité mémoire capable de gérer à la fois nombres entiers, chiffres BCD et caractères. L'octet était parfait pour ça. Les ''mainframes'' sont adaptés en supportant des caractères codés sur 6, 7 ou 8 bits. Mais le support demandait d'utiliser des circuits qui découpaient un groupe de 36 bits en plusieurs caractères ou en plusieurs chiffres BCD. Le tout demandait des circuits de décalage et de masquage assez complexes. Les ordinateurs 8 et 16 bits manipulaient des octets nativement : leur mémoire RAM gérait des octets, le processeur traitait des opérandes de 8, 16 ou 32 bits. Ils n'avaient pas besoin de matériel complexe pour extraire un caractère ou un chiffre BCD d'un octet/doublet/multiplet. C'était un avantage que les anciens ''mainframes'' n'avaient pas.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'encodage des données
| prevText=L'encodage des données
| next=Les codes de détection/correction d'erreur
| nextText=Les codes de détection/correction d'erreur
}}
</noinclude>
m2vfogbgrmxrk48lebj1wxe5dwfahdo
Fonctionnement d'un ordinateur/L'architecture de base d'un ordinateur
0
65780
775630
767483
2026-11-02T14:58:50Z
Mewtow
31375
/* Lecture et écriture : mémoires ROM et RWM */
775630
wikitext
text/x-wiki
Dans les chapitres précédents, nous avons vu comment représenter de l'information, la traiter et la mémoriser avec des circuits. Mais un ordinateur n'est pas qu'un amoncellement de circuits et est organisé d'une manière bien précise. Il est structuré autour de trois circuits principaux :
* un '''processeur''', qui manipule l'information et donne un résultat ;
* une '''mémoire''' qui mémorise les données à manipuler ;
* les '''entrées/sorties''', qui permettent à l'ordinateur de communiquer avec l'extérieur.
[[File:Architecture Von Neumann.png|centre|vignette|upright=2|Architecture d'un système à mémoire.]]
Pour faire simple, le processeur est un circuit qui s'occupe de faire des calculs. Rien d'étonnant à cela. Je rappelle que tout est codé par des nombres dans un ordinateur, ce qui fait que manipuler des nombres revient simplement à faire des calculs. Un ordinateur n'est donc qu'une grosse calculatrice améliorée, et le processeur est le composant qui fait les calculs.
La mémoire s'occupe purement de la mémorisation des données, des nombres sur lesquelles faire des calculs. Pour être plus précis, il y a deux mémoires : une pour les données proprement dites, une autre pour le programme à exécuter. La première est la '''mémoire RAM''', la seconde est la '''mémoire ROM'''. Nous détaillerons ce que sont ces deux mémoires dans la suite du chapitre, mais sachez que nous avions déjà rencontré ces deux types de mémoires dans les chapitres sur les registres et les mémoires adressables.
Les entrées-sorties permettent au processeur et à la mémoire de communiquer avec l'extérieur et d'échanger des informations avec des périphériques. Les '''périphériques''' regroupent, pour rappel, tout ce est branché sur un ordinateur, mais n'est pas à l'intérieur de celui-ci.
Le processeur, les mémoires et les entrées-sorties communiquent ensemble via un '''réseau d'interconnexions'''. Le terme est assez barbare, mais rien de compliqué sur le principe. C'est juste un ensemble de fils électriques qui relie les différents éléments d'un ordinateur. Les interconnexions sont souvent appelées le bus de communication, mais le terme est un abus de langage, comme on le verra plus bas.
Afin de simplifier les explications, on va supposer que le réseau d'interconnexion est le suivant. Tout est connecté au processeur. Il y a des interconnexions entre le processeur et la mémoire RAM, d'autres interconnexions entre processeur et mémoire ROM, et d'autres entre le processeur et les entrées-sorties. Nous verrons que d'autres réseaux d'interconnexions fusionnent certaines interconnexions, pour les partager entre la ROM et la RAM, par exemple. Mais pour le moment, gardez le schéma ci-dessous en tête.
[[File:Réseau d'interconnexion avec un processeur au centre.png|centre|vignette|upright=2|Réseau d'interconnexion avec un processeur au centre]]
==Les mémoires RAM et ROM==
La mémoire est le composant qui mémorise des informations, des données. Dans la majorité des cas, la mémoire est composée de plusieurs '''cases mémoire''', chacune mémorisant plusieurs bits, le nombre de bits étant identique pour toutes les cases mémoire. Dans le cas le plus simple, une case mémoire mémorise un '''octet''', un groupe de 8 bits. Mais les mémoires modernes mémorisent plusieurs octets par case mémoire : elles ont des cases mémoires de 16, 32 ou 64 bits, soit respectivement 2/4/8 octets. De rares mémoires assez anciennes utilisaient des cases mémoires contenant 1, 2, 3, 4, 5, 6 7, 13, 17, 23, 36 ou 48 bits. Mais ce n'était pas des mémoires électroniques, aussi nous allons les passer sous silence.
Tout ce qu'il faut savoir est que la quasi-totalité des mémoires électronique a un ou plusieurs octets par case mémoire. Pour simplifier, vous pouvez imaginer qu'une mémoire RAM est un regroupement de registre, chacun étant une case mémoire. C'est une description pas trop mauvaise pour décrire les mémoires RAM, qu'on abordera dans ce qui suit.
{|class="wikitable"
|+ Contenu d'une mémoire, case mémoire de 16 bits (deux octets)
|-
! Case mémoire N°1
| 0001 0110 1111 1110
|-
! Case mémoire N°2
| 1111 1110 0110 1111
|-
! Case mémoire N°3
| 0001 0000 0110 0001
|-
! Case mémoire N°4
| 1000 0110 0001 0000
|-
! Case mémoire N°5
| 1100 1010 0110 0001
|-
! ...
| ...
|-
! Case mémoire N°1023
| 0001 0110 0001 0110
|-
! Case mémoire N°1024
| 0001 0110 0001 0110
|}
Dans ce cours, il nous arrivera de partir du principe qu'il y a un octet par case mémoire, par souci de simplification. Mais ce ne sera pas systématique. De plus, il nous arrivera d'utiliser le terme adresse pour parler en réalité de la case mémoire associée, par métonymie.
===La capacité mémoire===
Bien évidemment, une mémoire ne peut stocker qu'une quantité finie de données. Et à ce petit jeu, certaines mémoires s'en sortent mieux que d'autres et peuvent stocker beaucoup plus de données que les autres. La '''capacité''' d'une mémoire correspond à la quantité d'informations que celle-ci peut mémoriser. Plus précisément, il s'agit du nombre maximal de bits qu'une mémoire peut contenir. Elle est le produit entre le nombre de cases mémoire, et la taille en bit d'une case mémoire.
Toutes les mémoires actuelles utilisant des cases mémoire d'un ou plusieurs octets, ce qui nous arrange pour compter la capacité d'une mémoire. Au lieu de compter cette capacité en bits, on préfère mesurer la capacité d'une mémoire avec le nombre d'octets qu'elle contient. Mais les mémoires des PC font plusieurs millions ou milliards d'octets. Pour se faciliter la tâche, on utilise des préfixes pour désigner les différentes capacités mémoires. Vous connaissez sûrement ces préfixes : kibioctets, mébioctets et gibioctets, notés respectivement Kio, Mio et Gio.
{|class="wikitable"
|-
!Préfixe!!Capacité mémoire en octets!!Puissance de deux
|-
||Kio||1024||2<sup>10</sup> octets
|-
||Mio||1 048 576||2<sup>20</sup> octets
|-
||Gio||1 073 741 824||2<sup>30</sup> octets
|}
On peut se demander pourquoi utiliser des puissances de 1024, et ne pas utiliser des puissances un peu plus communes ? Dans la majorité des situations, les électroniciens préfèrent manipuler des puissances de deux pour se faciliter la vie. Par convention, on utilise souvent des puissances de 1024, qui est la puissance de deux la plus proche de 1000. Or, dans le langage courant, kilo, méga et giga sont des multiples de 1000. Quand vous vous pesez sur votre balance et que celle-ci vous indique 58 kilogrammes, cela veut dire que vous pesez 58 000 grammes. De même, un kilomètre est égal à 1000 mètres, et non 1024 mètres.
Autrefois, on utilisait les termes kilo, méga et giga à la place de nos kibi, mebi et gibi, par abus de langage. Mais peu de personnes sont au courant de l'existence de ces nouvelles unités, et celles-ci sont rarement utilisées. Et cette confusion permet aux fabricants de disques durs de nous « arnaquer » : Ceux-ci donnent la capacité des disques durs qu'ils vendent en kilo, méga ou giga octets : l’acheteur croit implicitement avoir une capacité exprimée en kibi, mébi ou gibi octets, et se retrouve avec un disque dur qui contient moins de mémoire que prévu.
===Lecture et écriture : mémoires ROM et RWM===
Pour simplifier grandement, on peut grossièrement classer les mémoires en deux types : les ''Read Only Memory'' et les ''Read Write Memory'', aussi appelées mémoires ROM et mémoires RWM. Pour les '''mémoires ROM''', on ne peut pas modifier leur contenu. On peut y récupérer une donnée ou une instruction : on dit qu'on y accède en lecture. Mais on ne peut pas modifier les données qu'elles contiennent. Quant aux '''mémoires RWM''', on peut y accéder en lecture (récupérer une donnée stockée en mémoire), mais aussi en écriture : on peut stocker une donnée dans la mémoire, ou modifier une donnée existante.
Tout ordinateur contient au minimum une ROM et une RWM (souvent une mémoire RAM), les deux n'ont pas exactement le même rôle. Pour simplifier, la mémoire ROM mémorise le programme à exécuter, la mémoire RWM stocke des données. Il a existé des ordinateurs où la mémoire RWM était une mémoire magnétique, voire acoustique, mais ce n'est plus le cas de nos jours. Pour les ordinateurs modernes, la mémoire RWM est une mémoire électronique appelée la '''mémoire RAM'''. La mémoire RAM est utilisée pour stocker temporairement des données que le processeur doit manipuler. La mémoire RAM est donc une mémoire RWN qui a les caractéristiques suivantes :
* La mémoire RAM est une mémoire électronique, fabriquée avec des semi-conducteurs.
* La mémoire RAM s'efface complètement quand on coupe l'alimentation de l'ordinateur (on dit qu'elle est volatile).
* La mémoire RAM possède un temps d'accès approximativement constant aux données (quelle que soit l'adresse).
Et ce sont des caractéristiques que d'autres mémoires RWM n'ont pas :
* Une mémoire RWM peut être magnétique (mémoires à tore de ferrite), acoustique, basées sur des CRTs (tubes Wlliams).
* Une mémoire RWM peut ne pas être volatile et donc conserver les données écrites. L'exemple type étant les mémoires à tore de ferrite. Précisons cependant qu'il existe des prototypes de mémoires FERAM, MRAM et autres, qui sont des RAM non-volatiles.
* Une mémoire RWN n'a pas forcément un temps d'accès constant aux données, ce n'est pas garanti pour les mémoires RWM. Par exemple, les anciens tambours magnétiques des anciens ''mainframes'' étaient des mémoires RWM non-volatiles, utilisées comme stockage temporaire.
Outre le programme à exécuter, la mémoire ROM peut mémoriser des constantes, des données qui ne changent pas. Elles ne sont jamais modifiées et gardent la même valeur quoi qu'il se passe lors de l'exécution du programme. En conséquence, elles ne sont jamais accédées en écriture durant l'exécution du programme, ce qui fait que leur place est dans une mémoire ROM. La mémoire RWM est alors destinée aux données temporaires, qui changent ou sont modifiées lors de l'exécution du programme, et qui sont donc manipulées aussi bien en lecture et en écriture. La mémoire RWM mémorise alors les variables du programme à exécuter, qui sont des données que le programme va manipuler. Pour les systèmes les plus simples, la mémoire RWM ne sert à rien de plus.
[[File:Espaces d'adressage sur une archi harvard modifiée.png|centre|vignette|upright=2.5|Espaces d'adressage sur une archi harvard modifiée]]
Pour donner un exemple de données stockées en ROM, on peut prendre l'exemple des anciennes consoles de jeu 8 et 16 bits. Les jeux vidéos sur ces consoles étaient placés dans des cartouches de jeu, précisément dans une mémoire ROM à l'intérieur de la cartouche de jeu. La ROM mémorisait non seulement le code du jeu, le programme du jeu vidéo, mais aussi les niveaux et les ''sprites'' et autres données graphiques.
Une conséquence est que les consoles 8/16 bits n'avaient pas besoin de beaucoup de RAM, comparé aux ordinateurs de l'époque, vu qu'une grande partie des données utiles étaient dans une ROM directement accessible par le processeur. À l'opposé, les micro-ordinateurs devaient copier les données d'un jeu depuis une disquette dans la mémoire RAM, ce qui demandait d'avoir plus de RAM. Le passage au support CD sur les consoles 32 bits a eu la même conséquence. Le processeur ne pouvant pas lire directement le CD à sa guise, il fallait copier les données du CD en RAM. D'où l'apparition de temps de chargement assez longs, inexistants sur support cartouche.
===L'adressage mémoire===
Sur une mémoire RAM ou ROM, on ne peut lire ou écrire qu'une case mémoire, qu'un registre à la fois : une lecture ou écriture ne peut lire ou modifier qu'une seule case mémoire. Techniquement, le processeur doit préciser à quel case mémoire il veut accéder à chaque lecture/écriture. Pour cela, chaque case mémoire se voit attribuer un nombre binaire unique, l''''adresse''', qui va permettre de le sélectionner et de l'identifier celle-ci parmi toutes les autres. En fait, on peut comparer une adresse à un numéro de téléphone (ou à une adresse d'appartement) : chacun de vos correspondants a un numéro de téléphone et vous savez que pour appeler telle personne, vous devez composer tel numéro. Les adresses mémoires en sont l'équivalent pour les cases mémoire.
[[File:Adressage mémoire.png|centre|vignette|upright=2|Exemple : on demande à la mémoire de sélectionner la case mémoire d'adresse 1002 et on récupère son contenu (ici, 17).]]
L'adresse mémoire est générée par le processeur. Le processeur peut parfaitement calculer des adresses, en extraire du programme qu'il exécute, et bien d'autres choses. Nous détaillerons d'ailleurs les mécanismes pour dans les chapitres portant sur les modes d'adressage du processeur. Mais pour le moment, nous avons juste besoin de savoir que c'est le processeur qui envoie des adresses aux mémoires RAM et ROM.
Les adresses générées par le processeur sont alors envoyées à la RAM ou la ROM via une connexion dédiée, un ensemble de fils qui connecte le processeur à la mémoire : le '''bus d'adresse mémoire'''. L'adresse sélectionne une case mémoire, le processeur peut alors récupérer la donnée dedans pour une lecture, écrire une donnée pour l'écriture. Pour cela, un second ensemble de fil connecte le processeur à la RAM/ROM, mais cette fois-ci pour échanger des données. Il s'agit du '''bus de données mémoire'''. Les deux sont souvent regroupés sous le terme de '''bus mémoire'''.
Un ordinateur contient toujours une RAM et une ROM, ce qui demande aux bus mémoire de s'adapter à la présence de deux mémoires. Il y a alors deux solutions, illustrées dans les deux schémas ci-dessous. Avec la première, il y a un seul bus mémoire partagé entre la RAM et la ROM, comme illustré ci-dessous. Une autre solution utilise deux bus séparés : un pour la RAM et un autre pour la ROM. Nous verrons les différences pratiques entre les deux à la fin du chapitre. Pour le moment, nous allons partir du principe qu'il y a un bus pour la mémoire ROM, et un autre bus pour la RAM.
[[File:CPT-System-Architecture-gapfill1-ANS.svg|centre|vignette|upright=2|Architecture avec une ROM et une RAM.]]
[[File:Réseau d'interconnexion avec un processeur au centre.png|centre|vignette|upright=2|Réseau d'interconnexion avec un processeur au centre]]
===L'alignement mémoire : introduction===
Plus haut, nous avions dit qu'il y a une adresse par case mémoire, chaque case mémoire contenant un ou plusieurs octets. Mais les processeurs modernes partent du principe que la mémoire a un octet par adresse, pas plus. Et ce même si la mémoire reliée au processeur utilise des cases mémoires de 2, 3, 4 octets ou plus. D'ailleurs, la majorité des mémoires RAM actuelle a des cases mémoires de 64 bits, soit 8 octets par case mémoire. Les raisons à cela sont multiple, mais nous les verrons en détail dans le chapitre sur l'alignement mémoire. Toujours est-il qu'il faut distinguer les '''adresses mémoire''' et les '''adresses d'octet''' gérées par le processeur.
Le processeur génère des adresses d'octet, qui permettent de sélectionner un octet bien précis. L'adresse d'octet permet de sélectionner un octet parmi tous les autres. Mais la mémoire ne comprend pas directement cette adresse d'octet. Heureusement, l'octet en question est dans une case mémoire bien précise, qui a elle-même une adresse mémoire bien précise. L'adresse d'octet est alors convertie en une adresse mémoire, qui sélectionne la case mémoire adéquate, celle qui contient l'octet voulu. La case mémoire entière est lue, puis le processeur ne récupère que les données adéquates. Pour cela, des circuits d'alignement mémoire se chargent de faire la conversion entre adresses du processeur et adresse mémoire. Nous verrons cela dans le détail dans le chapitre sur l'alignement mémoire.
Il existe des mémoires qui n'utilisent pas d'adresses mémoire, mais passons : ce sera pour la suite du cours.
==Le processeur==
Dans les ordinateurs, l'unité de traitement porte le nom de '''processeur''', ou encore de '''''Central Processing Unit''''', abrévié en CPU. Le rôle principal du processeur est de faire des calculs. La plupart des processeurs actuels supportent au minimum l'addition, la soustraction et la multiplication. Quelques processeurs ne gèrent pas la division, qui est une opération très gourmande en circuit, peu utilisée, très lente. Il arrive que des processeurs très peu performants ne gèrent pas la multiplication, mais c'est assez rare.
Un processeur ne fait pas que des calculs. Tout processeur est conçu pour effectuer un nombre limité d'opérations bien précises, comme des calculs, des échanges de données avec la mémoire, etc. Ces opérations sont appelées des '''instructions'''. Les plus intuitives sont les '''instructions arithmétiques''', qui font des calculs, comme l'addition, la soustraction, la multiplication, la division. Mais il y a aussi des '''instructions d'accès mémoire''', qui échangent des données entre la mémoire RAM et le processeur. Les autres instructions ne sont pas très intuitives, aussi passons-les sous silence pour le moment, tout deviendra plus clair dans les chapitres sur le processeur.
===Le processeur exécute un programme, une suite d'instructions===
Tout processeur est conçu pour exécuter une suite d'instructions dans l'ordre demandé, cette suite s'appelant un '''programme'''. Ce que fait le processeur est défini par la suite d'instructions qu'il exécute, par le programme qu'on lui demande de faire. Les instructions sont exécutées dans un ordre bien précis, les unes après les autres. L'ordre en question est décidé par le programmeur. La totalité des logiciels présents sur un ordinateur sont des programmes comme les autres.
Le programme à exécuter est stockée dans la mémoire de l'ordinateur. C'est ainsi que l'ordinateur est rendu programmable : modifier le contenu de la mémoire permet de changer le programme exécuté. Mine de rien, cette idée de stocker le programme en mémoire est ce qui a fait que l’informatique est ce qu'elle est aujourd’hui. C'est la définition même d'ordinateur : appareil programmable qui stocke son programme dans une mémoire modifiable.
Une instruction est codée comme les données : sous la forme de suites de bits. Telle suite de bit indique qu'il faut faire une addition, telle autre demande de faire une soustraction, etc. Pour simplifier, nous allons supposer qu'il y a une instruction par adresse mémoire. Sur la grosse majorité des ordinateurs, les instructions sont placées les unes à la suite des autres dans l'ordre où elles doivent être exécutées. Un programme informatique n'est donc qu'une vulgaire suite d'instructions stockée quelque part dans la mémoire de l'ordinateur.
{|class="wikitable"
|+ Exemple de programme informatique
|-
! Adresse
! Instruction
|-
! 0
| Copier le contenu de l'adresse 0F05 dans le registre numéro 5
|-
! 1
| Charger le contenu de l'adresse 0555 dans le registre numéro 4
|-
! 2
| Additionner ces deux nombres
|-
! 3
| Charger le contenu de l'adresse 0555
|-
! 4
| Faire en XOR avec le résultat antérieur
|-
! ...
| ...
|-
! 5464
| Instruction d'arrêt
|}
Pour exécuter une suite d'instructions dans le bon ordre, le processeur détermine à chaque cycle quelle est la prochaine instruction à exécuter. Pour cela, le processeur mémorise l'adresse de l'instruction en cours dans un registre : le '''Program Counter'''. Je rappelle que des instructions consécutives sont dans des adresses consécutives. Pour passer à la prochaine instruction, il suffit donc d'incrémenter le ''program counter''.
: Si une instruction prend plusieurs octets, plusieurs adresses, il suffit de l'incrémenter du nombre d'octets/adresses.
D'autres processeurs font autrement : chaque instruction précise l'adresse de la suivante, directement dans la suite de bit représentant l'instruction en mémoire. Ces processeurs n'ont pas besoin de calculer une adresse qui leur est fournie sur un plateau d'argent. Sur des processeurs aussi bizarres, pas besoin de stocker les instructions en mémoire dans l'ordre dans lesquelles elles sont censées être exécutées. Mais ces processeurs sont très très rares et peuvent être considérés comme des exceptions à la règle.
Nous venons de voir qu'un processeur contient un registre appelé le ''program counter''. Mais il n'est pas le seul. Pour pouvoir fonctionner, tout processeur doit mémoriser un certain nombre d’informations nécessaires à son fonctionnement, qui sont mémorisées dans des '''registres de contrôle'''. La plupart ont des noms assez barbares (registre d'état, ''program counter'') et nous ne pouvons pas en parler à ce moment du cours. Nous les verrons en temps voulu, mais il est important de préciser qu'ils existent.
===L'intérieur d'un processeur===
Fort de ce que nous savons, nous pouvons expliquer ce qu'il y a à l'intérieur d'un processeur. Le premier point est qu'un processeur fait des calculs, ce qui implique qu'il contient des circuits de calcul. Ils sont regroupés dans une ou plusieurs '''unités de calcul'''. Nous avons déjà vu comment fabriquer une unité de calcul simple, dans un chapitre dédié, et c'est la même qui est présente dans un processeur. Du moins dans les grandes lignes, les circuits des processeurs modernes étant particulièrement optimisés. Il en est de même pour les autres circuits de calcul comme ceux pour les multiplications/division/autres.
Si le processeur fait des calculs, qu'en est-il des opérandes ? Et où sont mémorisés les résultats des opérations ? Pour cela, le processeur incorpore des '''registres généraux'''. Les registres généraux servent à mémoriser les opérandes et résultats des instructions. Ils mémorisent des données, contrairement aux registres de contrôle mentionnés plus haut. Le nombre de registres généraux dépend grandement du processeur. Les tout premiers processeurs se débrouillaient avec un seul registre, mais les processeurs actuels utilisent plusieurs registres, pour mémoriser plusieurs opérandes/résultats. Mais la présence de registres est source de pas mal de petites complications. Par exemple, il faut échanger les données entre la RAM et les registres, il faut gérer l'adressage des registres, etc. Il s'agit là de détails que nous expliquerons dans les chapitres sur le processeur.
Le processeur contient enfin un circuit pour interpréter les instructions, appelé l''''unité de contrôle'''. Elle lit les instructions depuis la mémoire, interprète la suite de bit associée, et commande le reste du processeur pour qu'il exécute l'instruction. Ses fonctions sont assez variées, mais nous allons simplifier en disant qu'elle configure l'unité de calcul et les registres pour faire le bon calcul. Son rôle est d'analyser la suite de bit qui constitue l'instruction, et d'en déduire quelle opération effectuer. Elle gère aussi l'accès à la mémoire RAM, et notamment ce qui est envoyé sur son bus d'adresse.
: Dans ce qui suit, on suppose que le ''program counter'' fait partie de l'unité de contrôle.
Pour résumer, un processeur contient une unité de calcul, des registres et une interface avec la mémoire RAM. Le tout est interconnecté, afin de pouvoir échanger des données. L’ensemble forme le '''chemin de données''', nom qui trahit le fait que c'est là que les données se déplacent et sont traitées. Il faut aussi ajouter l'unité de contrôle pour commander le tout. Elle lit les instructions en mémoire, puis commande le chemin de données pour que l'instruction soit exécutée correctement.
[[File:Microarchitecture d'un processeur.png|centre|vignette|upright=2|Microarchitecture d'un processeur]]
Un processeur parait donc assez simple expliqué comme ça, mais il y a de nombreuses subtilités. L'une d'entre elle est liée aux registres, et notamment à la manière dont on les utilise. Pour expliquer ces subtilités, nous allons voir comment les premiers processeurs fonctionnaient, avant de passer aux processeurs un peu plus modernes. Les tout premiers processeurs n'utilisaient qu'un seul registre, ce qui fait que l'utilisation des registres était très simple. Le passage à plusieurs registres a complexifié le tout.
===D'où viennent les adresses ?===
Il est maintenant temps de répondre à une question qui s'était posée dans la section sur l'adressage : d'où viennent les adresses envoyées à la mémoire ? Pour ce qui est des adresses des instructions, on connait déjà la réponse : c'est le ''program counter'' qui gère tout. Mais pour les données, il y a deux possibilités, qui correspondent à deux types de données : les données statiques et les données dynamiques.
Les '''données statiques''' sont les plus simples : elles existent durant toute la durée de vie du programme. Tant que celui-ci s'exécute, il aura besoin de ces données. En conséquence, il leur réserve une place en mémoire, qui est toujours la même. La donnée se situe donc à une adresse bien précise, qui ne change jamais. Attention cependant : les données peuvent être modifiées, changer de valeur. Le programme écrit dans les donnée statiques, c'est même assez fréquent. Ce sont ne sont pas forcément des données constantes !
Pour les données statiques, elles sont toujours placées à la même adresse mémoire. Pour le dire autrement, l'adresse mémoire est une constante, qui ne change pas. L'adresse est connue avant d’exécuter le programme, le programme a été codé pour utiliser cette adresse pour telle donnée, on a réservé une adresse pour la donnée voulue. Et même si vous quittez le programme et vous le relancez plusieurs jours après, l'adresse mémoire sera la même avant et après.
Et cela permet d'utiliser l''''adressage direct'''. L'idée est que les instructions précisent donc l'adresse à lire ou écrire. Pour cela, l'adresse est intégrée dans l’instruction elle-même. Pour rappel, l'instruction est codée par une suite de bit en mémoire RAM/ROM, dont certains précisent l'opération à faire, les autres servant à autre chose. L'idée est que certains bits précisent l'adresse mémoire de la donnée à lire. Les instructions sont donc du genre :
* ''LOAD 56'' - lit l'adresse numéro 56 et copie son contenu dans un registre;
* ''STORE R5 , adress 99'', copie le registre R5 dans l'adresse 99 ;
* ''ADD R5 , adress 209'' : additionne le registre R5 avec le contenu de l'adresse 209.
S'il existe des données statiques, c'est signe qu'il existe des '''données dynamiques'''. Ces dernières sont des données qui sont créées ou détruites selon les besoins. Pour comprendre d'où viennent ces données dynamiques, prenons le cas d'une personne qui écrit du texte sur un ordinateur. Le texte qu'il écrit est mémorisé dans la RAM de l’ordinateur, puis est sauvegardé sur le disque dur quand il sauvegarde son document. Au fur et à mesure qu'il écrit du texte, la RAM utilisée par ce texte augmente. Donc, ce texte est une donnée dynamique, dont la taille varie dans le temps.
Pour gérer des données dynamiques, la plupart des systèmes d'exploitation incorporent des fonctionnalités d''''allocation mémoire'''. Derrière ce nom barbare, se cache quelque chose de simple : les programmes peuvent réclamer de la mémoire au système d'exploitation, pour y placer les données qu'ils souhaitent. Les langages de programmation bas niveau supportent des fonctions comme malloc(), qui permettent de demander un bloc de mémoire de N octets à l'OS, qui doit alors accommoder la demande. De même, un programme peut libérer de la mémoire qu'il n'utilise plus avec des fonctions comme free().
Avec l'allocation mémoire, les données n'ont pas de place fixe en mémoire. Leur adresse mémoire peut varier d'une exécution du programme à l'autre. Pire que ça : les données peuvent être déplacées dans la mémoire RAM, si besoin. En clair : l'adresse est déterminée lors de l'exécution du programme. L'adresse est alors soit calculée, soit lue depuis la mémoire RAM, soit déterminée autrement. Toujours est-il qu'elle se retrouve dans un registre général. Pour gérer ces adresse variables, les processeurs utilisent l''''adressage indirect'''. L'adressage indirect permet d'utiliser une adresse qui est dans un registre de données. L'adresse en question peut être envoyée à la mémoire RAM sans problème, le processeur peut automatiquement connecter le registre adéquat sur le bus d'adresse.
Au tout début de l'informatique, les processeurs ne supportaient que l'adressage direct, pas plus. L'adressage indirect n'était tout simplement pas possible. Avec l'adressage direct, l'adresse à lire est extraite directement des instructions, par l'unité de contrôle. Le bus d'adresse de la RAM est alors relié directement à l'unité de contrôle, le bus de données est relié aux registres.
[[File:Architecture Harvard avec adressage direct uniquement.png|centre|vignette|upright=2|Architecture Harvard avec adressage direct uniquement]]
Mais dès les années 70, l'adressage indirect est apparu, rendant la programmation bien plus simple. Mais cela a demandé quelques adaptation au niveau du processeur. Avec l'adressage indirect, le bus d'adresse est connecté aux registres. Il a donc fallu rajouter un multiplexeur pour que le processeur décide de relier le bus d'adresse soit aux registres, soit à l'unité de contrôle.
===Un ordinateur peut avoir plusieurs processeurs===
La plupart des ordinateurs n'ont qu'un seul processeur, ce qui fait qu'on désigne avec le terme d''''ordinateurs mono-processeur'''. Mais il a existé (et existe encore) des '''ordinateurs multi-processeurs''', avec plusieurs processeurs sur la même carte mère. L'idée était de gagner en performance : deux processeurs permettent de faire deux fois plus de calcul qu'un seul, quatre permettent d'en faire quatre fois plus, etc. C'est très courant sur les supercalculateurs, des ordinateurs très puissants conçus pour du calcul industriel ou scientifique, mais aussi sur les serveurs ! Dans le cas le plus courant, ils utilisent plusieurs processeurs identiques : on utilise deux processeurs Core i3 de même modèle, ou quatre Pentium 3, etc.
Pour utiliser plusieurs processeurs, les programmes doivent être adaptés. Pour cela, il y a plusieurs possibilités :
* Une première possibilité, assez intuitive, est d’exécuter des programmes différents sur des processeurs différents. Par exemple, on exécute le navigateur web sur un processeur, le lecteur vidéo sur un autre, etc.
* La seconde option est de créer des programmes spéciaux, qui utilisent plusieurs processeurs. Ils répartissent les calculs à faire sur les différents processeurs. Un exemple est la lecture d'une vidéo sur le web : un processeur peut télécharger la vidéo pendant le visionnage et bufferiser celle-ci, un autre processeur peut décoder la vidéo, un autre décoder l'audio. De tels programmes restent des suites d'instructions, mais ils sont plus complexes que les programmes normaux, aussi nous les passons sous silence.
* La troisième option est d’exécuter le même programme sur les différents processeurs, mais chaque processeur traite son propre ensemble de données. Par exemple, pour un programme de rendu 3D, quatre processeurs peuvent s'occuper chacun d'une portion de l'image.
[[File:Architecture de Von Neumann Princeton multi processeurs.svg|centre|vignette|upright=2|Architecture de Von Neumann Princeton multi processeurs]]
De nos jours, les ordinateurs grand public les plus utilisés sont dans un cas intermédiaire, ils ne sont ni mono-, ni multi-processeur. Ils n'ont qu'un seul processeur, dans le sens où si on ouvre l'ordinateur et qu'on regarde la carte mère, il n'y a qu'un seul processeur. Mais ce processeur est en réalité assez similaire à un regroupement de plusieurs processeurs dans le même boitier. Il s'agit de '''processeurs multicœurs''', qui contiennent plusieurs cœurs, chaque cœur pouvant exécuter un programme tout seul.
La différence entre cœur et processeur est assez difficile à saisir, mais pour simplifier : un cœur est l'ensemble des circuits nécessaires pour exécuter un programme. Chaque cœur dispose de toute la machinerie électronique pour exécuter un programme, à savoir des circuits aux noms barbares comme : un séquenceur d'instruction, des registres, une unité de calcul. Par contre, certains circuits d'un processeur ne sont présents qu'en un seul exemplaire dans un processeur multicœur, comme les circuits de communication avec la mémoire ou les circuits d’interfaçage avec la carte mère.
Suivant le nombre de cœurs présents dans notre processeur, celui-ci sera appelé un processeur double-cœur (deux cœurs), quadruple-cœur (4 cœurs), octuple-cœur (8 cœurs), etc. Un processeur double-cœur est équivalent à avoir deux processeurs dans l'ordinateur, un processeur quadruple-cœur est équivalent à avoir quatre processeurs dans l'ordinateur, etc. Ces processeurs sont devenus la norme dans les ordinateurs grand public et les logiciels et systèmes d'exploitation se sont adaptés.
===Les coprocesseurs===
Quelques ordinateurs assez anciens disposaient de '''coprocesseurs''', des processeurs qui complémentaient un processeur principal. Les ordinateurs de ce type avaient un processeur principal, le '''CPU''', qui était secondé par un ou plusieurs coprocesseurs. Sauf exception, le CPU et le coprocesseur exécutent des programmes différents, ils travaillent en parallèle.
Les coprocesseurs les plus connus sont les '''coprocesseurs pour le rendu 2D/3D'''. Ils ont eu leur heure de gloire sur les anciennes consoles de jeux vidéo, comme Super Nintendo, la Playstation et autres consoles de cette génération ou antérieure. Ils s'occupaient respectivement de calculer les graphismes des jeux vidéos. De nos jours, ils ont été remplacés par des cartes graphiques, ou des ''Graphic Processing Units'', qui ne sont pas considérées comme des coprocesseurs.
Les '''coprocesseurs sonores''' sont une sorte d'ancêtre des cartes son, utilisés sur les anciennes consoles de jeux vidéo, comme La Nintendo 64, la Playstation et autres consoles antérieures. Ils s'occupaient respectivement de calculer tout ce qui a trait au son. Pour donner un exemple, on peut citer la console Neo-géo, qui disposait de deux processeurs travaillant en parallèle : un processeur principal, et un coprocesseur sonore. Le processeur principal était un Motorola 68000, alors que le coprocesseur sonore était un processeur Z80.
L'accès aux périphériques est quelque chose sur lequel nous passerons plusieurs chapitres dans ce cours. Mais sachez que l'accès aux périphériques peut demander pas mal de puissance de calculs. Le CPU principal peut faire ce genre de calculs par lui-même, mais il n'est pas rare qu'un '''coprocesseur d'IO''' soit dédié à l'accès aux périphériques. Un exemple assez récent est celui de la console de jeu Nintendo 3DS. Elle disposait d'un processeur principal de type ARM9, d'un coprocesseur pour les divisions qu'on abordera plus bas, et d'un second processeur ARM7. L'ARM 7 était utilisé comme coprocesseur d'I/O, ainsi que pour l'émulation de la console GBA.
[[File:Asmp 2.gif|centre|vignette|upright=2|Co-processeur pour l'accès aux entrées-sorties.]]
Les '''coprocesseurs arithmétiques''' sont un peu à part des autres. Ils sont spécialisés dans les calculs en virgule flottante. Ils étaient utilisés à une époque où les CPU ne géraient que des calculs entiers (en binaire ou en BCD). Un exemple est le coprocesseur flottant x87, complémentaire des premiers processeurs Intel x86. Il y a eu la même chose sur les processeurs Motorola 68000, avec deux coprocesseurs flottants appelés les Motorola 68881 et les Motorola 68882.
Un exemple récent de coprocesseur est celui utilisé sur la console de jeu Nintendo DS. La console utilisait deux processeurs, un ARM9 et un ARM7, qui ne pouvaient pas faire de division entière. Il s'agit pourtant d'opérations importantes dans le cas du rendu 3D, ce qui fait que les concepteurs de la console ont rajouté un coprocesseur spécialisé dans les divisions entières et les racines carrées. Le coprocesseur était adressable directement par le processeur, comme peuvent l'être la RAM ou les périphériques.
==Les entrées-sorties==
Tous les circuits vus précédemment traitent des données codées en binaire. Ceci dit, les données ne sortent pas de n'importe où : l'ordinateur contient des composants électroniques qui traduisent des informations venant de l’extérieur en nombres. Ces composants sont ce qu'on appelle des '''entrées'''. Par exemple, le clavier est une entrée : l'électronique du clavier attribue un nombre entier (''scancode'') à une touche, nombre qui sera communiqué à l’ordinateur lors de l'appui d'une touche. Pareil pour la souris : quand vous bougez la souris, celle-ci envoie des informations sur la position ou le mouvement du curseur, informations qui sont codées sous la forme de nombres. La carte son évoquée il y a quelques chapitres est bien sûr une entrée : elle est capable d'enregistrer un son, et de le restituer sous la forme de nombres.
S’il y a des entrées, on trouve aussi des '''sorties''', des composants électroniques qui transforment des nombres présents dans l'ordinateur en quelque chose d'utile. Ces sorties effectuent la traduction inverse de celle faite par les entrées : si les entrées convertissent une information en nombre, les sorties font l'inverse : là où les entrées encodent, les sorties décodent. Par exemple, un écran LCD est un circuit de sortie : il reçoit des informations, et les transforme en image affichée à l'écran. Même chose pour une imprimante : elle reçoit des documents texte encodés sous forme de nombres, et permet de les imprimer sur du papier. Et la carte son est aussi une sortie, vu qu'elle transforme les sons d'un fichier audio en tensions destinées à un haut-parleur : c'est à la fois une entrée, et une sortie.
Les '''entrées-sorties''' incluent toutes les entrées et sorties, et même certains composants qui sont les deux à la fois. Il s'agit d'un terme générique, qui regroupe des composants forts différents. Dans ce qui va suivre, nous allons parfois parler de périphériques au lieu d'entrées-sorties, mais les deux termes ne sont pas équivalents. Dans le détail, les entrées-sorties regroupent :
* Les '''périphériques''' sont les composants connectés sur l'unité centrale. Exemple : les claviers, souris, webcam, imprimantes, écrans, clés USB, disques durs externes, la Box internet, etc.
* Les '''cartes d'extension''', qui se connectent sur la carte mère via un connecteur, comme les cartes son ou les cartes graphiques.
* D'autres composants sont soudés à la carte mère mais sont techniquement des entrées-sorties : les cartes sons soudées sur les cartes mères actuelles, par exemple.
===L'interface avec le reste de l'ordinateur===
Les entrées-sorties sont très diverses, fonctionnent très différemment les unes des autres. Mais du point de vue du reste de l'ordinateur, les choses sont relativement standardisées. Du point de vue du processeur, les entrées-sorties sont juste des paquets de registres ! Tous les périphériques, toutes les entrées-sorties contiennent des '''registres d’interfaçage''', qui permettent de faire l'intermédiaire entre l'entrée/sortie et le reste de l'ordinateur. L'entrée/sortie est conçu pour réagir automatiquement quand on écrit dans ces registres.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage sont assez variés. Les plus évidents sont les '''registres de données''', qui permettent l'échange de données entre le processeur et les périphériques. Pour échanger des données avec l'entrée/sortie, le processeur a juste à lire ou écrire dans ces registres de données. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données. Si le processeur veut envoyer une donnée à une entrée/sortie, il a juste à écrire dans ces registres. Inversement, s'il veut lire une donnée, il a juste à lire le registre adéquat.
Mais le processeur ne fait pas que transmettre des données à l'entrée/sortie. Le processeur lui envoie aussi des « commandes », des valeurs numériques auxquelles l'entrée/sortie répond en effectuant un ensemble d'actions préprogrammées. En clair, ce sont l'équivalent des instructions du processeur, mais pour l'entrée/sortie. Par exemple, les commandes envoyées à une carte graphique peuvent être : affiche l'image présente à cette adresse mémoire, calcule le rendu 3D à partir des données présentes dans ta mémoire, etc. Pour recevoir les commandes, l'entrée/sortie contient des ''registres de commande'' qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande à l'entrée/sortie, il écrit la commande en question dans ce ou ces registres.
Enfin, beaucoup d'entrée/sortie ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état de l'entrée/sortie. Ils servent notamment à indiquer au processeur que l'entrée/sortie est disponible, qu'il est en train d’exécuter une commande, qu'il est occupé, qu'il y a un problème, qu'il y a une erreur de configuration, etc.
===Les adresses des registres d’interfaçage===
Les registres des périphériques sont identifiés par des adresses mémoires. Et les adresses sont conçues de façon à ce que les adresses des différentes entrées/sorties ne se marchent pas sur les pieds. Chaque entrée/sortie, chaque registre, chaque contrôleur a sa propre adresse. D'ordinaire, certains bits de l'adresse indiquent quel est le destinataire. Certains indiquent quel est l'entrée/sortie voulue, les restants indiquant le registre de destination.
Il existe deux organisations possibles pour les adresses des registres d’interfaçages. La première possibilité est de séparer les adresses pour les registres d’interfaçage et les adresses pour la mémoire. Le processeur doit avoir des instructions séparées pour gérer les périphériques et adresser la mémoire. Il a des instructions de lecture/écriture pour lire/écrire en mémoire, et d'autres pour lire/écrire les registres d’interfaçage. Sans cela, le processeur ne saurait pas si une adresse est destinée à un périphérique ou à la mémoire.
[[File:Espaces d'adressages séparés entre mémoire et périphérique.png|centre|vignette|upright=2.5|Espaces d'adressages séparés entre mémoire et périphérique]]
L'autre méthode mélange les adresses mémoire et des entrées-sorties. Si on prend par exemple un processeur de 16 bits, où les adresses font 16 bits, alors les 65536 adresses possibles seront découpées en deux portions : une partie ira adresser la RAM/ROM, l'autre les périphériques. On parle alors d''''entrées-sorties mappées en mémoire'''. L'avantage est que le processeur n'a pas besoin d'avoir des instructions séparées pour les deux.
[[File:IO mappées en mémoire.png|centre|vignette|upright=2.0|IO mappées en mémoire]]
Pour résumer, communiquer avec une entrée/sortie est similaire à ce qu'on a avec les mémoires. Il suffit de lire ou écrire dans des registres d’interfaçage, qui ont chacun une adresse mémoire. Le problème est que le système d'exploitation ne connaît pas toujours le fonctionnement d'une entrée/sortie : il faut installer un programme qui va s'exécuter quand on souhaite communiquer avec l'entrée/sortie, et qui s'occupera de tout ce qui est nécessaire pour le transfert des données, l'adressage du périphérique, etc. Ce petit programme est appelé un driver ou '''pilote de périphérique'''. La « programmation » périphérique est très simple : il suffit de savoir quoi mettre dans les registres, et c'est le pilote qui s'en charge.
==Les architectures Harvard et Von Neumann==
Après avoir vu le processeur, les mémoires et les entrées-sorties, voyons voir comment le tout est interconnecté. Tous les ordinateurs ne sont pas organisés de la même manière, pour ce qui est de leurs bus. Mais pour comprendre pourquoi, nous devons regarder qui communique avec qui, dans un ordinateur. Pour rappel, les données sont placées en mémoire RAM, alors que les instructions sont placées en mémoire ROM. Le processeur lit des instructions dans la mémoire ROM, il lit et écrit dans la mémoire RAM, et accède aux registres d’interfaçage des entrées-sorties. Il y a donc besoins de trois interconnexions : CPU-ROM, CPU-RAM et CPU-IO.
[[File:Réseau d'interconnexion avec un processeur au centre.png|centre|vignette|upright=2|Réseau d'interconnexion avec un processeur au centre]]
Il parait intéressant d'utiliser trois interconnexions, au minimum CPU-ROM, CPU-RAM et CPU-IO. Néanmoins, faire ainsi a de nombreux désavantages. Déjà, il faut pouvoir brancher tout ça sur le processeur. Et celui-ci n'a pas forcément assez de broches pour. Aussi, il est parfois préférable de mutualiser des bus, à savoir de connecter plusieurs composants sur un même bus. Par exemple, on peut mutualiser le bus pour la mémoire RAM et pour la mémoire ROM. Il faut dire que les deux bus sont des bus mémoire, avec un bus d'adresse, un bus de données, et surtout : des bus de commande similaires. Les mutualiser est alors très simple, et permet d'économiser pas mal de broches.
[[File:Réseau d'interconnexion avec un processeur au centre et une architecture Harvard.png|centre|vignette|upright=2|Réseau d'interconnexion avec un processeur au centre et une architecture Harvard]]
Cette mutualisation nous amène naturellement à parler de la distinction entre les architectures Harvard d'un côté et les architectures Von Neumann de l'autre. Elle est très liée au fait d'utiliser soit un bus mémoire unique, soit des bus séparés pour la ROM et la RAM. Voyons cela en détail.
===Les architectures Harvard et Von Neumann : des bus séparés ou unifiés===
Avec l''''architecture Harvard''', la mémoire ROM et la mémoire RAM sont reliées au processeur par deux bus séparés. Il y a un bus RAM pour la mémoire RAM, un bus ROM pour la mémoire ROM. L'avantage de cette architecture est qu'elle permet de charger une instruction et une donnée simultanément : une instruction chargée sur le bus relié à la mémoire programme, et une donnée chargée sur le bus relié à la mémoire de données. Et cela simplifie fortement la conception du processeur.
[[File:Harvard Architecture.png|centre|vignette|upright=2|Architecture Harvard, avec une ROM et une RAM séparées.]]
Avec l''''architecture Von Neumann''', mémoire ROM et mémoire RAM sont reliées au processeur par un bus unique. Le bus unique qui relie processeur, RAM et ROM, s'appelle le '''bus mémoire'''. Un défaut de ces architecture est qu'elles ne peuvent pas charger une instruction et une donnée en même temps. Et cela pose quelques problèmes pour la conception du processeur. Par contre, nous verrons dans ce qui suit qu'utiliser un bus mémoire partagé est bien plus flexible et permet des choses que les architectures Harvard ne peuvent pas faire.
[[File:Architecture Von Neumann, avec deux bus séparés.png|centre|vignette|upright=2|Architecture Von Neumann, avec deux bus séparés.]]
===Les architectures Harvard et Von Neumann : des espaces d'adressage séparés ou unifiés===
La distinction précédente se base sur les connexions entre RAM, ROM et processeur. Mais il existe une autre distinction, très liée, qui est souvent utilisée comme seconde définition des architectures Harvard/Von Neumann. Elle est liée aux adresses mémoire que le processeur peut gérer. Prenons un processeur 16 bits, par exemple, qui gère naturellement des adresses de 16 bits. Il peut gérer 2^16 adresses, soit 64 kibioctets de mémoire. L'ensemble de ces adresses est appelé un '''espace d'adressage'''. Mais comment cet espace d'adressage est utilisé pour adresser une RAM et une ROM ?
Sur les architectures Harvard, le processeur voit deux mémoires séparées avec leur lot d'adresses distinctes. Une même adresse peut donc correspondre soit à la mémoire ROM, soit à la mémoire RAM, suivant le bus utilisé. L'espace d'adressage est donc doublé, dupliqué, avec un pour la ROM, un autre pour la RAM. Rien d'étonnant à cela : il y a deux bus d'adresses, chacun correspondant à un espace d'adressage.
[[File:Vision de la mémoire par un processeur sur une architecture Harvard.png|centre|vignette|upright=2|Vision de la mémoire par un processeur sur une architecture Harvard.]]
Avec l'architecture Von Neumann, la RAM et la ROM doivent se partager les adresses mémoires disponibles. Il n'y a qu'un seul espace d'adressage qui est coupé en deux, avec une partie pour la ROM et une autre pour la RAM. Une adresse correspond soit à la mémoire RAM, soit à la mémoire ROM, mais pas aux deux. Typiquement, la mémoire ROM occupe une partie des adresses, la mémoire RAM utilise le reste. La répartition des adresses est réalisée par les circuits de décodage d'adresse mentionnés plus haut.
[[File:Vision de la mémoire par un processeur sur une architecture Von Neumann.png|centre|vignette|upright=2|Vision de la mémoire par un processeur sur une architecture Von Neumann.]]
Les '''architectures Harvard modifiées''' sont des intermédiaires entre architectures Harvard et architectures Von Neumann, bien qu'elles penchent bien plus du côté des architectures Harvard. Précisons que la terminologie n'est pas claire, beaucoup d'auteurs mettent des définitions différentes derrière ces deux termes. Mais dans ce cours, nous utiliserons une définition très stricte de ce qu'est une architecture Harvard modifiée.
Une architecture Harvard modifiée est une architecture Harvard, où le processeur peut lire des données constantes depuis la mémoire ROM. Nous avions vu plus haut que les mémoires ROM peuvent mémoriser, en plus d'un programme exécutable, des données constantes, qui ne varient pas. Les architectures Harvard pures ne permettent pas de lire des données de ce genre depuis la mémoire ROM, alors que les architectures Harvard modifiées le permettent.
Une architecture Harvard modifiée dispose d'une instruction pour lire les données en mémoire RWM, et d'une instruction pour lire des données en mémoire ROM. Il y a donc deux versions de l'instruction LOAD, qui copient la donnée dans un registre général, mais dont la source de la donnée est différente. Une autre possibilité, plus rare, est que une instruction de copie, qui copie une constante depuis la mémoire ROM vers la mémoire RAM. Le cas le plus commun est l'utilisation de deux instructions LOAD séparées.
[[File:Espaces d'adressage sur une archi harvard modifiée.png|centre|vignette|upright=2.5|Espaces d'adressage sur une archi harvard modifiée]]
Ceci étant dit, revenons à la distinction entre architecture Harvard et Von Neumann. Il faut noter que la RAM et la ROM n'ont pas forcément la même taille. Et ce que ce soit sur une architecture Harvard que sur une architecture Von Neumann, mais c'est plus facile à expliquer sur une architecture Harvard.
On peut par exemple imaginer une architecture Harvard qui utilise des adresses de 16 bits pour la ROM, et seulement 8 bits pour la RAM. Le résultat est qu'il peut adresser 64 kibioctets de ROM, mais seulement 256 octets de RAM. Les deux bus d'adresse sont alors de taille différente, l'un faisant 8 bits, l'autre 16. Quelques processeurs 8 bits étaient dans ce cas, comme on le verra dans le chapitre sur les CPU 8bits. Mais d'autres processeurs utilisent des valeurs différentes, avec par exemple des adresses de 16 bits pour la RAM, mais de 20 bits pour la ROM, ou inversement.
Sur une architecture Von Neumann, tout dépend de comment les adresses sont réparties. La solution la plus simple découpe l'espace d'adressage en deux parties égales, avec la RAM qui est dans la moitié basse (qui part de l'adresse 0 jusqu'à l'adresse au milieu), alors que la ROM est dans la moitié haute (entre l'adresse du milieu et l'adresse maximale). Mais ce n'est pas la seule possibilité, la limite entre RAM et ROM peut être mise n'importe où. Prenons par exemple un processeur 32 bits, capable de gérer 4 milliards d'adresse. Il est parfaitement possible de réserver 128 mébioctets de poids fort à la mémoire ROM, et de laisser le reste à la mémoire RAM.
===Le décodage d'adresse sur les architectures Von Neumann===
Pour résumer, les architectures Harvard et Von Neumann se distinguent sur deux points :
* L'accès à la RAM et à la ROM se font par des bus séparés sur l'architecture Harvard, sur le même bus avec l'architecture Von Neumann.
* Les adresses pour la mémoire ROM et la mémoire RAM sont séparées sur les architectures Harvard, partagées sur l’architecture Von Neumann.
Les architectures Von Neumann utilisent donc un seul bus pour connecter la RAM et la ROM au processeur. Mais cela ne parait pas intuitif : comment deux composants peuvent se connecter aux mêmes fils ? Parce que c'est ce qu'implique le fait de partager un bus. Si je prends une mémoire RAM et une mémoire ROM, toutes deux de 8 bits, elles seront connectées à un bus mémoire de 8 bits. Intuitivement, on se dit qu'il y aura des conflits, du genre : la RAM et la ROM vont accéder au bus en même temps, comment savoir si une adresse est destinée à la RAM ou la ROM, etc ?
Tous ces problèmes sont résolus avec une solution très simple : à chaque instant, seule une mémoire est connectée au bus. L'idée est que les mémoires sont connectées ou déconnectées du bus selon les besoins. Si le processeur veut envoyer lire une donnée en mémoire RAM, il déconnecte la mémoire ROM du bus. Et inversement, s'il veut lire une instruction, il déconnecte la RAM et connecte la ROM.
Pour cela, les mémoires RAM et ROM possèdent une entrée ''Chip Select'' ou ''Output Enable'', qui agit comme une sorte de bouton ON/OFF. Lorsqu'on met un 1 sur cette entrée, la mémoire se connectera au bus. Ses entrées et sorties fonctionneront normalement, elle pourra recevoir des adresses, envoyer ou recevoir des données, tout sera normal. Par contre, si on met un 0 sur cette entrée, la mémoire se "désactive", ses entrée-sorties ne répondent plus aux sollicitations extérieures. Pire que ça : elles sont électriquement déconnectées.
Au total, tout cela demande de gérer deux bit ''Chip Select''/''Output Enable'' : un pour la RAM, un pour la ROM. Et ces deux bits sont configurés pour chaque accès mémoire, pour chaque lecture ou écriture. Pour cela, un circuit de '''décodage d'adresse''' prend en entrée l'adresse mémoire à lire/écrire, et active/désactive les mémoires RAM/ROM selon les besoins. Il prend l'adresse et configure les bits ''Chip Select''/''Output Enable''.
[[File:Décodage d'adresse sur une architecture Von Neumann.png|centre|vignette|upright=2|Décodage d'adresse sur une architecture Von Neumann.]]
L'implémentation la plus simple réserve la moitié des adresses pour la RAM, l'autre moitié pour la ROM. Typiquement, la ROM prend la moitié basse, la RAM la moitié haute. Dans ce cas, activer/désactiver la RAM et la ROM se fait avec seulement le bit de poids fort de l'adresse. Si le bit de poids fort est à 1, alors on accède à la RAM et la ROM doit être désactivée. Mais si ce bit est à 0, alors on accède à la moitié basse et il faut désactiver la RAM.
Une remarque intéressante : le fait de séparer la mémoire en deux parts égales permet de simuler une architecture Harvard à partir d'une architecture Von Neumann. Par exemple, le tout premier processeur d'Intel, le 4004, était l'un de ceux là. La RAM et la ROM sont reliés au même bus, et il y a donc un unique espace d'adressage, qui est séparé en deux parties égales. Le truc est que le processeur traite les deux parties égales comme deux espaces d'adressage séparés. Le processeur se débrouille pour cacher le fait qu'il y a un espace d'adressage unique coupé en deux, ce qui fait que les programmeurs voient bien deux espaces d'adressages distincts.
[[File:Décodage d'adresse sur une architecture Von Neumann basique.png|centre|vignette|upright=2|Décodage d'adresse sur une architecture Von Neumann basique.]]
Pour résumer, quand une adresse est envoyée sur le bus, les deux mémoires vont la recevoir mais une seule va répondre et se connecter au bus. Le décodage d'adresse garantit que seule la mémoire adéquate réponde à un accès mémoire. Le décodage d'adresse est réalisé par la carte mère, par un composant dédié.
Le mécanisme peut être utilisé pour combiner plusieurs RAM en une seule, idem avec les ROM. Pour comprendre l'idée, je vais prendre l'exemple de l'IBM PC, un des tout premier PC existant. Nous étudierons ce PC dans une section dédiée, à la fin du chapitre, aussi je vais passer rapidement dessus. Tout ce que je vais faire est vous présenter la carte mère du PC, et vous demander de faire est de compter les mémoires ROM et mémoires RAM sur la carte mère :
[[File:IBM 5150 Motherboard.svg|centre|vignette|upright=3|Carte mère de l'IBM 5150, un modèle de l'IBM PC.]]
Si vous remarquerez qu'il y a 5 mémoires ROM et 8 à 32 mémoires RAM. Le fait est que le processeur voit les différentes mémoires ROM comme une seule mémoire ROM. Idem avec les mémoires RAM : elle font chacune 2 kibioctets, et l'ensemble est vu par le processeur comme une seule RAM de 16 à 64 kibioctets. Et cela grâce aux circuits de décodage d'adresse, qui sont situés en haut à droite de la carte mère.
Pour comprendre l'idée, prenons l'exemple d'un processeur 16 bits, capable de gérer 64 kibioctets de mémoire. L'espace d'adressage est découpé en quatre portions, de 16 kibioctets chacune. Une portion est réservée à une ROM de 16 kibioctet, les autres sont chacune réservée à une RAM de 16 kibioctet. Le décodage d'adresse sélectionne alors la mémoire adéquate en utilisant les deux bits de poids fort de l'adresse.
* S'ils valent 00, alors c'est la mémoire ROM qui est activée, connectée au bus.
* S'ils valent 01, alors c'est la première mémoire RAM qui est connectée au bus.
* S'ils valent 10, alors c'est la seconde mémoire RAM qui est connectée au bus.
* S'ils valent 11, alors c'est la troisième mémoire RAM qui est connectée au bus.
[[File:Décodage d'adresse sur une architecture Von Neumann, utilisant plusieurs RAM et une ROM.png|centre|vignette|upright=3|Décodage d'adresse sur une architecture Von Neumann, utilisant plusieurs RAM et une ROM]]
===L'impact sur la conception du processeur===
Plus haut, j'ai parlé d'un des avantages des architectures Harvard : elles peuvent lire une instruction en même temps qu'elles accèdent à une donnée. La donnée est lue/écrite en RAM, alors que l'instruction est lue en ROM. Et cela permet de simplifier l'intérieur du processeur. Pas de beaucoup, mais c'est déjà ça de pris. Voyons maintenant comment cela impacte l'intérieur du processeur. Tout ce dont vous avez à vous rappeler est la séparation entre chemin de données et unité de contrôle, et que les registres généraux sont dans le premier, le ''program counter'' dans la seconde.
Avec une architecture Harvard, les instructions et les données passent par des bus différent : bus ROM pour les instructions, bus RAM pour les données. L'intuition nous dit que le bus pour la mémoire ROM est connecté à l'unité de contrôle, alors que le bus pour la RAM est connecté au chemin de données. Et dans les grandes lignes, c'est vrai. La logique est imparable pour ce qui est des bus de données. Mais il y a une petite subtilité pour les bus d'adresse.
Pour comprendre comment le processeur exploite ces deux bus, voyons ce qui transite dessus. Pour la mémoire ROM, elle reçoit l'adresse de l'instruction à lire, elle renvoie l'instruction adéquate. Pour cela, le ''program counter'' est envoyé sur le bus d'adresse, l'instruction sur le bus de données. Pour la mémoire RAM, elle échange des données avec les registres généraux, les registres pour les données. Les adresses utilisées pour la RAM viennent elles soit du chemin de données, soit de l'unité de contrôle, tout dépend du mode d'adressage. Mais le ''program counter'' n'est pas impliqué.
[[File:Architecture Harvard - échanges de données.png|centre|vignette|upright=2|Architecture Harvard - échanges de données]]
Les architectures Harvard modifiées doivent cependant rajouter une connexion entre le bus ROM et les registres généraux. C'est nécessaire pour charger une donnée constante depuis la mémoire ROM. Rappelons que la donnée constante est copiée dans un registre général, donc dans le chemin de données.
[[File:Architecture Harvard modifiée - implémentation du processeur.png|centre|vignette|upright=2|Architecture Harvard modifiée - implémentation du processeur]]
Avec les architectures Von Neumann, il y a un seul bus qui est relié à la fois au chemin de données et à l'unité de contrôle. Si le processeur lit une instruction, le bus doit être relié à l'unité de contrôle. Par contre, s'il accède à une donnée, il doit être relié au chemin de données (le bus d'adresse peut éventuellement être connecté au séquenceur, si celui-ci fournit l'adresse à lire). Il faut donc utiliser un paquet de multiplexeurs et de démultiplexeurs pour faire la connexion au bon endroit.
[[File:Architecture Von Neumann - implémentation du processeur.png|centre|vignette|upright=2|Architecture Von Neumann - implémentation du processeur]]
Une instruction se fait en deux temps : on charge l'instruction depuis la mémoire ROM, puis on l'exécute. Avec une architecture Harvard, tout cela se fait en un seul cycle d'horloge, vu que charger la ROM et accéder aux données peut se faire en même temps. Pas avec les architectures Von Neumann, qui doivent libérer le bus mémoire après avoir chargé une instruction. Elles n'ont pas le choix : elles chargent l'instruction lors d'un premier cycle d'horloge, puis l'exécutent lors du second.
Pour cela, ils incorporent un registre appelé le '''registre d'instruction''', qui mémorise l'instruction chargée. L'instruction est copiée dans ce registre lors du premier cycle, puis est utilisée lors du second cycle. Le registre permet de ne pas oublier l’instruction entre les deux cycles. Le registre d'instruction est obligatoire sur les architectures Von Neumann. En comparaison, il est facultatif sur les architectures Harvard. Elles peuvent en avoir un, pour des raisons techniques, mais ce n'est pas obligatoire.
[[File:Registre d'instruction.png|centre|vignette|upright=2|Registre d'instruction.]]
===Les architectures Von Neumann sont plus flexibles===
Sur les architectures Harvard, le processeur sait faire la distinction entre programme et données. Les données sont stockées dans la mémoire RAM, le programme est stocké dans la mémoire ROM. Les deux sont séparés, accédés par le processeur sur des bus séparés, et c'est ce qui permet de faire la différence entre les deux. Il est impossible que le processeur exécute des données ou modifie le programme. Du moins, tant que la mémoire qui stocke le programme est bien une ROM.
Par contre, sur les architectures Von Neumann, il est impossible de distinguer programme et données, sauf en ajoutant des techniques de protection mémoire avancées. La raison est qu'il est impossible de faire la différence entre donnée et instruction, vu que rien ne ressemble plus à une suite de bits qu'une autre suite de bits. Et c'est à l'origine d'un des avantages majeur de l'architecture Von Neumann : il est possible que des programmes soient copiés dans la mémoire RWM et exécutés dans celle-ci.
Un cas d'utilisation familier est celui de votre ordinateur personnel. Le système d'exploitation et les autres logiciels sont copiés en mémoire RAM à chaque fois que vous les lancez. Mais cet exemple implique un disque dur, ce qui rend les choses plus compliquées que prévu. Un autre exemple serait la compilation de code à la volée, mais il ne sera pas très parlant.
Un exemple plus adapté serait celui où la ROM mémorise un programme compressée dans la mémoire ROM, qui est décompressé pour être exécuté en mémoire RAM. Le programme de décompression est stocké en mémoire ROM et est exécuté au lancement de l’ordinateur. Cette méthode permet d'utiliser une mémoire ROM très petite et très lente, tout en ayant un programme rapide (si la mémoire RWM est rapide).
Il est aussi possible de créer des programmes qui modifient leurs propres instructions : cela s'appelle du '''code auto-modifiant'''. Ce genre de choses servait autrefois sur des ordinateurs rudimentaires, au tout début de l'informatique. À l'époque, les adresses à lire/écrire devaient être écrites en dur dans le programme, dans les instructions exécutées. Pour gérer certaines fonctionnalités des langages de programmation qui ont besoin d'adresses modifiables, comme les tableaux, on devait corriger les adresses au besoin avec du code auto-modifiant. De nos jours, le code automodifiant est utilisée occasionnellement pour rendre un programme indétectable dans la mémoire (les virus informatiques utilisent beaucoup ce genre de procédés).
L'impossibilité de séparer données et instructions est à l'origine de problèmes assez fâcheux. Il est parfaitement possible que le processeur charge et exécute des données, qu'il prend par erreur pour des instructions. C'est le cas quand des pirates informatiques arrivent à exploiter des bugs. Il arrive que des pirates informatiques vous fournissent des données corrompues, qui contiennent un virus ou un programme malveillant est caché dans les données. Les bugs en question permettent d'exécuter ces données, donc virus. Pour éviter cela, le système d'exploitation peut marquer certaines zones de la mémoire comme non-exécutable, c’est-à-dire que le système d'exploitation interdit d’exécution de quoi que ce soit qui est dans cette zone. Mais ce n'est pas parfait.
Toujours est-il que tout cela est impossible sur les architectures Harvard. Et ce serait très limitant. Imaginez : pas possible de lancer un programme depuis le disque dur ou une clé USB, le programme doit impérativement être dans une mémoire ROM, pas de compilation à la volée, etc. Que des techniques très utilisées dans l'informatique moderne. Malgré ses défauts, les architectures Von Neumann ne sont pas les plus utilisées pour rien. Les architectures Harvard sont concrètement utilisées uniquement dans l'informatique embarquée, sur des microcontrôleurs très spécifiques.
==Le bus de communication avec les entrées-sorties==
Le processeur, la mémoire et les entrées-sorties sont connectées par un ou plusieurs '''bus de communication'''. Ce bus n'est rien d'autre qu'un ensemble de fils électriques sur lesquels on envoie des zéros ou des uns. Pour communiquer avec la mémoire, il y a trois prérequis qu'un bus doit respecter : pouvoir sélectionner la case mémoire (ou l'entrée-sortie) dont on a besoin, préciser à la mémoire s'il s'agit d'une lecture ou d'une écriture, et enfin pouvoir transférer la donnée. Pour cela, on doit donc avoir trois bus spécialisés, bien distincts, qu'on nommera le bus de commande, le bus d'adresse, et le bus de donnée.
* Le '''bus de données''', sur lequel s'échangent les données entre les composants.
* Le '''bus de commande''' pour configurer la mémoire et les entrées-sorties.
* Le '''bus d'adresse''', facultatif, permet de préciser quelle adresse mémoire il faut lire/écrire.
Chaque composant possède des entrées séparées pour le bus d'adresse, le bus de commande et le bus de données. Par exemple, une mémoire RAM possédera des entrées sur lesquelles brancher le bus d'adresse, d'autres sur lesquelles brancher le bus de commande, et des broches d'entrée-sortie pour le bus de données. Précisons cependant que le bus de commande n'est pas exactement le même entre des mémoires RAM/ROM et des entrées-sorties.
[[File:Bus general schematic.svg|centre|vignette|upright=2|Contenu d'un bus, généralités.]]
===Le réseau d'interconnexion : généralités===
Reprenons où nous nous étions arrêté. Avant de voir les architectures Harvard et Von Neumann, nous avions dit que le processeur, les mémoires et les entrées-sorties sont reliées entre eux par un réseau d'interconnexion. Nous venons de voir qu'il est possible de mutualiser certains bus, notamment celui de la mémoire RAM et celui de la mémoire ROM. Mais il est possible de faire la même chose pour les entrées-sorties. Là encore, il est possible de regrouper le bus mémoire avec les bus pour les entrées-sorties. Voyons ce que cela implique.
{|
|[[File:Réseau d'interconnexion avec un processeur au centre.png|centre|vignette|upright=2|Réseau d'interconnexion avec une architecture Harvard.]]
|[[File:Réseau d'interconnexion avec un processeur au centre et une architecture Harvard.png|centre|vignette|upright=2|Interconnexions d'une architecture Von Neumann.]]
|}
Avant de poursuivre, nous devons préciser quelque chose d'important. Sur les ordinateurs modernes, les entrées-sorties peuvent accéder à la mémoire RAM. Les ordinateurs modernes intègrent des techniques de '''''Direct Memory Access''''' (DMA) qui permettent aux entrées-sorties de lire ou d'écrire en mémoire RAM. Les transferts DMA se font sans intervention du processeur. Ils permettent de copier un bloc de plusieurs octets, dans deux sens : de la mémoire RAM vers une entrée-sortie, ou inversement. Le DMA demande d'ajouter un circuit dédié sur la carte mère : le contrôleur DMA. Il effectue la copie d'un paquet d'octets de la RAM vers l'entrée-sortie ou dans l'autre sens.
[[File:Réseau d'interconnexion avec un processeur au centre, et direct memory access.png|centre|vignette|upright=2|Réseau d'interconnexion avec un processeur au centre, et direct memory access]]
===Les bus systèmes===
La première solution utilise un bus unique, celui-ci est appelé le '''bus système''', aussi appelé ''backplane bus''. Le bus système est connecté à la mémoire RAM, la mémoire ROM, au processeur, et aux entrées-sorties. Tous les composants présents dans l'ordinateur sont connectés à ce bus, sans exception. De tels bus avaient pour avantage la simplicité. Le processeur n'est connecté qu'à un seul bus, ce qui utilise peu de broches et économise des fils. La mutualisation des bus est totale, le câblage est plus simple, la fabrication aussi.
[[File:Architecture minimale d'un ordinateur.png|centre|vignette|upright=2|Architecture minimale d'un ordinateur.]]
Un bus système contient un bus d'adresse, de données et de commande. Un bus système se marie bien avec des entrées-sorties mappées en mémoire. La conséquence est que le bus d'adresse ne sert pas que pour l'accès à la mémoire RAM/ROM, mais aussi pour l'accès aux entrées-sorties. Il y a moyen d'implémenter un système d'adresse séparés avec, mais c'est pas l'idéal.
[[File:Architecture Von Neumann avec les bus.png|centre|vignette|upright=2|Architecture Von Neumann avec les bus.]]
Un bus système n'a pas de limitations quant aux échanges de données. Le processeur peut communiquer directement avec les mémoires et les entrées-sorties, les entrées-sorties peuvent communiquer avec la mémoire RAM, etc. Notamment, un bus système peut implémenter le ''Direct Memory Access''. Il suffit juste de connecter un contrôleur DMA sur le bus système. Le contrôleur DMA est considéré comme une entrée-sortie, ses registres sont mappés en mémoire et sont donc accessibles directement par le processeur.
[[File:Bus système avec controleur DMA.png|centre|vignette|upright=2|Bus système avec contrôleur DMA.]]
Si on suit la définition à la lettre, un bus système est systématiquement une architecture Von Neumann, vu que la mémoire ROM et la mémoire RAM sont reliées sur le bus système. La conséquence est que les circuits de décodage d'adresse sont présents. Ils sont toujours sur la carte mère, et sont plus ou moins à côté du bus système. Cependant, le décodage d'adresse est parfois étendu pour tenir compte des entrées-sorties.
Les entrées-sorties soudées sur la carte mère ont elles aussi des entrées ''Chip Select'' ou quelque chose de similaire. Le décodage d'adresse peut alors les activer ou les désactiver suivant l'adresse envoyée sur le bus d'adresse. C'est ce qui arrive quand le processeur écrit dans un registre d’interfaçage : il envoie l'adresse de ce registre sur le bus d'adresse, le circuit de décodage d'adresse active seulement l'entrée-sortie associée. Il faut noter que ce n'est pas systématique, il existe des techniques pour se passer de décodage d'adresse. Mais nous en reparlerons dans le chapitre sur les bus de communication.
[[File:Chipselectfr.png|centre|vignette|upright=1.5|Exemple détaillé.]]
Les bus systèmes sont certes très simples, mais ils ont aussi des désavantages. Par exemple, il faut éviter que le processeur et les entrées-sorties se marchent sur les pieds, ils ne peuvent pas utiliser le bus en même temps. De tels conflits d'accès au bus système sont fréquents et ils réduisent la performance, comme on le verra dans le chapitre sur les bus. De plus, un bus système a le fâcheux désavantage de relier des composants allant à des vitesses très différentes : il arrivait fréquemment qu'un composant rapide doive attendre qu'un composant lent libère le bus. Le processeur était le composant le plus touché par ces temps d'attente.
Elle était utilisée sur les tout premiers ordinateurs, pour sa simplicité. Elle était parfaitement adaptée aux anciens composants, qui allaient tous à la même vitesse. De nos jours, les ordinateurs à haute performance ne l'utilisent plus trop, mais elle est encore utilisée sur certains systèmes embarqués, en informatique industrielle dans des systèmes très peu puissants.
===Les bus d'entrées-sorties===
Les bus systèmes ont de nombreux problèmes, ce qui fait que d'anciens ordinateurs faisaient autrement. À la place d'un bus système unique, ils utilisent un bus séparé pour les mémoires, et un autre séparé pour les entrées-sorties. Le bus spécialisé pour la mémoire est appelé le '''bus mémoire''', l'autre bus est appelé le '''bus d'entrées-sorties'''. Le bus mémoire est généralement relié à la fois à la mémoire RAM et à la mémoire ROM, les exceptions ne sont pas rares, cependant.
[[File:Bus mémoire séparé du bus pour les IO.png|centre|vignette|upright=2|Bus mémoire séparé du bus pour les IO]]
Les bus d'entrée-sorties peuvent être spécialisés et simplifiés. Par exemple, ils peuvent avoir un bus de commande différent de celui de la mémoire, qui utilise nettement moins de fils. Le bus d'adresse peut aussi être réduit, et utiliser des adresses plus courtes que celles du bus mémoire. Les bus de données peuvent aussi être de taille différentes. Il est ainsi possible d'avoir un bus mémoire capable de lire/écrire 64 bits à la fois, alors que la communication avec les entrées-sorties se fait octet par octet ! En général, les bus d'entrée-sortie sont assez petits, ils ont une taille de 8 ou 16 bits, même si le bus mémoire est plus grand. Cela permet de ne pas gaspiller trop de broches. Ajouter un bus d'entrée-sortie n'est donc pas très gourmand en broches et en fils.
: Il est en théorie possible d'avoir une fréquence différente pour les deux bus, avec un bus mémoire ultra-rapide et un bus pour les entrées-sorties est un bus moins rapide. Mais il faut que le processeur soit prévu pour, et c'est très rare.
Niveau performances, le processeur peut théoriquement accéder à la mémoire en attendant qu'une entrée/sortie réponde, mais il faut que le processeur soit prévu pour, et ce n'est pas de la tarte. Par contre, cela implique d'avoir des adresses séparées pour les registres d’interfaçage et la mémoire. En clair : pas d'entrée-sortie mappée en mémoire ! Un autre problème est que les entrées-sorties ne peuvent pas communiquer avec la mémoire directement, elles doivent passer par l'intermédiaire du processeur. En clair : pas de ''Direct Memory Access'' ! Les deux sont des défauts rédhibitoires pour les programmeurs système, notamment pour ceux qui codent les pilotes de périphériques.
Pour résumer, les défauts sont assez problématiques : pas d'entrées-sorties mappées en mémoire, pas de ''Direct Memory Access'', économie de broches limitée. Les deux premiers sont des défauts majeurs, qui font que de tels bus ne sont pas utilisés dans les ordinateurs modernes. À la place, ils utilisent une troisième solution, distincte des bus systèmes et des bus d'entrée-sorties.
===Les bus avec répartiteur===
Il existe une méthode intermédiaire, qui garde deux bus séparés pour la mémoire et les entrées-sorties, mais élimine les problèmes de brochage sur le processeur. L'idée est d'intercaler, entre le processeur et les deux bus, un '''circuit répartiteur'''. Il récupère tous les accès et distribue ceux-ci soit sur le bus mémoire, soit sur le bus des périphériques. Le ou les répartiteurs s'appellent aussi le '''''chipset''''' de la carte mère.
C'était ce qui était fait à l'époque des premiers Pentium. À l'époque, la puce de gestion du bus PCI faisait office de répartiteur. Elle mémorisait des plages mémoires entières, certaines étant attribuées à la RAM, les autres aux périphériques mappés en mémoire. Elles utilisaient ces plages pour faire la répartition.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus]]
Niveau adresses des registres d'interfacage, il est possible d'avoir soit des adresses unifiées avec les adresses mémoire, soit des adresses séparées.
L'usage d'un répartiteur ne pose pas de problèmes particuliers pour implémenter le DMA. La seule contrainte est que le contrôleur DMA soit intégré dans le répartiteur. Les échanges entre IO et mémoire passent par le répartiteur, qui fait le pont entre bus mémoire et bus des IO.
[[File:Implémentation du DMA avec un répartiteur.png|centre|vignette|upright=2|Implémentation du DMA avec un répartiteur]]
==Les microcontrôleurs et ''system on chip''==
Parfois, on décide de regrouper la mémoire, les bus, le CPU et les ports d'entrée-sortie dans un seul circuit intégré, un seul boitier. L'ensemble forme alors ce qu'on appelle un '''''System on Chip''''' (système sur une puce), abrévié en SoC. Le nom est assez explicite : un SoC comprend un système informatique complet sur une seule puce de silicium, microprocesseurs, mémoires et périphériques inclus. Ils incorporent aussi des ''timers'', des compteurs, et d'autres circuits très utiles.
[[File:ARMSoCBlockDiagram.svg|centre|vignette|upright=2|SoC basé sur un processeur ARM, avec des entrées-sorties typiques de celles d'un µ-contrôleur. Le support du bus CAN, d'Ethernet, du bus SPI, d'un circuit de PWM (génération de signaux spécifiques), de convertisseurs analogique-digital et inverse, sont typiques des µ-contrôleurs.]]
Le terme SoC regroupe des circuits imprimés assez variés, aux usages foncièrement différents et à la conception distincte. Les plus simples d’entre eux sont des microcontrôleurs, qui sont utilisés pour des applications à basse performance. Les plus complexes sont utilisés pour des applications qui demandent plus de puissance, que nous appellerons SoC haute performance.
La relation entre SoC et microcontrôleurs est assez compliquée à expliquer, la terminologie n'étant pas clairement établie. Il existe quelques cours/livres qui séparent les deux, d'autres qui pensent que les deux sont très liés. Dans ce cours, nous allons partir du principe que tous les systèmes qui regroupent processeur, mémoire et quelques périphériques/entrées-sorties sont des SoC. En suivant cette définition, les microcontrôleurs sont donc un cas particulier de SoC, .
===Les microcontrôleurs===
Les '''microcontrôleurs''' sont des composants utilisés dans l'embarqué ou d'informatique industrielle. Leur nom trahit leur rôle. Ils sont utilisés pour contrôler de l'électroménager, des chaines de fabrication dans une usine, des applications robotiques, les alarmes domestiques ou ebcore les voitures. De manière générale, on les trouve dans tous les systèmes dits embarqués et/ou temps réel. Ils ont besoin de s'interconnecter à un grand nombre de composants et intègrent pour cela un grand nombre d'entrée-sorties. Les microcontrôleurs sont généralement peu puissants et doivent consommer peu d'énergie/électricité.
Fait amusant, on en trouve dans certains périphériques informatiques. Par exemple, les anciens disques durs intégraient un microcontrôleur qui contrôlait plusieurs moteurs : les moteurs pour faire tourner les plateaux magnétiques et les moteurs pour déplacer les têtes de lecture/écriture. Autre exemple : les claviers d'ordinateurs, qui intègrent un microcontrôleur connecté aux touches. Celui-ci détecte quand les touches sont appuyées et qui communique avec l'ordinateur. Nous détaillerons ces deux exemples dans les chapitres dédiés aux périphériques et aux disques durs, tout deviendra plus clair à ce moment là. La majorité des périphériques ou des composants internes à un ordinateur contiennent des microcontrôleurs.
Un microcontrôleur tend à intégrer des entrées-sorties assez spécifiques, qu'on ne retrouve pas dans les SoC destinés au grand public. Un microcontrôleur est typiquement relié à un paquet de senseurs et son rôle est de commander des moteurs ou d'autres composants. Et les entrées-sorties intégrées sont adaptées à cette tâche. Par exemple, ils tendent à intégrer de nombreux convertisseurs numériques-analogiques pour gérer des senseurs. Ils intègrent aussi des circuits de génération de signaux PWM spécialisés pour commander des moteurs, le processeur peut gérer des calculs trigonométriques (utiles pour commander la rotation d'un moteur), etc.
[[File:C8051F340-9 A B C D.png|centre|vignette|upright=2|Exemple d'entrées-sorties intégrées à un microcontroleur.]]
En plus des entrées-sorties intégrées, les microcontrôleurs ont souvent des '''ports d'entrées-sorties''' banalisés, à savoir qu'on peut brancher n'importe quoi dessus. Il était possible de brancher un capteur de température, un moteur à commander, un port série, un port parallèle, un écran, un clavier, une souris, peu importe. Les ports font souvent un octet et ils sont généralement reliées directement ou indirectement au processeur. Le logiciel qui s'exécute sur le processeur décide quoi envoyer sur des broches et comment interprète ce qui est reçu dessus.
Les ports banalisés de ce type sont parfois appelés des '''GPIO''', abréviation de ''General Purpose Input/Output'', mais nous utiliserons le terme de ''port I/O''. Un port regroupe plusieurs broches qui peuvent être utilisés à volonté. C'est le logiciel qui s'exécute sur le processeur qui décide de la fonction de broches. Un port IO peut fonctionner soit en tant qu'entrée, soit en tant que sortie. Il est possible de changer de sens en cours de fonctionnement, pour passer d'une entrée à une sortie ou inversement.
[[File:Afficheurs7seg.png|centre|vignette|upright=2|Exemple d'utilisation d'un port I/O. le port est connecté à un afficheur LCD dit 7-segments. Les 8 entrées de cet afficheur sont connectées à un port I/O d'un octet.]]
===Les SoC haute performance===
Les SoC les plus performants sont actuellement utilisés dans les téléphones mobiles, tablettes, ''Netbook'', ''smartphones'', ou tout appareil informatique grand public qui ne doit pas prendre beaucoup de place. La petite taille de ces appareils fait qu'ils gagnent à regrouper toute leur électronique dans un circuit imprimé unique. Mais les contraintes font qu'ils doivent être assez puissants. Ils incorporent des processeurs assez puissants, surtout ceux des ''smartphones''. C'est absolument nécessaire pour faire tourner le système d'exploitation du téléphone et les applications installées dessus.
Niveau entrées-sorties, ils incorporent souvent des interfaces WIFI et cellulaires (4G/5G), des ports USB, des ports audio, et même des cartes graphiques pour les plus puissants d'entre eux. Les SoC incorporent des cartes graphiques pour gérer tout ce qui a trait à l'écran LCD/OLED, mais aussi pour gérer la caméra, voire le visionnage de vidéo (avec des décodeurs/encodeurs matériel). Par exemple, les SoC Tegra de NVIDIA incorporent une carte graphique, avec des interfaces HDMI et VGA, avec des décodeurs vidéo matériel H.264 & VC-1 gérant le 720p. Pour résumer, les périphériques sont adaptés à leur utilisation et sont donc foncièrement différents de ceux des microcontrôleurs.
[[File:SOMblk.png|centre|vignette|upright=3|Exemple de SoC.]]
Un point important est que les processeurs d'un SoC haute performance sont... performants. Ils sont le plus souvent des processeurs de marque ARM, qui sont différents de ceux utilisés dans les PC fixe/portables grand public qui sont eux de type x86. Nous verrons dans quelques chapitres en quoi consistent ces différences, quand nous parlerons des jeux d'instruction du processeur. Autrefois réservé au monde des PCs, les processeurs multicœurs deviennent de plus en plus fréquents pour les SoC de haute performance. Il n'est pas rare qu'un SoC incorpore plusieurs cœurs. Il arrive même qu'ils soient foncièrement différents, avec plusieurs cœurs d'architecture différente.
La frontière entre SoC haute performance et microcontrôleur est de plus en plus floue. De nombreux appareils du quotidien intègrent des SoC haute performance, d'autres des microcontrôleurs. Par exemple, les lecteurs CD/DVD/BR et certains trackers GPS intègrent un SoC ou des processeurs dont la performance est assez pêchue. À l'opposé, les systèmes domotiques intègrent souvent des microcontrôleurs simples. Malgré tout, les deux cas d'utilisation font que le SoC/microcontrôleur est connecté à un grand nombre d'entrées-sorties très divers, comme des capteurs, des écrans, des LEDs, etc.
[[File:GPS tracker Hardware Architecture.png|centre|vignette|upright=2|Hardware d'un tracker GPS.]]
==Étude de l'architecture de quelques consoles de jeu==
Après avoir vu la théorie, nous allons voir des exemples réels d'ordinateurs. Dans ce qui suit, nous allons voir des ordinateurs qui collent assez bien à l''''architecture de base''' vue plus haut, avec un CPU, une RAM et une ROM, quelques entrées-sorties. Tous les ordinateurs modernes, mais aussi dans les smartphones, les consoles de jeu et autres, utilisent une architecture grandement modifiée et améliorée, avec un grand nombre de périphériques, des disques durs/SSD, un grand nombre de mémoires différentes, etc.
Il pourrait sembler pertinent d’étudier des microcontrôleurs ou des ''System On Chip'', en premier lieu. Mais nous éviterons soigneusement de tels systèmes pour le moment. La raison est qu'ils ont un grand nombre d'entrées-sorties, qui sont peu familières. Attendez-vous à avoir près d'une vingtaine ou centaine d'entrée-sorties différentes pour de tels systèmes. Le tout est très complexe, bien trop pour un premier exemple.
À la place, nous allons voir précisément des exemples plus simples : les premiers PC, et des consoles de jeu 8 et 16 bits. Bien que ce soit des systèmes très simples, ils sont cependant plus complexes que l'architecture de base. Et leur avantages/désavantages sont un peu inverse l'un de l'autre. Si on devait résumer les différences, on aurait ceci :
* Les PC ont plus d'entrées-sorties que les consoles, bien que nettement moins que pour les microcontrôleurs/SoC.
* Les PC utilisent des disques durs, les consoles font avec soit des cartouches de jeu, soit des CD/DVD.
* Les PC utilisent des cartes électroniques séparées pour le son et l'écran, les consoles utilisent des circuits soudés sur la carte mère, qui sont souvent des co-processeurs.
* Les PC ont une mémoire ROM soudées sur la carte mère, les consoles 8 bits font sans.
Les PC et micro-ordinateurs ont plus d'entrées-sorties que les consoles. Même si on mets de côté les périphériques, ils ont aussi beaucoup d'entrées-sorties soudées sur la carte mère. En comparaison, les consoles de jeu 8/16 bits se débrouillent avec : une cartouche de jeu et une manette en entrée, une sortie vidéo et une sortie son.
Un autre point important est l'absence de disque dur ou de lecteur CD. La présence d'un disque dur ou d'un lecteur CD/DVD complexifie tout de suite l'architecture des PC. Il faut leur réserver un bus dédié ou les connecter à un bus système, ajouter des circuits sur la carte mère, etc. Et surtout, il faut expliquer comment l'ordinateur exécute des programmes, ce qui demande de parler de l'interaction avec le disque dur et la ROM du BIOS. Rien de tout cela sur les consoles de jeu 8 et 16 bits. Elles utilisent à la place une mémoire ROM, dans la cartouche de jeu, pour mémoriser le code du jeu. Pas besoin de parler des mémoires de stockage, on est beaucoup plus proche de l'architecture de base avec une ROM unique.
Autre différence : les PC utilisent des cartes électroniques à brancher sur la carte mère pour alimenter l'écran et les hauts-parleurs/casques, alors que les consoles de jeu utilisent des co-processeurs dédiés pour le son et les graphismes. Nous avons déjà expliqué ce que sont les co-processeurs plus haut, aussi les co-processeurs des consoles nous paraitrons familiers. On n'a pas à s’embêter à expliquer ce que sont les cartes d'extension, les bus associés et tout ce qui va avec, cela peut être retardé pour la section sur l'architecture des PC.
Par contre, n'allez pas croire que tout est rose avec les consoles 8/16 bits. Il y a quelques différences qui font qu'elles sont plus complexes qu'un PC sur certains points. La gestion de la cartouche de jeu est notamment un peu subtile à comprendre, bien que ce soit bien plus simple à comprendre qu'un système avec un disque dur. Les cartouches de jeu intègrent une mémoire ROM, pour mémoriser les données du jeu, voire son code. Et le processeur doit exécuter le code depuis cette mémoire ROM. La conséquence est que les consoles 8/16 bits utilisent une architecture Harvard, avec un bus relié à la cartouche pour lire les instructions. Mais si ce n'était que ça... Les cartouches mémorisent aussi les données pour les graphismes, ce qui fait que le co-processeur vidéo doit lui aussi lire la cartouche pour récupérer ces données...
===L'architecture de la TurboGraphX-16===
La console PC Engine, aussi appelée TurboGraphX, est une ancienne console 8 bits. Elle contient un processeur 65C02, 8 kibioctets de RAM, un port manettes, une carte son et une carte vidéo. La '''carte son''' est le composant qui s'occupe de commander les haut-parleurs et de gérer tout ce qui a rapport au son. La '''carte graphique''' est le composant qui est en charge de calculer les graphismes, tout ce qui s'affiche à l'écran. Sur cette console, les cartes son et graphique ne sont PAS des co-processeurs, ce sont des circuits électroniques dits fixes. C'est totalement différent de ce qu'on a sur les consoles modernes, aussi le préciser est important.
Bien que la carte graphique ne soit pas un processeur, elle a 64 kibioctets de RAM rien que pour elle. La RAM en question est séparée de la RAM normale, c'est un circuit intégré séparé. Et c'est un cas très fréquent, qui reviendra par la suite. La majeure partie des cartes graphiques dispose de leur propre '''mémoire vidéo''', totalement réservée à la carte graphique. La RAM vidéo est connectée à la carte graphique via un bus séparé. Le processeur est souvent connecté à ce bus, afin de pouvoir écrire des données dedans, mais ce n'est pas le cas ici.
[[File:Architecture de la PC Engine, aussi appelée TurboGrafx-16.png|centre|vignette|upright=2.5|Architecture de la PC Engine, aussi appelée TurboGrafx-16]]
L'architecture de la console était particulièrement simple. Le processeur était le centre de l'architecture, tout était connecté dessus. Il y a un bus pour la cartouche de jeu, un autre pour la RAM, un autre pour les manettes, un autre pour carte son, et un dernier pour la carte graphique. Le fait d'avoir un bus par composant est assez rare et ce n'est le cas ici que parce des conditions particulières sont remplies. Déjà, il y a peu d'entrée-sorties. Ensuite, les bus font tous 8 bits, vu que le processeur est un CPU 8 bits. Avec 5 connexions de 8 bits, le tout utilise 40 broches, ce qui est beaucoup, mais totalement gérable. Par contre, les choses changerons pour les autres consoles.
Au final, l'organisation des bus peut s'expliquer avec ce qu'on a vu dans la section sur les bus de communication. La console utilise une architecture Harvard, car la ROM et la RAM utilisent des bus différents. De plus, il y a des bus dédiés aux entrées-sorties, séparés des bus mémoire. Enfin, la carte graphique a droit à ses propres bus pour lire dans la cartouche et dans sa RAM vidéo dédiée.
===L'architecture de la console de jeu NES===
Maintenant, nous allons voir la console de Jeu Famicom, aussi appelée la NES en occident. Elle a une architecture centrée sur un processeur Ricoh 2A03, similaire au processeur 6502, un ancien processeur autrefois très utilisé et très populaire. Le processeur est associé à 2 KB de mémoire RAM.
Sur certaines cartouches, on trouve une RAM utilisée pour les sauvegardes, qui est adressée par le processeur directement. Première variation par rapport à l'architecture de la console précédente : l'ajout de la RAM pour les sauvegardes dans les cartouches.
Niveau carte graphique, une différence importante est que la carte graphique est connectée à la cartouche de jeu via un autre bus, afin de pouvoir lire les sprites et textures du jeu dans la cartouche.
[[File:Architecture de la NES.png|centre|vignette|upright=2.5|Architecture de la NES]]
La différence avec l'architecture précédente est que des bus ont été fusionnés. Comme dit plus haut, le système utilise une architecture Harvard, vu que la ROM est dans la cartouche, alors que la RAM est soudée à la carte mère. Par contre, la Famicon utilise un bus dédié aux entrées-sorties. Il est utilisé pour la carte son et la carte graphique, seules les manettes sont sur un bus à part. Ce qui fait qu'on devrait plutôt parler de bus de sorties, mais passons... L'essentiel est qu'on n'est plus tout à fait dans le cas de la console précédente, avec un bus par composant.
===L'architecture de la SNES===
L'architecture de la SNES est illustrée ci-dessous. Les changements pour le processeur et la RAM sont mineurs.La RAM a augmenté en taille et passe à 128 KB. Pareil pour la RAM de la carte vidéo, qui passe à 64 KB. Par contre, on remarque un changement complet au niveau des bus, de la carte graphique et de la carte son.
[[File:Architecture de la SNES.png|centre|vignette|upright=2|Architecture de la SNES]]
La console utilise un '''bus système unique''', sur lequel tout est connecté : ROM, RAM, entrées-sorties, etc. La seule exception est pour les manettes, qui sont encore connectées directement sur le processeur, via un bus séparé. La transition vers un bus système s'explique par le fait que la console est maintenant de 16 bits, ce qui fait que les bus doivent être plus larges. Le processeur adresse des mémoires RAM et ROM plus grandes, ce qui double la taille de leurs bus. De plus, les entrées-sorties aussi ont besoin d'un bus plus large. Le processeur n'ayant pas un nombre illimité de broches, la seule solution est de fusionner les bus en un seul bus système.
Un autre changement est que la carte graphique est maintenant composée de deux circuits séparés. Encore une fois, il ne s'agit pas de coprocesseurs, mais de circuits non-programmables. Par contre, la carte son est remplacée par deux coprocesseurs audio ! De plus, les deux processeurs sont connectés à une mémoire RAM dédiée de 64 KB, comme pour la carte graphique. L'un est un processeur 8 bits (le DSP), l'autre est un processeur 16 bits.
Un point très intéressant : certains jeux intégraient des coprocesseurs dans leurs cartouches de jeu ! Par exemple, les cartouches de Starfox et de Super Mario 2 contenait un coprocesseur Super FX, qui gérait des calculs de rendu 2D/3D. Le Cx4 faisait plus ou moins la même chose, il était spécialisé dans les calculs trigonométriques, et diverses opérations de rendu 2D/3D. En tout, il y a environ 16 coprocesseurs d'utiliser et on en trouve facilement la liste sur le net. La console était conçue pour, des pins sur les ports cartouches étaient prévues pour des fonctionnalités de cartouche annexes, dont ces coprocesseurs. Ces pins connectaient le coprocesseur au bus des entrées-sorties. Les coprocesseurs des cartouches de NES avaient souvent de la mémoire rien que pour eux, qui était intégrée dans la cartouche.
===L'architecture de la Megadrive et de la néo-géo===
Passons maintenant à la console de jeu Megadrive, une console 16 bits. Elle a une architecture similaire à celle de la néo-géo, une autre console bien plus puissante, sorti à peu près en même temps. Elles intègrent deux processeurs : un Motorola 68000 qui sert de processeur principal, un Z80 qui sert de processeur dédié à l'audio. Le Z80 et le Motorola 68000 étaient deux processeurs très populaires à l'époque. Le Z80 est une sorte de version améliorée de l'Intel 8088 utilisé sur les anciens PC et de nombreuses consoles utilisaient des Z80 comme processeur principal. Il était familier pour les programmeurs de l'époque, pour son cout réduit, sa bonne disponibilité, et bien d'autres avantages liés à sa production de masse.
Le Z80 est utilisé comme co-processeur audio. Il commande un synthétiseur sonore, et est relié à sa propre mémoire, distincte de la mémoire principale. Le MC68000 est le processeur principal et a une relation maitre-esclave avec le Z80 : le MC68000 envoie des commandes au Z80, mais la communication ne va pas dans l'autre sens.
Le Motorola 68000 était un processeur 16 bits, alors que le Z80 est un processeur 8 bits. Et cette différence fait que l'on ne peut pas connecter directement les deux sur le même bus, ou du moins pas facilement. La solution retenue est d'utiliser deux bus séparés : un bus de 16 bits connecté au 68000, un bus de 8 bits connecté au Z80. Le premier bus est un bus système sur lequel est connecté le 68000, 64 kibioctets de RAM, la cartouche de jeu, et la carte graphique. Le second bus est un bus de 8 bits, plus court, relié au Z80, à un synthétiseur sonore, et 8 kibioctets de RAM
Les deux bus sont connectés à un '''''chipset''''', un circuit répartiteur, qui fait le pont entre les deux bus. Les manettes sont connectées sur le ''chipset''. Il contient un registre de 8 bits, dans lequel le MC68000 peut écrire dedans à sa guise, le registre étant adressable par le processeur. Lorsque le MC68000 écrit une valeur dedans, cela déclenche l’exécution automatique d'un programme pré-déterminé sur le Z80.
: Pour ceux qui savent ce qu'est une interruption, les valeurs écrites dans ce registre sont des numéros d'interruption, qui indiquent quelle routine d'interruption exécuter.
[[File:Architecture de la Megadrive et de la Néogeo.png|centre|vignette|upright=2.5|Architecture de la Megadrive et de la Néogeo]]
Cet exemple nous montre que les bus systèmes sont certes très simples, mais aussi inflexibles. Ils fonctionnent bien quand les composants branchés dessus sont tous des composants 8 bits, ou sont tous de 16 bits, ou tous 32 bits. Mais dès qu'on mélange composants 8, 16, 32 ou 64 bits, les choses deviennent plus compliquées. Il est alors préférable d'utiliser des bus séparés, avec des répartiteurs pour faire le pont entre les différents bus. Et nous verrons que le problème s'est posé lui aussi sur les PC.
===L'architecture des anciennes consoles Playstation : beaucoup de co-processeurs===
Les consoles que nous venons d'aborder étaient des consoles 8 ou 16 bits. A partir des consoles 32 bits, leur architecture s'est rapprochée de celle des PC, avec un usage plus complexes de répartiteurs. La XBOX était très semblable à un PC : le processeur était un Pentium 3 modifié, la carte graphique était une Geforce 3 modifiée, les 64 mébioctets de RAM était la même mémoire DDR que celle des PC, le répartiteur secondaire était un ''chipset'' nForce de NVIDIA, etc. Mais les Playstation 1, 2 et 3 se distinguent de leur contemporains. Elles disposent de très nombreux co-processeurs, qui sont en plus très variés.
La Playstation 1 a été une des premières console à utiliser les CD-ROM comme support de stockage, en remplacement des cartouches. La conséquence est que la console contient une mémoire ROM, soudée à la carte mère, de 512 kibioctets. Elle contient aussi 2 mébioctets de RAM, une carte graphique avec 1 mébioctet de mémoire vidéo, un processeur, et de quoi gérer les périphériques. Il y a un co-processeur audio spécialisé, avec 512 kibioctets de RAM, ce qui nous est familier. Par contre, les autres co-processeurs ne le sont pas.
Déjà, le lecteur de CD-ROM est associé à des circuits sur la carte mère, il y a tout un sous-système dédié au lecteur de CD. Il y a un contrôleur qui sert d'interface avec le lecteur proprement dit, mais aussi deux co-processeurs audio et 32 kibioctets de RAM. Les co-processeurs audio servent à lire des CD sans trop utiliser le second co-processeur audio, ils lui servent de complément.
Ensuite, le processeur incorpore plusieurs cœurs, avec un cœur principal et plusieurs co-processeurs. Le premier est un co-processeur système, qui est utilisé pour gérer la mémoire cache intégrée au processeur, pour des fonctionnalités appelées interruptions et exceptions, ainsi que pour configurer le processeur. Le second est un co-processeur arithmétique spécialisé dans les calculs en virgule flottante, très importants pour le rendu 3D. Enfin, il y a un décodeur vidéo, qui n'est pas un co-processeur, mais un circuit non-programmable, spécialisé dans le décodage vidéo. De nos jours, ce circuit aurait été intégré dans la carte graphique, mais il était intégré dans le processeur sur la Playstation 2.
Pour le reste, le processeur est la figure centrale de la console. Il est connecté à 4 bus : un pour la RAM, un pour la carte graphique, un pour les manettes, un autre pour le reste. Le dernier bus est connecté au système audio et au système pour le lecteur CD. Ce serait un bus d'entrée-sortie, s'il n'était pas connecté à la mémoire ROM. Vous avez bien lu : la mémoire ROM est reliée au bus d'entrée-sortie.
[[File:Architecture de la Playstation.png|centre|vignette|upright=2.5|Architecture de la Playstation]]
La Playstation 2 est composé d'un processeur, couplé à 32 Mébioctets de RAM, et d'un paquet de co-processeurs. Plus de co-processeurs que la PS1. Le processeur principal n'est pas la même que celui de la PS1, mais il a une architecture similaire. Il intègre un décodeur vidéo sur le même circuit intégré, ainsi que deux co-processeur. Les co-processeurs ne sont cependant pas les mêmes.
Le co-processeur système disparait et est remplacé par un second co-processeur arithmétique. Les deux co-processeurs arithmétiques sont spécialisés dans les nombres flottants, avec quelques différences entre les deux. Par exemple, le second co-processeur gérait des calculs trigonométriques, des exponentielles, des logarithmes, et d'autres fonctions complexes du genre ; mais pas le premier co-processeur. Ils sont reliés à 4 kibioctets de RAM pour le premier, 16 kibioctets de RAM pour le second ; qui sont intégrées dans le processeur et non-représentés dans le diagramme ci-dessous.
La PS2 intègre aussi un co-processeur d'entrées-sorties. Pour information, il s'agit du processeur principal de la Playstation 1, qui est ici utilisé différemment, suivant que l'on place un jeu PS1 ou PS1 dans la console. Si on met un jeu PS1, il est utilisé pour émuler la Playstation 1, afin de faire tourner le jeu PS1 sur la PS2. Si on met un jeu PS2, il est utilisé comme co-processeur d'entrée-sortie et fait l'interface entre CPU et entrées-sorties. Il est relié à 2 mébioctets de RAM, soit exactement la même quantité de mémoire que la Playstation 1.
Tous les périphériques sont connectés au co-processeur d'entrées-sortie. Pour cela, le co-processeur d'entrées-sortie est relié à deux bus dédiés aux périphériques. Le premier bus est relié aux manettes, aux ports USB et aux ports pour cartes mémoires. Le second bus est relié à la carte son, la carte réseau, le lecteur DVD, et un port PCMIA. Notons que la carte son intègre un co-processeur audio, qui n'est pas représenté dans le diagramme ci-dessous.
[[File:Playstation 2 architecture.png|centre|vignette|upright=2.5|Playstation 2 architecture]]
==L'architecture des PC et son évolution==
Après avoir vu les consoles, nous allons maintenant voir les anciens PC, des années 80 ou 90. Le tout premier PC était techniquement l''''IBM PC'''. Par la suite, de nombreux ordinateurs ont tenté de reproduire l'IBM PC originel, avec parfois quelques modifications mineures. De tels ordinateurs ''IBM PC compatibles'', ont été très nombreux, pour des raisons diverses. Le fait d'utiliser des composants banalisés, facilement disponibles, ainsi qu'une bonne documentation de l'IBM PC originel, a grandement aidé. Les IBM PC compatibles ont progressivement évolué pour donner les PC actuels. L'IBM PC compatible a donné naissance à de nombreux standards divers.
===L'IBM PC originel et l'IBM PC XT===
[[File:IBM PC XT 02.jpg|vignette|IBM PC XT.]]
Nous allons commencer par voir l'IBM PC originel, et son successeur : l'IBM Personal Computer XT. Nous les appelerons tous deux l'IBM PC. L'IBM PC utilisait un processeur Intel 8088, qui était un processeur 8 bits. Ils utilisaient un bus système unique, appelé le '''bus XT'''. Le bus système allait à 4.77 MHz, soit la même fréquence que le processeur. C'était un bus de 8 bits, ce qui collait parfaitement avec les processeurs 8 bits commercialisés par Intel à l'époque.
L'IBM PC comprenait une mémoire ROM avec de quoi faire fonctionner le PC. La ROM en question contenait un programme minimal, appelé le '''BIOS''', sans lequel le PC ne fonctionnait pas du tout. Il servait de base pour le système d'exploitation et MS-DOS ne fonctionnait pas sans elle. De nos jours, son rôle est plus limité : sans elle, le PC ne démarre pas. Mais nous détaillerons cela dans le prochain chapitre.
En plus de la ROM pour le BIOS, l'IBM PC avait quatre mémoires ROM dédiée au langage de programmation BASIC. Lorsque le PC démarrait, il ne bootait pas un système d'exploitation, mais lançait l'interpréteur pour le langage BASIC. De nos jours, ce serait l'équivalent d'un ordinateur qui boote directement sur du Python, à savoir la console Python que vous avez peut-être déjà utilisé si vous avez testé Python. Ceux qui ont déjà touché à un ordinateur de l'époque savent ce que ca veut dire, mais c'est malheureusement très difficile à expliquer sans ce genre d'expérience. Toujours est-il que c'était une sorte de norme à l'époque
: les ordinateurs bootaient généralement sur un interpréteur BASIC.
[[File:XT Bus pins.svg|vignette|Connecteur du bus XT.]]
Les PC étaient conçus pour qu'on branche des '''cartes d'extension''', à savoir des cartes électroniques qu'on branchait sur la carte mère, à l'intérieur du PC. Les cartes d'extension de l'époque étaient surtout des cartes son ou des cartes graphiques, mais aussi des cartes pour brancher des péripéhriques. par exemple, on pouvait ajouter deux cartes graphiques dans l'IBM PC originel : l'''IBM Monochrome Display Adapter'' et/ou la ''IBM Color Graphics Adapter''. De nos jours, les cartes son sont intégrées à la carte mère, mais les cartes graphiques sont restées des cartes d'extension.
Les cartes d'extension étaient branchées sur un '''connecteur XT''', qui était directement relié au bus XT. Le connecteur XT est illustré ci-contre, mais ne vous en souciez pas trop pour le moment. La carte mère de l'IBM PC avait 5 connecteurs de ce type, qu'on pouvait peupler avec autant de cartes d'extension. L'IBM Personal Computer XT est passé à 8 connecteurs XT, soit trois de plus.
Pour ce qui est des périphériques, l'IBM PC avait plusieurs connecteurs : un port série, un port parallèle, un port pour le clavier, et un port pour un lecteur cassette. Le clavier et le lecteur cassette étaient connectés directement sur la carte mère, qui contenait quelques circuits pour gérer le clavier. Par contre, les deux premiers n'étaient pas connectés à la carte mère. Le port série était en réalité une carte d'extension, branchée sur un connecteur XT. Et il en est de même pour le port parallèle.
Pour ce qui est des supports de stockage, l'IBM PC originel n'avait pas de disque dur et n'avait que des lecteurs de disquette. De plus, le lecteur de disquette n'était pas connecté directement sur la carte mère, mais était connecté à une carte d'extension, branchée sur un connecteur XT. La carte d'extension avait deux connecteurs, un par lecteur de disquette, ce qui fait que les deux lecteurs de disquettes pouvaient être branchés sur une seule carte d'extension. L'IBM Personal Computer XT a ajouté un disque dur, sauf sur quelques sous-modèles spécifiques.
Le PC avait aussi un petit haut-parleur capable de faire des bips.
Pour résumer, l'IBM PC originel se reposait beaucoup sur les cartes d'extension, sa carte mère contenait peu de choses. Enfin, peu de choses... Il y avait un processeur Intel 8088, éventuellement un coprocesseur flottant 8087, de la RAM, de la ROM, et des circuits intégrés assez divers. En voici la liste, certains vous seront familiers, d'autres vous seront inconnus à ce stade du cours :
* les circuits de décodage d'adresse ;
* un contrôleur DMA intel 8273 ;
* un contrôleur d'interruption 8259 ;
* un contrôleur de bus Intel 8288 pour gérer le bus XT ;
* un générateur d'horloge Intel 8284 et un diviseur de fréquence ;
* un ''timer'' Intel 8253, le même que celui étudié dans le chapitre sur les ''timers'' ;
* un contrôleur parallèle 8255.
Les multiplexeurs, registres et portes logiques, sont des circuits de décodage d'adresse, qui permettent de combiner plusieurs RAM en une seule, idem avec la mémoire ROM. Si vous verrez qu'il y a 5 mémoires ROM : une ROM pour le BIOS, et quatre autres ROM pour le BASIC. Les 4 ROM du BASIC sont combinées en une seule mémoire ROM. Pour les RAM, il y en a 8 à 32, qui sont combinées en une seule RAM de 16 à 64 kibioctets.
[[File:IBM 5150 Motherboard.svg|centre|vignette|upright=3|Carte mère de l'IBM 5150, un modèle de l'IBM PC.]]
===L'architecture d'un IBM PC compatible 16 bits===
Les PC suivants sont passés à des processeurs 16 bits, mais c'était toujours des processeurs x86 d'Intel, à savoir des Intel 286 et 386. La RAM a grossi, quelques entrées-sorties ont été ajoutées, mais l'architecture globale est plus moins resté le même. C'est surtout au niveau du bus et des périphériques que les changements majeurs ont eu lieu.
[[File:ISA Bus pins.svg|vignette|Connecteur ISA.]]
Les PC 16 bits utilisaient un bus système unique, sur lequel tout était connecté : le processeur, la RAM, la ROM, les cartes d'extension et tout le reste. Le bus en question s'appelait le '''bus AT''', mais il a rapidement été renommé en '''bus ISA''' (''Industry Standard Architecture''). Le bus ISA était prévu pour avoir une compatibilité avec le bus 8 bits de l'IBM PC originel. D'ailleurs, cela se ressent jusque dans le connecteur utilisé : le connecteur ISA est un connecteur XT qu'on a fusionné avec un second connecteur pour l'étendre de 8 à 16 bits.
Les PC 16 bits avaient toujours un port série, un port parallèle, un clavier, un lecteur de disquette et des cartes d'extension. Des disques durs pouvaient être ajoutés, aussi. Mais pour ces périphériques, un changement majeur a eu lieu comparé à l'IBM PC originel. L'IBM PC originel utilisait des cartes d'extension pour tout, sauf le clavier. Mais maintenant, les périphériques ne sont plus connectés à une carte d'extension. À la place, les circuits de la carte d'extension sont déplacés sur la carte mère. Mais n'allez pas croire qu'ils étaient connectés directement au bus ISA, il y avait des intermédiaires.
Le clavier était relié à un '''contrôleur de clavier''', qui faisait l'interface entre le connecteur du clavier et le bus ISA. Le contrôleur de clavier était appelé le ''Keyboard Controler'', abrévié en KB. Il recevait ce qui est tapé au clavier et traduisait cela en quelque chose de compréhensible par l'ordinateur.
Les autres périphériques étaient connectés à un circuit intégré dédié : l''''Intel 82091AA'''. Il était connecté au lecteur de disquette, au port série et au port parallèle. Il servait d'intermédiaire entre ces périphériques et le bus ISA. Vous pouvez le voir comme une sorte de répartiteur, mais qui ne serait pas connecté sur le processeur et la RAM
Enfin, il ne faut pas oublier les autres composants présents sur l'IBM PC originel. Le BIOS est toujours là, de même que les ''timers'' Intel 8253 PIT, le contrôleur d'interruption Intel 8259 et le contrôleur DMA Intel 8237. Les PC 16 bits ont aussi intégré une ''Real Time Clock'' (RTC). Pour rappel, c'est un composant qui permet au PC de mémoriser la date et l'heure courante, à la seconde près. Le tout est résumé dans le schéma ci-dessous.
[[File:Architecture de l'IBM PC compatible.png|centre|vignette|upright=2.5|Architecture de l'IBM PC compatible]]
Un point important est que le bus ISA allait à la même fréquence que le processeur, vu que c'était un bus système. Les processeurs de l'époque étaient des CPU 286 d'Intel, ou le 386 d'Intel. Les Intel 286 allaient de 4 MHz minimum, à 25 MHz maximum. Le 386, quant à lui, allait de 12 à 40 MHz. Le bus ISA devait aller à cette fréquence, il était synchrone avec le processeur.
Par la suite, les processeurs ont gagné en performance, ce qui fait que le bus ISA est devenu trop lent pour le processeur. Une idée a alors été de conserver le bus ISA, pour des raisons de compatibilité, mais de le reléguer comme bus secondaire. L'ordinateur contient alors deux bus : un bus système, et un bus ISA secondaire. Le lien entre les deux est réalisé par un '''pont ISA''', ''ISA Bridge'' en anglais. Le bus ISA fonctionnait alors sa fréquence usuelle, alors que le bus système était beaucoup plus rapide. Le bus système fonctionnait à une fréquence bien plus élevée, ce qui fait que le processeur pouvait communiquer à pleine vitesse, notamment avec la RAM. Le processeur n'était alors plus forcé à aller à la même fréquence que le bus ISA
[[File:Architecture de l'IBM PC compatible avec bridge ISA.png|centre|vignette|upright=2.5|Architecture de l'IBM PC compatible avec bridge ISA]]
Les PC de l'époque intégraient donc plusieurs bus séparés. Vous avez bien lu : plusieurs bus ! Ici, il s'agit de ce que j'appelle des '''bus en cascade''', à savoir qu'un bus est connecté à un autre bus par un intermédiaire. Au passage, si j'aborde ces exemples, car c'est pareil sur les ordinateurs modernes. Le pont ISA a été remplacé par des circuits différents, mais qui ont un rôle assez similaire. Le ''chipset'' de votre carte mère n'est qu'un lointain descendant du pont ISA, qui s'interface avec des bus différents.
===L'arrivée des standards AT et IDE pour les disques durs===
Initialement, les disques durs étaient placés dans l'ordinateur et étaient connectés sur le bus ISA, via une carte d'extension ISA. En clair, il fallait connecter le disque dur sur une carte d'extension, et non sur la carte mère. Les cartes d'extension en question permettaient de connecter un ou plusieurs disques durs, parfois des lecteurs de disquette supplémentaires. Les cartes ISA de ce type faisaient juste l'interface entre le bus ISA et les disques durs, rien de plus. L'interface en question a été standardisée, ce qui a donné le standard ''AT Bus Attachment'', qui a été abrévié en ATA.
Et ce n'était pas que pour les disques durs, de nombreux composants étaient dans ce cas. Une carte d'extension servait d'intermédiaire entre eux et la carte mère. Les cartes d'extension en question étaient appelées des ''Host bus adapter''.
[[File:Acculogic sIDE-4 Controller ISA.jpg|centre|vignette|upright=2|Carte ISA d'interface disque dur, de marque Acculogic.]]
Mais les choses ont rapidement évoluées, que ce soit du côté des cartes mères que du côté des disques durs. Le '''standard IDE''' a permis de brancher un disque dur directement sur la carte mère, sans passer par une carte d'interface ISA. Pour cela, la carte mère réservait un connecteur ISA pour le disque dur, renommé '''connecteur ATA'''. Pour que cela soit possible, il a fallu rajouter des circuits sur la carte mère. Tout ce qui était sur les cartes d'interface ISA s'est retrouvé sur la carte mère.
[[File:Ajout des ports IDE sur la carte mère.png|centre|vignette|upright=2|Ajout des ports IDE sur la carte mère]]
En réalité, les connecteurs ATA étaient des connecteurs ISA simplifiés. Un connecteur ISA avait en tout 98 broches, alors qu'un connecteur ATA n'en contient que 40. Les broches qui étaient inutiles pour les disques durs ont simplement été enlevées. Et qui dit connecteur spécialisé, dit câble spécialisé. Les disques durs étaient branchés sur le connecteur AT grâce à un câble ATA, sur lequel on pouvait connecter deux disques durs.
[[File:ATA Plug.svg|centre|vignette|upright=2|Connecteur ATA.]]
[[File:ATA cables.jpg|centre|vignette|upright=2|Cable ATA.]]
Il était donc possible de connecter deux disques durs sur un seul connecteur ATA. Et cette possibilité est devenue d'autant plus utile par la suite. A partir de la version 2, ATA supportait aussi les lecteurs de disquettes, les lecteurs de CD/DVD, et bien d'autres supports de stockage. Il était alors possible de connecter un lecteur CD et un disque dur sur un seul connecteur. Les cartes mères avaient généralement deux connecteurs ATA, et n'avaient pas besoin de plus. C'était suffisant pour connecter un disque dur, un lecteur de disquette et un lecteur CD, configuration courante entre les années 90 et 2000.
Un câble est donc connecté à deux supports de stockage. Pour distinguer les deux, le standard ATA ajoute une possibilité de configuration. Sur un câble, il doit y avoir un support de stockage "maitre" et un support "esclave". C'était la terminologie de l'époque, que je reproduis ici, même si elle est fortement trompeuse. N'allez pas croire que cela implique que l'un ait des avantages sur l'autre. Le support 'maitre" n'a pas droit à plus de bande passante, il n'a pas la priorité sur l'autre, rien du tout. Il s'agit juste d'un nombre qui permet de savoir avec qui le processeur communique, qui vaut 0 pour le premier support, 1 pour l'autre. Une sorte d'adresse de 1 bit, si l'on veut.
[[File:ATA-Konfiguration02.png|centre|vignette|upright=2|Configuration ATA.]]
Pour configurer un support de stockage en mode "maitre" ou "esclave", le support de stockage avait quelques pins dédiés. Il suffisait de placer un détrompeur en plastique sur les pins adéquats. Les pins se trouvaient à l'arrière du disque dur ou du lecteur de CD/DVD/Disquette/autre.
[[File:HDD Master and Slave Description.jpg|centre|vignette|upright=2|Configuration ''Master/Slave''.]]
===L'architecture d'un PC avec un processeur Intel 486===
Maintenant, passons aux ordinateurs 32 bits, avec l'exemple d'un PC avec un processeur 486 d'Intel. A cette époque, le bus ISA était devenu trop limité et était en place d'être remplacé par le bus PCI, qui avait la même fonction. De nombreuses cartes d'extension utilisaient déjà ce standard et étaient branchées sur des connecteurs PCI dédiés, différents des connecteurs ISA. Intuitivement, on se dit que le bus PCI remplaçait le bus ISA, mais les choses étaient plus compliquées. Les disques durs gardaient leur connecteur ATA, et ne passaient pas par le bus PCI. Ils avaient un bus IDE séparé, qui était un bus ISA modifié.
Là encore, les processeurs étaient devenus beaucoup plus rapides que le bus PCI. Les deux allaient à des fréquences assez différentes, ce qui fait que le bus PCI était séparé du bus système. Il y avait alors deux implémentations possibles.
* La première utilise un répartiteur unique, relié au processeur, à la RAM, au bus PCI, et au bus IDE.
* La seconde utilise un bus système séparé du bus PCI, avec un '''pont PCI''' pour faire l'interface entre les deux.
Le '''''System Controler''''' était un circuit intégré, placé sur la carte mère, qui peut servir soit de pont PCI, soit de répartiteur. Le répartiteur PCI sert d'intermédiaire avec le bus PCI, mais aussi avec le bus IDE, utilisé pour les disques durs, aussi appelé le bus ''Parallel ATA''. Il peut aussi être connecté au processeur, à la mémoire RAM, ainsi qu'à la mémoire cache, mais cela ne sert que quand il est utilisé comme répartiteur.
[[File:Architecture d'un PC utilisant un bus PCI, implémentation avec un répartiteur.png|centre|vignette|upright=2|Architecture d'un PC utilisant un bus PCI, implémentation avec un répartiteur]]
Pour des raisons de compatibilité, le bus ISA avait été conservé, aux côtés du bus PCI. Il y avait un pont ISA en plus du pont/répartiteur PCI. Une implémentation possible aurait été de connecter les deux ponts ISA et PCI à un bus système unique. Mais cette solution n'a pas été retenue. La raison est que le bus PCI et le bus ISA ont des performances très différentes. Le bus PCI est très rapide, le bus ISA beaucoup plus lent. La différence est d'un ordre de grandeur, environ. Dans ces conditions, il est possible de faire passer les communications ISA à travers le bus PCI. Pour cela, le pont ISA est directement connecté sur le pont PCI, comme illustré ci-dessous.
Et il en est de même pour le bus dédié aux disques durs. En effet, les disques durs étaient autrefois reliés au bus ISA, mais cela a changé depuis. Ils disposent maintenant de leur propre bus dédié, le '''bus IDE''', qui est un bus ISA simplifié. Et ce bus ISA simplifié était connecté directement sur le pont PCI.
[[File:Architecture de l'IBM PC compatible avec pont PCI.png|centre|vignette|upright=2|Architecture de l'IBM PC compatible avec pont PCI]]
Dans ce qui va suivre, nous allons étudier un exemple qui utilise un bus système séparé, avec un pont PCI, sans répartiteur. Voilà pour les grandes lignes, mais le schéma ci-dessous montre que tout est plus complexe. Vous remarquerez des connexions optionnelles entre le pont PCI et la mémoire RAM et la mémoire cache. La raison est que le pont PCI peut aussi servir de répartiteur en remplacement du bus système. Concrètement, on peut alors retirer le bus système. La mémoire, le bus PCI, le bus ISA, le bus IDE, le processeur et la RAM sont alors connectés au répartiteur PCI, qui sert d'intermédiaire central entre tous ces composants. Mais ce n'est pas la solution qui a été retenue dans notre exemple.
[[File:Intel486-Typ PCI System.png|centre|vignette|upright=2|PC IBM compatible avec un 486, un bus PCI et un bus ISA. Le ''host bus'' est le bus système.]]
Le pont ISA sert ici d'intermédiaire entre le bus système et le bus ISA. De plus, il a été amélioré sur de nombreux points. Il inclut notamment des circuits qui étaient autrefois sur la carte mère, à savoir le contrôleur DMA 82C87 et le contrôleur d'interruption 82C59, ainsi que les ''timers'' Intel 82C54. Les composants restants sont eux reliés sur un quatrième bus : le bus X, l'ancêtre du bus ''Low Pin Count''. Le bus X était celui du BIOS, du contrôleur de clavier, de la ''Real Time Clock'', et du contrôleur de périphérique 82091AA d'Intel.
[[File:ISA Bridge schematic.png|centre|vignette|upright=2|ISA Bridge.]]
===L'architecture des PC des années 90-2000===
Par la suite, les ponts PCI et ISA ont évolué avec l'évolution des bus de l'ordinateur. Le bus ISA a progressivement été remplacé par d'autres bus, comme le bus ''Low Pin Count'', le bus PCI a été remplacé par le PCI Express, d'autres bus ont été ajoutés, etc. Mais la séparation du ''chipset'' en deux a été conservée.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Le pont PCI et le pont ISA ont été remplacés respectivement par le '''pont nord''' et le '''pont sud''', plus connus par leurs noms anglais de ''northbridge'' et de ''southbridge''. Le pont nord servait d'interface entre le processeur, la mémoire et la carte graphique et est connecté à chacun par un bus dédié. Il intégrait aussi le contrôleur mémoire. Le pont sud est le répartiteur pour les composants lents, à savoir l'USB, l'Ethernet, etc. Le bus qui relie le processeur au pont nord était appelé le '''''Front Side Bus''''', abrévié en FSB.
[[File:IMac Chipset.png|centre|vignette|upright=2|Chipset séparé en northbridge et southbridge.]]
Un point important est que le bus PCI est devenu un bus assez lent, ce qui fait qu'il a finit par être connecté au pont sud. Le pont PCI est donc devenu le pont sud, dans le courant des années 2000. Durant un moment, un équivalent du pont ISA a subsisté dans un circuit de '''''Super IO'''''. Concrètement, il s'occupait du lecteur de disquette, du port parallèle, du port série, et des ports PS/2 pour le clavier et la souris. Mais il ne gérait pas le bus ISA, mais son remplaçant, le bus ''Low Pin Count''.
[[File:Motherboard diagram fr.svg|centre|vignette|upright=1.5|Carte mère avec circuit Super IO.]]
===L'architecture des PC depuis les années 2000===
Depuis la sortie du processeur AMD Athlon 64, le pont nord a été fusionné dans le processeur. La fusion ne s'est pas faite en une fois, des fonctionnalités ont progressivement été progressivement intégrées dans le processeur. Le pont sud est resté, mais il a alors été progressivement connecté directement au processeur. La raison derrière cette intégration est que les processeurs avaient de plus en plus de transistors à leur disposition. Ils en ont profité pour intégrer le pont nord. Et cela permettait de simplifier le câblage des cartes mères, sans pour autant rendre vraiment plus complexe la fabrication du processeur. Les industriels y trouvent leur compte.
La première étape a été l'intégration du contrôleur mémoire a été intégré au processeur. Concrètement, le résultat était que la mémoire RAM n'était plus connectée au pont nord, mais était connectée directement au processeur ! Il y a donc eu un retour d'un bus mémoire, mais spécialisé pour la mémoire RAM. En théorie, une telle intégration permet diverses optimisations quant aux transferts avec la mémoire RAM. Les transferts ne passent pas par un répartiteur, ce qui réduit le temps d'accès à la mémoire RAM. Ajoutons de sombres histoires de prefetching, d'optimisation des commandes, et j'en passe. Toujours est-il que le pont nord ne servait alors d'intermédiaire que pour les ports PCI Express, et le pont sud.
[[File:X58 Block Diagram.png|centre|vignette|upright=2|Chipset X58 d'Intel.]]
Par la suite, la carte graphique fût aussi connectée directement sur le processeur. Le processeur incorpore pour cela des contrôleurs PCI-Express. Le pont nord a alors disparu complétement, son intégration dans le processeur était complète. Sur les cartes mères Intel récentes, le pont sdud subsiste, il est appelé le ''Platform Controler Hub'', ou PCH. L'organisation des bus sur la carte mère qui résulte de cette connexion du processeur à la carte graphique, est illustrée ci-dessous, avec l'exemple du PCH.
[[File:Intel 5 Series architecture.png|centre|vignette|upright=2|Intel 5 Series architecture]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'interface électrique entre circuits intégrés et bus
| prevText=L'interface électrique entre circuits intégrés et bus
| next=La hiérarchie mémoire
| nextText=La hiérarchie mémoire
}}
</noinclude>
q9ows4ayi5w760lxgk90w8u0oduyn5a
775631
775630
2026-11-02T15:02:14Z
Mewtow
31375
/* L'intérieur d'un processeur */
775631
wikitext
text/x-wiki
Dans les chapitres précédents, nous avons vu comment représenter de l'information, la traiter et la mémoriser avec des circuits. Mais un ordinateur n'est pas qu'un amoncellement de circuits et est organisé d'une manière bien précise. Il est structuré autour de trois circuits principaux :
* un '''processeur''', qui manipule l'information et donne un résultat ;
* une '''mémoire''' qui mémorise les données à manipuler ;
* les '''entrées/sorties''', qui permettent à l'ordinateur de communiquer avec l'extérieur.
[[File:Architecture Von Neumann.png|centre|vignette|upright=2|Architecture d'un système à mémoire.]]
Pour faire simple, le processeur est un circuit qui s'occupe de faire des calculs. Rien d'étonnant à cela. Je rappelle que tout est codé par des nombres dans un ordinateur, ce qui fait que manipuler des nombres revient simplement à faire des calculs. Un ordinateur n'est donc qu'une grosse calculatrice améliorée, et le processeur est le composant qui fait les calculs.
La mémoire s'occupe purement de la mémorisation des données, des nombres sur lesquelles faire des calculs. Pour être plus précis, il y a deux mémoires : une pour les données proprement dites, une autre pour le programme à exécuter. La première est la '''mémoire RAM''', la seconde est la '''mémoire ROM'''. Nous détaillerons ce que sont ces deux mémoires dans la suite du chapitre, mais sachez que nous avions déjà rencontré ces deux types de mémoires dans les chapitres sur les registres et les mémoires adressables.
Les entrées-sorties permettent au processeur et à la mémoire de communiquer avec l'extérieur et d'échanger des informations avec des périphériques. Les '''périphériques''' regroupent, pour rappel, tout ce est branché sur un ordinateur, mais n'est pas à l'intérieur de celui-ci.
Le processeur, les mémoires et les entrées-sorties communiquent ensemble via un '''réseau d'interconnexions'''. Le terme est assez barbare, mais rien de compliqué sur le principe. C'est juste un ensemble de fils électriques qui relie les différents éléments d'un ordinateur. Les interconnexions sont souvent appelées le bus de communication, mais le terme est un abus de langage, comme on le verra plus bas.
Afin de simplifier les explications, on va supposer que le réseau d'interconnexion est le suivant. Tout est connecté au processeur. Il y a des interconnexions entre le processeur et la mémoire RAM, d'autres interconnexions entre processeur et mémoire ROM, et d'autres entre le processeur et les entrées-sorties. Nous verrons que d'autres réseaux d'interconnexions fusionnent certaines interconnexions, pour les partager entre la ROM et la RAM, par exemple. Mais pour le moment, gardez le schéma ci-dessous en tête.
[[File:Réseau d'interconnexion avec un processeur au centre.png|centre|vignette|upright=2|Réseau d'interconnexion avec un processeur au centre]]
==Les mémoires RAM et ROM==
La mémoire est le composant qui mémorise des informations, des données. Dans la majorité des cas, la mémoire est composée de plusieurs '''cases mémoire''', chacune mémorisant plusieurs bits, le nombre de bits étant identique pour toutes les cases mémoire. Dans le cas le plus simple, une case mémoire mémorise un '''octet''', un groupe de 8 bits. Mais les mémoires modernes mémorisent plusieurs octets par case mémoire : elles ont des cases mémoires de 16, 32 ou 64 bits, soit respectivement 2/4/8 octets. De rares mémoires assez anciennes utilisaient des cases mémoires contenant 1, 2, 3, 4, 5, 6 7, 13, 17, 23, 36 ou 48 bits. Mais ce n'était pas des mémoires électroniques, aussi nous allons les passer sous silence.
Tout ce qu'il faut savoir est que la quasi-totalité des mémoires électronique a un ou plusieurs octets par case mémoire. Pour simplifier, vous pouvez imaginer qu'une mémoire RAM est un regroupement de registre, chacun étant une case mémoire. C'est une description pas trop mauvaise pour décrire les mémoires RAM, qu'on abordera dans ce qui suit.
{|class="wikitable"
|+ Contenu d'une mémoire, case mémoire de 16 bits (deux octets)
|-
! Case mémoire N°1
| 0001 0110 1111 1110
|-
! Case mémoire N°2
| 1111 1110 0110 1111
|-
! Case mémoire N°3
| 0001 0000 0110 0001
|-
! Case mémoire N°4
| 1000 0110 0001 0000
|-
! Case mémoire N°5
| 1100 1010 0110 0001
|-
! ...
| ...
|-
! Case mémoire N°1023
| 0001 0110 0001 0110
|-
! Case mémoire N°1024
| 0001 0110 0001 0110
|}
Dans ce cours, il nous arrivera de partir du principe qu'il y a un octet par case mémoire, par souci de simplification. Mais ce ne sera pas systématique. De plus, il nous arrivera d'utiliser le terme adresse pour parler en réalité de la case mémoire associée, par métonymie.
===La capacité mémoire===
Bien évidemment, une mémoire ne peut stocker qu'une quantité finie de données. Et à ce petit jeu, certaines mémoires s'en sortent mieux que d'autres et peuvent stocker beaucoup plus de données que les autres. La '''capacité''' d'une mémoire correspond à la quantité d'informations que celle-ci peut mémoriser. Plus précisément, il s'agit du nombre maximal de bits qu'une mémoire peut contenir. Elle est le produit entre le nombre de cases mémoire, et la taille en bit d'une case mémoire.
Toutes les mémoires actuelles utilisant des cases mémoire d'un ou plusieurs octets, ce qui nous arrange pour compter la capacité d'une mémoire. Au lieu de compter cette capacité en bits, on préfère mesurer la capacité d'une mémoire avec le nombre d'octets qu'elle contient. Mais les mémoires des PC font plusieurs millions ou milliards d'octets. Pour se faciliter la tâche, on utilise des préfixes pour désigner les différentes capacités mémoires. Vous connaissez sûrement ces préfixes : kibioctets, mébioctets et gibioctets, notés respectivement Kio, Mio et Gio.
{|class="wikitable"
|-
!Préfixe!!Capacité mémoire en octets!!Puissance de deux
|-
||Kio||1024||2<sup>10</sup> octets
|-
||Mio||1 048 576||2<sup>20</sup> octets
|-
||Gio||1 073 741 824||2<sup>30</sup> octets
|}
On peut se demander pourquoi utiliser des puissances de 1024, et ne pas utiliser des puissances un peu plus communes ? Dans la majorité des situations, les électroniciens préfèrent manipuler des puissances de deux pour se faciliter la vie. Par convention, on utilise souvent des puissances de 1024, qui est la puissance de deux la plus proche de 1000. Or, dans le langage courant, kilo, méga et giga sont des multiples de 1000. Quand vous vous pesez sur votre balance et que celle-ci vous indique 58 kilogrammes, cela veut dire que vous pesez 58 000 grammes. De même, un kilomètre est égal à 1000 mètres, et non 1024 mètres.
Autrefois, on utilisait les termes kilo, méga et giga à la place de nos kibi, mebi et gibi, par abus de langage. Mais peu de personnes sont au courant de l'existence de ces nouvelles unités, et celles-ci sont rarement utilisées. Et cette confusion permet aux fabricants de disques durs de nous « arnaquer » : Ceux-ci donnent la capacité des disques durs qu'ils vendent en kilo, méga ou giga octets : l’acheteur croit implicitement avoir une capacité exprimée en kibi, mébi ou gibi octets, et se retrouve avec un disque dur qui contient moins de mémoire que prévu.
===Lecture et écriture : mémoires ROM et RWM===
Pour simplifier grandement, on peut grossièrement classer les mémoires en deux types : les ''Read Only Memory'' et les ''Read Write Memory'', aussi appelées mémoires ROM et mémoires RWM. Pour les '''mémoires ROM''', on ne peut pas modifier leur contenu. On peut y récupérer une donnée ou une instruction : on dit qu'on y accède en lecture. Mais on ne peut pas modifier les données qu'elles contiennent. Quant aux '''mémoires RWM''', on peut y accéder en lecture (récupérer une donnée stockée en mémoire), mais aussi en écriture : on peut stocker une donnée dans la mémoire, ou modifier une donnée existante.
Tout ordinateur contient au minimum une ROM et une RWM (souvent une mémoire RAM), les deux n'ont pas exactement le même rôle. Pour simplifier, la mémoire ROM mémorise le programme à exécuter, la mémoire RWM stocke des données. Il a existé des ordinateurs où la mémoire RWM était une mémoire magnétique, voire acoustique, mais ce n'est plus le cas de nos jours. Pour les ordinateurs modernes, la mémoire RWM est une mémoire électronique appelée la '''mémoire RAM'''. La mémoire RAM est utilisée pour stocker temporairement des données que le processeur doit manipuler. La mémoire RAM est donc une mémoire RWN qui a les caractéristiques suivantes :
* La mémoire RAM est une mémoire électronique, fabriquée avec des semi-conducteurs.
* La mémoire RAM s'efface complètement quand on coupe l'alimentation de l'ordinateur (on dit qu'elle est volatile).
* La mémoire RAM possède un temps d'accès approximativement constant aux données (quelle que soit l'adresse).
Et ce sont des caractéristiques que d'autres mémoires RWM n'ont pas :
* Une mémoire RWM peut être magnétique (mémoires à tore de ferrite), acoustique, basées sur des CRTs (tubes Wlliams).
* Une mémoire RWM peut ne pas être volatile et donc conserver les données écrites. L'exemple type étant les mémoires à tore de ferrite. Précisons cependant qu'il existe des prototypes de mémoires FERAM, MRAM et autres, qui sont des RAM non-volatiles.
* Une mémoire RWN n'a pas forcément un temps d'accès constant aux données, ce n'est pas garanti pour les mémoires RWM. Par exemple, les anciens tambours magnétiques des anciens ''mainframes'' étaient des mémoires RWM non-volatiles, utilisées comme stockage temporaire.
Outre le programme à exécuter, la mémoire ROM peut mémoriser des constantes, des données qui ne changent pas. Elles ne sont jamais modifiées et gardent la même valeur quoi qu'il se passe lors de l'exécution du programme. En conséquence, elles ne sont jamais accédées en écriture durant l'exécution du programme, ce qui fait que leur place est dans une mémoire ROM. La mémoire RWM est alors destinée aux données temporaires, qui changent ou sont modifiées lors de l'exécution du programme, et qui sont donc manipulées aussi bien en lecture et en écriture. La mémoire RWM mémorise alors les variables du programme à exécuter, qui sont des données que le programme va manipuler. Pour les systèmes les plus simples, la mémoire RWM ne sert à rien de plus.
[[File:Espaces d'adressage sur une archi harvard modifiée.png|centre|vignette|upright=2.5|Espaces d'adressage sur une archi harvard modifiée]]
Pour donner un exemple de données stockées en ROM, on peut prendre l'exemple des anciennes consoles de jeu 8 et 16 bits. Les jeux vidéos sur ces consoles étaient placés dans des cartouches de jeu, précisément dans une mémoire ROM à l'intérieur de la cartouche de jeu. La ROM mémorisait non seulement le code du jeu, le programme du jeu vidéo, mais aussi les niveaux et les ''sprites'' et autres données graphiques.
Une conséquence est que les consoles 8/16 bits n'avaient pas besoin de beaucoup de RAM, comparé aux ordinateurs de l'époque, vu qu'une grande partie des données utiles étaient dans une ROM directement accessible par le processeur. À l'opposé, les micro-ordinateurs devaient copier les données d'un jeu depuis une disquette dans la mémoire RAM, ce qui demandait d'avoir plus de RAM. Le passage au support CD sur les consoles 32 bits a eu la même conséquence. Le processeur ne pouvant pas lire directement le CD à sa guise, il fallait copier les données du CD en RAM. D'où l'apparition de temps de chargement assez longs, inexistants sur support cartouche.
===L'adressage mémoire===
Sur une mémoire RAM ou ROM, on ne peut lire ou écrire qu'une case mémoire, qu'un registre à la fois : une lecture ou écriture ne peut lire ou modifier qu'une seule case mémoire. Techniquement, le processeur doit préciser à quel case mémoire il veut accéder à chaque lecture/écriture. Pour cela, chaque case mémoire se voit attribuer un nombre binaire unique, l''''adresse''', qui va permettre de le sélectionner et de l'identifier celle-ci parmi toutes les autres. En fait, on peut comparer une adresse à un numéro de téléphone (ou à une adresse d'appartement) : chacun de vos correspondants a un numéro de téléphone et vous savez que pour appeler telle personne, vous devez composer tel numéro. Les adresses mémoires en sont l'équivalent pour les cases mémoire.
[[File:Adressage mémoire.png|centre|vignette|upright=2|Exemple : on demande à la mémoire de sélectionner la case mémoire d'adresse 1002 et on récupère son contenu (ici, 17).]]
L'adresse mémoire est générée par le processeur. Le processeur peut parfaitement calculer des adresses, en extraire du programme qu'il exécute, et bien d'autres choses. Nous détaillerons d'ailleurs les mécanismes pour dans les chapitres portant sur les modes d'adressage du processeur. Mais pour le moment, nous avons juste besoin de savoir que c'est le processeur qui envoie des adresses aux mémoires RAM et ROM.
Les adresses générées par le processeur sont alors envoyées à la RAM ou la ROM via une connexion dédiée, un ensemble de fils qui connecte le processeur à la mémoire : le '''bus d'adresse mémoire'''. L'adresse sélectionne une case mémoire, le processeur peut alors récupérer la donnée dedans pour une lecture, écrire une donnée pour l'écriture. Pour cela, un second ensemble de fil connecte le processeur à la RAM/ROM, mais cette fois-ci pour échanger des données. Il s'agit du '''bus de données mémoire'''. Les deux sont souvent regroupés sous le terme de '''bus mémoire'''.
Un ordinateur contient toujours une RAM et une ROM, ce qui demande aux bus mémoire de s'adapter à la présence de deux mémoires. Il y a alors deux solutions, illustrées dans les deux schémas ci-dessous. Avec la première, il y a un seul bus mémoire partagé entre la RAM et la ROM, comme illustré ci-dessous. Une autre solution utilise deux bus séparés : un pour la RAM et un autre pour la ROM. Nous verrons les différences pratiques entre les deux à la fin du chapitre. Pour le moment, nous allons partir du principe qu'il y a un bus pour la mémoire ROM, et un autre bus pour la RAM.
[[File:CPT-System-Architecture-gapfill1-ANS.svg|centre|vignette|upright=2|Architecture avec une ROM et une RAM.]]
[[File:Réseau d'interconnexion avec un processeur au centre.png|centre|vignette|upright=2|Réseau d'interconnexion avec un processeur au centre]]
===L'alignement mémoire : introduction===
Plus haut, nous avions dit qu'il y a une adresse par case mémoire, chaque case mémoire contenant un ou plusieurs octets. Mais les processeurs modernes partent du principe que la mémoire a un octet par adresse, pas plus. Et ce même si la mémoire reliée au processeur utilise des cases mémoires de 2, 3, 4 octets ou plus. D'ailleurs, la majorité des mémoires RAM actuelle a des cases mémoires de 64 bits, soit 8 octets par case mémoire. Les raisons à cela sont multiple, mais nous les verrons en détail dans le chapitre sur l'alignement mémoire. Toujours est-il qu'il faut distinguer les '''adresses mémoire''' et les '''adresses d'octet''' gérées par le processeur.
Le processeur génère des adresses d'octet, qui permettent de sélectionner un octet bien précis. L'adresse d'octet permet de sélectionner un octet parmi tous les autres. Mais la mémoire ne comprend pas directement cette adresse d'octet. Heureusement, l'octet en question est dans une case mémoire bien précise, qui a elle-même une adresse mémoire bien précise. L'adresse d'octet est alors convertie en une adresse mémoire, qui sélectionne la case mémoire adéquate, celle qui contient l'octet voulu. La case mémoire entière est lue, puis le processeur ne récupère que les données adéquates. Pour cela, des circuits d'alignement mémoire se chargent de faire la conversion entre adresses du processeur et adresse mémoire. Nous verrons cela dans le détail dans le chapitre sur l'alignement mémoire.
Il existe des mémoires qui n'utilisent pas d'adresses mémoire, mais passons : ce sera pour la suite du cours.
==Le processeur==
Dans les ordinateurs, l'unité de traitement porte le nom de '''processeur''', ou encore de '''''Central Processing Unit''''', abrévié en CPU. Le rôle principal du processeur est de faire des calculs. La plupart des processeurs actuels supportent au minimum l'addition, la soustraction et la multiplication. Quelques processeurs ne gèrent pas la division, qui est une opération très gourmande en circuit, peu utilisée, très lente. Il arrive que des processeurs très peu performants ne gèrent pas la multiplication, mais c'est assez rare.
Un processeur ne fait pas que des calculs. Tout processeur est conçu pour effectuer un nombre limité d'opérations bien précises, comme des calculs, des échanges de données avec la mémoire, etc. Ces opérations sont appelées des '''instructions'''. Les plus intuitives sont les '''instructions arithmétiques''', qui font des calculs, comme l'addition, la soustraction, la multiplication, la division. Mais il y a aussi des '''instructions d'accès mémoire''', qui échangent des données entre la mémoire RAM et le processeur. Les autres instructions ne sont pas très intuitives, aussi passons-les sous silence pour le moment, tout deviendra plus clair dans les chapitres sur le processeur.
===Le processeur exécute un programme, une suite d'instructions===
Tout processeur est conçu pour exécuter une suite d'instructions dans l'ordre demandé, cette suite s'appelant un '''programme'''. Ce que fait le processeur est défini par la suite d'instructions qu'il exécute, par le programme qu'on lui demande de faire. Les instructions sont exécutées dans un ordre bien précis, les unes après les autres. L'ordre en question est décidé par le programmeur. La totalité des logiciels présents sur un ordinateur sont des programmes comme les autres.
Le programme à exécuter est stockée dans la mémoire de l'ordinateur. C'est ainsi que l'ordinateur est rendu programmable : modifier le contenu de la mémoire permet de changer le programme exécuté. Mine de rien, cette idée de stocker le programme en mémoire est ce qui a fait que l’informatique est ce qu'elle est aujourd’hui. C'est la définition même d'ordinateur : appareil programmable qui stocke son programme dans une mémoire modifiable.
Une instruction est codée comme les données : sous la forme de suites de bits. Telle suite de bit indique qu'il faut faire une addition, telle autre demande de faire une soustraction, etc. Pour simplifier, nous allons supposer qu'il y a une instruction par adresse mémoire. Sur la grosse majorité des ordinateurs, les instructions sont placées les unes à la suite des autres dans l'ordre où elles doivent être exécutées. Un programme informatique n'est donc qu'une vulgaire suite d'instructions stockée quelque part dans la mémoire de l'ordinateur.
{|class="wikitable"
|+ Exemple de programme informatique
|-
! Adresse
! Instruction
|-
! 0
| Copier le contenu de l'adresse 0F05 dans le registre numéro 5
|-
! 1
| Charger le contenu de l'adresse 0555 dans le registre numéro 4
|-
! 2
| Additionner ces deux nombres
|-
! 3
| Charger le contenu de l'adresse 0555
|-
! 4
| Faire en XOR avec le résultat antérieur
|-
! ...
| ...
|-
! 5464
| Instruction d'arrêt
|}
Pour exécuter une suite d'instructions dans le bon ordre, le processeur détermine à chaque cycle quelle est la prochaine instruction à exécuter. Pour cela, le processeur mémorise l'adresse de l'instruction en cours dans un registre : le '''Program Counter'''. Je rappelle que des instructions consécutives sont dans des adresses consécutives. Pour passer à la prochaine instruction, il suffit donc d'incrémenter le ''program counter''.
: Si une instruction prend plusieurs octets, plusieurs adresses, il suffit de l'incrémenter du nombre d'octets/adresses.
D'autres processeurs font autrement : chaque instruction précise l'adresse de la suivante, directement dans la suite de bit représentant l'instruction en mémoire. Ces processeurs n'ont pas besoin de calculer une adresse qui leur est fournie sur un plateau d'argent. Sur des processeurs aussi bizarres, pas besoin de stocker les instructions en mémoire dans l'ordre dans lesquelles elles sont censées être exécutées. Mais ces processeurs sont très très rares et peuvent être considérés comme des exceptions à la règle.
Nous venons de voir qu'un processeur contient un registre appelé le ''program counter''. Mais il n'est pas le seul. Pour pouvoir fonctionner, tout processeur doit mémoriser un certain nombre d’informations nécessaires à son fonctionnement, qui sont mémorisées dans des '''registres de contrôle'''. La plupart ont des noms assez barbares (registre d'état, ''program counter'') et nous ne pouvons pas en parler à ce moment du cours. Nous les verrons en temps voulu, mais il est important de préciser qu'ils existent.
===L'intérieur d'un processeur===
Fort de ce que nous savons, nous pouvons expliquer ce qu'il y a à l'intérieur d'un processeur. Le premier point est qu'un processeur fait des calculs, ce qui implique qu'il contient des circuits de calcul. Ils sont regroupés dans une ou plusieurs '''unités de calcul'''. Nous avons déjà vu comment fabriquer une unité de calcul simple, dans un chapitre dédié, et c'est la même qui est présente dans un processeur. Du moins dans les grandes lignes, les circuits des processeurs modernes étant particulièrement optimisés. Il en est de même pour les autres circuits de calcul comme ceux pour les multiplications/division/autres.
Si le processeur fait des calculs, qu'en est-il des opérandes ? Et où sont mémorisés les résultats des opérations ? Pour cela, le processeur incorpore des '''registres généraux''', qui mémorisent les opérandes et résultats des instructions. Ils mémorisent des données, contrairement aux registres de contrôle mentionnés plus haut. Le nombre de registres généraux dépend grandement du processeur. Les tout premiers processeurs se débrouillaient avec un seul registre, mais les processeurs actuels utilisent plusieurs registres, pour mémoriser plusieurs opérandes/résultats. Mais la présence de registres est source de pas mal de petites complications que nous expliquerons dans les chapitres sur le processeur.
Le processeur contient aussi de quoi communiquer avec la mémoire RAM. Il y a une unité dédiée, un circuit dédié pour, appelé l'''unité mémoire'''. Elle permet d'échanger les données entre la RAM et les registres, mais aussi de simplement lire les instructions à exécuter.
Le processeur contient enfin un circuit pour interpréter les instructions, appelé l''''unité de contrôle'''. Elle lit les instructions depuis la mémoire, interprète la suite de bit associée, et commande le reste du processeur pour qu'il exécute l'instruction. Ses fonctions sont assez variées, mais nous allons simplifier en disant qu'elle configure l'unité de calcul et les registres pour faire le bon calcul. Son rôle est d'analyser la suite de bit qui constitue l'instruction, et d'en déduire quelle opération effectuer. Elle gère aussi l'accès à la mémoire RAM, et notamment ce qui est envoyé sur son bus d'adresse.
: Dans ce qui suit, on suppose que le ''program counter'' fait partie de l'unité de contrôle.
Pour résumer, un processeur contient une unité de calcul, des registres et une interface avec la mémoire RAM. Le tout est interconnecté, afin de pouvoir échanger des données. L’ensemble forme le '''chemin de données''', nom qui trahit le fait que c'est là que les données se déplacent et sont traitées. Il faut aussi ajouter l'unité de contrôle pour commander le tout. Elle lit les instructions en mémoire, puis commande le chemin de données pour que l'instruction soit exécutée correctement.
[[File:Microarchitecture d'un processeur.png|centre|vignette|upright=2|Microarchitecture d'un processeur]]
Un processeur parait donc assez simple expliqué comme ça, mais il y a de nombreuses subtilités. L'une d'entre elle est liée aux registres, et notamment à la manière dont on les utilise. Pour expliquer ces subtilités, nous allons voir comment les premiers processeurs fonctionnaient, avant de passer aux processeurs un peu plus modernes. Les tout premiers processeurs n'utilisaient qu'un seul registre, ce qui fait que l'utilisation des registres était très simple. Le passage à plusieurs registres a complexifié le tout.
===D'où viennent les adresses ?===
Il est maintenant temps de répondre à une question qui s'était posée dans la section sur l'adressage : d'où viennent les adresses envoyées à la mémoire ? Pour ce qui est des adresses des instructions, on connait déjà la réponse : c'est le ''program counter'' qui gère tout. Mais pour les données, il y a deux possibilités, qui correspondent à deux types de données : les données statiques et les données dynamiques.
Les '''données statiques''' sont les plus simples : elles existent durant toute la durée de vie du programme. Tant que celui-ci s'exécute, il aura besoin de ces données. En conséquence, il leur réserve une place en mémoire, qui est toujours la même. La donnée se situe donc à une adresse bien précise, qui ne change jamais. Attention cependant : les données peuvent être modifiées, changer de valeur. Le programme écrit dans les donnée statiques, c'est même assez fréquent. Ce sont ne sont pas forcément des données constantes !
Pour les données statiques, elles sont toujours placées à la même adresse mémoire. Pour le dire autrement, l'adresse mémoire est une constante, qui ne change pas. L'adresse est connue avant d’exécuter le programme, le programme a été codé pour utiliser cette adresse pour telle donnée, on a réservé une adresse pour la donnée voulue. Et même si vous quittez le programme et vous le relancez plusieurs jours après, l'adresse mémoire sera la même avant et après.
Et cela permet d'utiliser l''''adressage direct'''. L'idée est que les instructions précisent donc l'adresse à lire ou écrire. Pour cela, l'adresse est intégrée dans l’instruction elle-même. Pour rappel, l'instruction est codée par une suite de bit en mémoire RAM/ROM, dont certains précisent l'opération à faire, les autres servant à autre chose. L'idée est que certains bits précisent l'adresse mémoire de la donnée à lire. Les instructions sont donc du genre :
* ''LOAD 56'' - lit l'adresse numéro 56 et copie son contenu dans un registre;
* ''STORE R5 , adress 99'', copie le registre R5 dans l'adresse 99 ;
* ''ADD R5 , adress 209'' : additionne le registre R5 avec le contenu de l'adresse 209.
S'il existe des données statiques, c'est signe qu'il existe des '''données dynamiques'''. Ces dernières sont des données qui sont créées ou détruites selon les besoins. Pour comprendre d'où viennent ces données dynamiques, prenons le cas d'une personne qui écrit du texte sur un ordinateur. Le texte qu'il écrit est mémorisé dans la RAM de l’ordinateur, puis est sauvegardé sur le disque dur quand il sauvegarde son document. Au fur et à mesure qu'il écrit du texte, la RAM utilisée par ce texte augmente. Donc, ce texte est une donnée dynamique, dont la taille varie dans le temps.
Pour gérer des données dynamiques, la plupart des systèmes d'exploitation incorporent des fonctionnalités d''''allocation mémoire'''. Derrière ce nom barbare, se cache quelque chose de simple : les programmes peuvent réclamer de la mémoire au système d'exploitation, pour y placer les données qu'ils souhaitent. Les langages de programmation bas niveau supportent des fonctions comme malloc(), qui permettent de demander un bloc de mémoire de N octets à l'OS, qui doit alors accommoder la demande. De même, un programme peut libérer de la mémoire qu'il n'utilise plus avec des fonctions comme free().
Avec l'allocation mémoire, les données n'ont pas de place fixe en mémoire. Leur adresse mémoire peut varier d'une exécution du programme à l'autre. Pire que ça : les données peuvent être déplacées dans la mémoire RAM, si besoin. En clair : l'adresse est déterminée lors de l'exécution du programme. L'adresse est alors soit calculée, soit lue depuis la mémoire RAM, soit déterminée autrement. Toujours est-il qu'elle se retrouve dans un registre général. Pour gérer ces adresse variables, les processeurs utilisent l''''adressage indirect'''. L'adressage indirect permet d'utiliser une adresse qui est dans un registre de données. L'adresse en question peut être envoyée à la mémoire RAM sans problème, le processeur peut automatiquement connecter le registre adéquat sur le bus d'adresse.
Au tout début de l'informatique, les processeurs ne supportaient que l'adressage direct, pas plus. L'adressage indirect n'était tout simplement pas possible. Avec l'adressage direct, l'adresse à lire est extraite directement des instructions, par l'unité de contrôle. Le bus d'adresse de la RAM est alors relié directement à l'unité de contrôle, le bus de données est relié aux registres.
[[File:Architecture Harvard avec adressage direct uniquement.png|centre|vignette|upright=2|Architecture Harvard avec adressage direct uniquement]]
Mais dès les années 70, l'adressage indirect est apparu, rendant la programmation bien plus simple. Mais cela a demandé quelques adaptation au niveau du processeur. Avec l'adressage indirect, le bus d'adresse est connecté aux registres. Il a donc fallu rajouter un multiplexeur pour que le processeur décide de relier le bus d'adresse soit aux registres, soit à l'unité de contrôle.
===Un ordinateur peut avoir plusieurs processeurs===
La plupart des ordinateurs n'ont qu'un seul processeur, ce qui fait qu'on désigne avec le terme d''''ordinateurs mono-processeur'''. Mais il a existé (et existe encore) des '''ordinateurs multi-processeurs''', avec plusieurs processeurs sur la même carte mère. L'idée était de gagner en performance : deux processeurs permettent de faire deux fois plus de calcul qu'un seul, quatre permettent d'en faire quatre fois plus, etc. C'est très courant sur les supercalculateurs, des ordinateurs très puissants conçus pour du calcul industriel ou scientifique, mais aussi sur les serveurs ! Dans le cas le plus courant, ils utilisent plusieurs processeurs identiques : on utilise deux processeurs Core i3 de même modèle, ou quatre Pentium 3, etc.
Pour utiliser plusieurs processeurs, les programmes doivent être adaptés. Pour cela, il y a plusieurs possibilités :
* Une première possibilité, assez intuitive, est d’exécuter des programmes différents sur des processeurs différents. Par exemple, on exécute le navigateur web sur un processeur, le lecteur vidéo sur un autre, etc.
* La seconde option est de créer des programmes spéciaux, qui utilisent plusieurs processeurs. Ils répartissent les calculs à faire sur les différents processeurs. Un exemple est la lecture d'une vidéo sur le web : un processeur peut télécharger la vidéo pendant le visionnage et bufferiser celle-ci, un autre processeur peut décoder la vidéo, un autre décoder l'audio. De tels programmes restent des suites d'instructions, mais ils sont plus complexes que les programmes normaux, aussi nous les passons sous silence.
* La troisième option est d’exécuter le même programme sur les différents processeurs, mais chaque processeur traite son propre ensemble de données. Par exemple, pour un programme de rendu 3D, quatre processeurs peuvent s'occuper chacun d'une portion de l'image.
[[File:Architecture de Von Neumann Princeton multi processeurs.svg|centre|vignette|upright=2|Architecture de Von Neumann Princeton multi processeurs]]
De nos jours, les ordinateurs grand public les plus utilisés sont dans un cas intermédiaire, ils ne sont ni mono-, ni multi-processeur. Ils n'ont qu'un seul processeur, dans le sens où si on ouvre l'ordinateur et qu'on regarde la carte mère, il n'y a qu'un seul processeur. Mais ce processeur est en réalité assez similaire à un regroupement de plusieurs processeurs dans le même boitier. Il s'agit de '''processeurs multicœurs''', qui contiennent plusieurs cœurs, chaque cœur pouvant exécuter un programme tout seul.
La différence entre cœur et processeur est assez difficile à saisir, mais pour simplifier : un cœur est l'ensemble des circuits nécessaires pour exécuter un programme. Chaque cœur dispose de toute la machinerie électronique pour exécuter un programme, à savoir des circuits aux noms barbares comme : un séquenceur d'instruction, des registres, une unité de calcul. Par contre, certains circuits d'un processeur ne sont présents qu'en un seul exemplaire dans un processeur multicœur, comme les circuits de communication avec la mémoire ou les circuits d’interfaçage avec la carte mère.
Suivant le nombre de cœurs présents dans notre processeur, celui-ci sera appelé un processeur double-cœur (deux cœurs), quadruple-cœur (4 cœurs), octuple-cœur (8 cœurs), etc. Un processeur double-cœur est équivalent à avoir deux processeurs dans l'ordinateur, un processeur quadruple-cœur est équivalent à avoir quatre processeurs dans l'ordinateur, etc. Ces processeurs sont devenus la norme dans les ordinateurs grand public et les logiciels et systèmes d'exploitation se sont adaptés.
===Les coprocesseurs===
Quelques ordinateurs assez anciens disposaient de '''coprocesseurs''', des processeurs qui complémentaient un processeur principal. Les ordinateurs de ce type avaient un processeur principal, le '''CPU''', qui était secondé par un ou plusieurs coprocesseurs. Sauf exception, le CPU et le coprocesseur exécutent des programmes différents, ils travaillent en parallèle.
Les coprocesseurs les plus connus sont les '''coprocesseurs pour le rendu 2D/3D'''. Ils ont eu leur heure de gloire sur les anciennes consoles de jeux vidéo, comme Super Nintendo, la Playstation et autres consoles de cette génération ou antérieure. Ils s'occupaient respectivement de calculer les graphismes des jeux vidéos. De nos jours, ils ont été remplacés par des cartes graphiques, ou des ''Graphic Processing Units'', qui ne sont pas considérées comme des coprocesseurs.
Les '''coprocesseurs sonores''' sont une sorte d'ancêtre des cartes son, utilisés sur les anciennes consoles de jeux vidéo, comme La Nintendo 64, la Playstation et autres consoles antérieures. Ils s'occupaient respectivement de calculer tout ce qui a trait au son. Pour donner un exemple, on peut citer la console Neo-géo, qui disposait de deux processeurs travaillant en parallèle : un processeur principal, et un coprocesseur sonore. Le processeur principal était un Motorola 68000, alors que le coprocesseur sonore était un processeur Z80.
L'accès aux périphériques est quelque chose sur lequel nous passerons plusieurs chapitres dans ce cours. Mais sachez que l'accès aux périphériques peut demander pas mal de puissance de calculs. Le CPU principal peut faire ce genre de calculs par lui-même, mais il n'est pas rare qu'un '''coprocesseur d'IO''' soit dédié à l'accès aux périphériques. Un exemple assez récent est celui de la console de jeu Nintendo 3DS. Elle disposait d'un processeur principal de type ARM9, d'un coprocesseur pour les divisions qu'on abordera plus bas, et d'un second processeur ARM7. L'ARM 7 était utilisé comme coprocesseur d'I/O, ainsi que pour l'émulation de la console GBA.
[[File:Asmp 2.gif|centre|vignette|upright=2|Co-processeur pour l'accès aux entrées-sorties.]]
Les '''coprocesseurs arithmétiques''' sont un peu à part des autres. Ils sont spécialisés dans les calculs en virgule flottante. Ils étaient utilisés à une époque où les CPU ne géraient que des calculs entiers (en binaire ou en BCD). Un exemple est le coprocesseur flottant x87, complémentaire des premiers processeurs Intel x86. Il y a eu la même chose sur les processeurs Motorola 68000, avec deux coprocesseurs flottants appelés les Motorola 68881 et les Motorola 68882.
Un exemple récent de coprocesseur est celui utilisé sur la console de jeu Nintendo DS. La console utilisait deux processeurs, un ARM9 et un ARM7, qui ne pouvaient pas faire de division entière. Il s'agit pourtant d'opérations importantes dans le cas du rendu 3D, ce qui fait que les concepteurs de la console ont rajouté un coprocesseur spécialisé dans les divisions entières et les racines carrées. Le coprocesseur était adressable directement par le processeur, comme peuvent l'être la RAM ou les périphériques.
==Les entrées-sorties==
Tous les circuits vus précédemment traitent des données codées en binaire. Ceci dit, les données ne sortent pas de n'importe où : l'ordinateur contient des composants électroniques qui traduisent des informations venant de l’extérieur en nombres. Ces composants sont ce qu'on appelle des '''entrées'''. Par exemple, le clavier est une entrée : l'électronique du clavier attribue un nombre entier (''scancode'') à une touche, nombre qui sera communiqué à l’ordinateur lors de l'appui d'une touche. Pareil pour la souris : quand vous bougez la souris, celle-ci envoie des informations sur la position ou le mouvement du curseur, informations qui sont codées sous la forme de nombres. La carte son évoquée il y a quelques chapitres est bien sûr une entrée : elle est capable d'enregistrer un son, et de le restituer sous la forme de nombres.
S’il y a des entrées, on trouve aussi des '''sorties''', des composants électroniques qui transforment des nombres présents dans l'ordinateur en quelque chose d'utile. Ces sorties effectuent la traduction inverse de celle faite par les entrées : si les entrées convertissent une information en nombre, les sorties font l'inverse : là où les entrées encodent, les sorties décodent. Par exemple, un écran LCD est un circuit de sortie : il reçoit des informations, et les transforme en image affichée à l'écran. Même chose pour une imprimante : elle reçoit des documents texte encodés sous forme de nombres, et permet de les imprimer sur du papier. Et la carte son est aussi une sortie, vu qu'elle transforme les sons d'un fichier audio en tensions destinées à un haut-parleur : c'est à la fois une entrée, et une sortie.
Les '''entrées-sorties''' incluent toutes les entrées et sorties, et même certains composants qui sont les deux à la fois. Il s'agit d'un terme générique, qui regroupe des composants forts différents. Dans ce qui va suivre, nous allons parfois parler de périphériques au lieu d'entrées-sorties, mais les deux termes ne sont pas équivalents. Dans le détail, les entrées-sorties regroupent :
* Les '''périphériques''' sont les composants connectés sur l'unité centrale. Exemple : les claviers, souris, webcam, imprimantes, écrans, clés USB, disques durs externes, la Box internet, etc.
* Les '''cartes d'extension''', qui se connectent sur la carte mère via un connecteur, comme les cartes son ou les cartes graphiques.
* D'autres composants sont soudés à la carte mère mais sont techniquement des entrées-sorties : les cartes sons soudées sur les cartes mères actuelles, par exemple.
===L'interface avec le reste de l'ordinateur===
Les entrées-sorties sont très diverses, fonctionnent très différemment les unes des autres. Mais du point de vue du reste de l'ordinateur, les choses sont relativement standardisées. Du point de vue du processeur, les entrées-sorties sont juste des paquets de registres ! Tous les périphériques, toutes les entrées-sorties contiennent des '''registres d’interfaçage''', qui permettent de faire l'intermédiaire entre l'entrée/sortie et le reste de l'ordinateur. L'entrée/sortie est conçu pour réagir automatiquement quand on écrit dans ces registres.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage sont assez variés. Les plus évidents sont les '''registres de données''', qui permettent l'échange de données entre le processeur et les périphériques. Pour échanger des données avec l'entrée/sortie, le processeur a juste à lire ou écrire dans ces registres de données. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données. Si le processeur veut envoyer une donnée à une entrée/sortie, il a juste à écrire dans ces registres. Inversement, s'il veut lire une donnée, il a juste à lire le registre adéquat.
Mais le processeur ne fait pas que transmettre des données à l'entrée/sortie. Le processeur lui envoie aussi des « commandes », des valeurs numériques auxquelles l'entrée/sortie répond en effectuant un ensemble d'actions préprogrammées. En clair, ce sont l'équivalent des instructions du processeur, mais pour l'entrée/sortie. Par exemple, les commandes envoyées à une carte graphique peuvent être : affiche l'image présente à cette adresse mémoire, calcule le rendu 3D à partir des données présentes dans ta mémoire, etc. Pour recevoir les commandes, l'entrée/sortie contient des ''registres de commande'' qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande à l'entrée/sortie, il écrit la commande en question dans ce ou ces registres.
Enfin, beaucoup d'entrée/sortie ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état de l'entrée/sortie. Ils servent notamment à indiquer au processeur que l'entrée/sortie est disponible, qu'il est en train d’exécuter une commande, qu'il est occupé, qu'il y a un problème, qu'il y a une erreur de configuration, etc.
===Les adresses des registres d’interfaçage===
Les registres des périphériques sont identifiés par des adresses mémoires. Et les adresses sont conçues de façon à ce que les adresses des différentes entrées/sorties ne se marchent pas sur les pieds. Chaque entrée/sortie, chaque registre, chaque contrôleur a sa propre adresse. D'ordinaire, certains bits de l'adresse indiquent quel est le destinataire. Certains indiquent quel est l'entrée/sortie voulue, les restants indiquant le registre de destination.
Il existe deux organisations possibles pour les adresses des registres d’interfaçages. La première possibilité est de séparer les adresses pour les registres d’interfaçage et les adresses pour la mémoire. Le processeur doit avoir des instructions séparées pour gérer les périphériques et adresser la mémoire. Il a des instructions de lecture/écriture pour lire/écrire en mémoire, et d'autres pour lire/écrire les registres d’interfaçage. Sans cela, le processeur ne saurait pas si une adresse est destinée à un périphérique ou à la mémoire.
[[File:Espaces d'adressages séparés entre mémoire et périphérique.png|centre|vignette|upright=2.5|Espaces d'adressages séparés entre mémoire et périphérique]]
L'autre méthode mélange les adresses mémoire et des entrées-sorties. Si on prend par exemple un processeur de 16 bits, où les adresses font 16 bits, alors les 65536 adresses possibles seront découpées en deux portions : une partie ira adresser la RAM/ROM, l'autre les périphériques. On parle alors d''''entrées-sorties mappées en mémoire'''. L'avantage est que le processeur n'a pas besoin d'avoir des instructions séparées pour les deux.
[[File:IO mappées en mémoire.png|centre|vignette|upright=2.0|IO mappées en mémoire]]
Pour résumer, communiquer avec une entrée/sortie est similaire à ce qu'on a avec les mémoires. Il suffit de lire ou écrire dans des registres d’interfaçage, qui ont chacun une adresse mémoire. Le problème est que le système d'exploitation ne connaît pas toujours le fonctionnement d'une entrée/sortie : il faut installer un programme qui va s'exécuter quand on souhaite communiquer avec l'entrée/sortie, et qui s'occupera de tout ce qui est nécessaire pour le transfert des données, l'adressage du périphérique, etc. Ce petit programme est appelé un driver ou '''pilote de périphérique'''. La « programmation » périphérique est très simple : il suffit de savoir quoi mettre dans les registres, et c'est le pilote qui s'en charge.
==Les architectures Harvard et Von Neumann==
Après avoir vu le processeur, les mémoires et les entrées-sorties, voyons voir comment le tout est interconnecté. Tous les ordinateurs ne sont pas organisés de la même manière, pour ce qui est de leurs bus. Mais pour comprendre pourquoi, nous devons regarder qui communique avec qui, dans un ordinateur. Pour rappel, les données sont placées en mémoire RAM, alors que les instructions sont placées en mémoire ROM. Le processeur lit des instructions dans la mémoire ROM, il lit et écrit dans la mémoire RAM, et accède aux registres d’interfaçage des entrées-sorties. Il y a donc besoins de trois interconnexions : CPU-ROM, CPU-RAM et CPU-IO.
[[File:Réseau d'interconnexion avec un processeur au centre.png|centre|vignette|upright=2|Réseau d'interconnexion avec un processeur au centre]]
Il parait intéressant d'utiliser trois interconnexions, au minimum CPU-ROM, CPU-RAM et CPU-IO. Néanmoins, faire ainsi a de nombreux désavantages. Déjà, il faut pouvoir brancher tout ça sur le processeur. Et celui-ci n'a pas forcément assez de broches pour. Aussi, il est parfois préférable de mutualiser des bus, à savoir de connecter plusieurs composants sur un même bus. Par exemple, on peut mutualiser le bus pour la mémoire RAM et pour la mémoire ROM. Il faut dire que les deux bus sont des bus mémoire, avec un bus d'adresse, un bus de données, et surtout : des bus de commande similaires. Les mutualiser est alors très simple, et permet d'économiser pas mal de broches.
[[File:Réseau d'interconnexion avec un processeur au centre et une architecture Harvard.png|centre|vignette|upright=2|Réseau d'interconnexion avec un processeur au centre et une architecture Harvard]]
Cette mutualisation nous amène naturellement à parler de la distinction entre les architectures Harvard d'un côté et les architectures Von Neumann de l'autre. Elle est très liée au fait d'utiliser soit un bus mémoire unique, soit des bus séparés pour la ROM et la RAM. Voyons cela en détail.
===Les architectures Harvard et Von Neumann : des bus séparés ou unifiés===
Avec l''''architecture Harvard''', la mémoire ROM et la mémoire RAM sont reliées au processeur par deux bus séparés. Il y a un bus RAM pour la mémoire RAM, un bus ROM pour la mémoire ROM. L'avantage de cette architecture est qu'elle permet de charger une instruction et une donnée simultanément : une instruction chargée sur le bus relié à la mémoire programme, et une donnée chargée sur le bus relié à la mémoire de données. Et cela simplifie fortement la conception du processeur.
[[File:Harvard Architecture.png|centre|vignette|upright=2|Architecture Harvard, avec une ROM et une RAM séparées.]]
Avec l''''architecture Von Neumann''', mémoire ROM et mémoire RAM sont reliées au processeur par un bus unique. Le bus unique qui relie processeur, RAM et ROM, s'appelle le '''bus mémoire'''. Un défaut de ces architecture est qu'elles ne peuvent pas charger une instruction et une donnée en même temps. Et cela pose quelques problèmes pour la conception du processeur. Par contre, nous verrons dans ce qui suit qu'utiliser un bus mémoire partagé est bien plus flexible et permet des choses que les architectures Harvard ne peuvent pas faire.
[[File:Architecture Von Neumann, avec deux bus séparés.png|centre|vignette|upright=2|Architecture Von Neumann, avec deux bus séparés.]]
===Les architectures Harvard et Von Neumann : des espaces d'adressage séparés ou unifiés===
La distinction précédente se base sur les connexions entre RAM, ROM et processeur. Mais il existe une autre distinction, très liée, qui est souvent utilisée comme seconde définition des architectures Harvard/Von Neumann. Elle est liée aux adresses mémoire que le processeur peut gérer. Prenons un processeur 16 bits, par exemple, qui gère naturellement des adresses de 16 bits. Il peut gérer 2^16 adresses, soit 64 kibioctets de mémoire. L'ensemble de ces adresses est appelé un '''espace d'adressage'''. Mais comment cet espace d'adressage est utilisé pour adresser une RAM et une ROM ?
Sur les architectures Harvard, le processeur voit deux mémoires séparées avec leur lot d'adresses distinctes. Une même adresse peut donc correspondre soit à la mémoire ROM, soit à la mémoire RAM, suivant le bus utilisé. L'espace d'adressage est donc doublé, dupliqué, avec un pour la ROM, un autre pour la RAM. Rien d'étonnant à cela : il y a deux bus d'adresses, chacun correspondant à un espace d'adressage.
[[File:Vision de la mémoire par un processeur sur une architecture Harvard.png|centre|vignette|upright=2|Vision de la mémoire par un processeur sur une architecture Harvard.]]
Avec l'architecture Von Neumann, la RAM et la ROM doivent se partager les adresses mémoires disponibles. Il n'y a qu'un seul espace d'adressage qui est coupé en deux, avec une partie pour la ROM et une autre pour la RAM. Une adresse correspond soit à la mémoire RAM, soit à la mémoire ROM, mais pas aux deux. Typiquement, la mémoire ROM occupe une partie des adresses, la mémoire RAM utilise le reste. La répartition des adresses est réalisée par les circuits de décodage d'adresse mentionnés plus haut.
[[File:Vision de la mémoire par un processeur sur une architecture Von Neumann.png|centre|vignette|upright=2|Vision de la mémoire par un processeur sur une architecture Von Neumann.]]
Les '''architectures Harvard modifiées''' sont des intermédiaires entre architectures Harvard et architectures Von Neumann, bien qu'elles penchent bien plus du côté des architectures Harvard. Précisons que la terminologie n'est pas claire, beaucoup d'auteurs mettent des définitions différentes derrière ces deux termes. Mais dans ce cours, nous utiliserons une définition très stricte de ce qu'est une architecture Harvard modifiée.
Une architecture Harvard modifiée est une architecture Harvard, où le processeur peut lire des données constantes depuis la mémoire ROM. Nous avions vu plus haut que les mémoires ROM peuvent mémoriser, en plus d'un programme exécutable, des données constantes, qui ne varient pas. Les architectures Harvard pures ne permettent pas de lire des données de ce genre depuis la mémoire ROM, alors que les architectures Harvard modifiées le permettent.
Une architecture Harvard modifiée dispose d'une instruction pour lire les données en mémoire RWM, et d'une instruction pour lire des données en mémoire ROM. Il y a donc deux versions de l'instruction LOAD, qui copient la donnée dans un registre général, mais dont la source de la donnée est différente. Une autre possibilité, plus rare, est que une instruction de copie, qui copie une constante depuis la mémoire ROM vers la mémoire RAM. Le cas le plus commun est l'utilisation de deux instructions LOAD séparées.
[[File:Espaces d'adressage sur une archi harvard modifiée.png|centre|vignette|upright=2.5|Espaces d'adressage sur une archi harvard modifiée]]
Ceci étant dit, revenons à la distinction entre architecture Harvard et Von Neumann. Il faut noter que la RAM et la ROM n'ont pas forcément la même taille. Et ce que ce soit sur une architecture Harvard que sur une architecture Von Neumann, mais c'est plus facile à expliquer sur une architecture Harvard.
On peut par exemple imaginer une architecture Harvard qui utilise des adresses de 16 bits pour la ROM, et seulement 8 bits pour la RAM. Le résultat est qu'il peut adresser 64 kibioctets de ROM, mais seulement 256 octets de RAM. Les deux bus d'adresse sont alors de taille différente, l'un faisant 8 bits, l'autre 16. Quelques processeurs 8 bits étaient dans ce cas, comme on le verra dans le chapitre sur les CPU 8bits. Mais d'autres processeurs utilisent des valeurs différentes, avec par exemple des adresses de 16 bits pour la RAM, mais de 20 bits pour la ROM, ou inversement.
Sur une architecture Von Neumann, tout dépend de comment les adresses sont réparties. La solution la plus simple découpe l'espace d'adressage en deux parties égales, avec la RAM qui est dans la moitié basse (qui part de l'adresse 0 jusqu'à l'adresse au milieu), alors que la ROM est dans la moitié haute (entre l'adresse du milieu et l'adresse maximale). Mais ce n'est pas la seule possibilité, la limite entre RAM et ROM peut être mise n'importe où. Prenons par exemple un processeur 32 bits, capable de gérer 4 milliards d'adresse. Il est parfaitement possible de réserver 128 mébioctets de poids fort à la mémoire ROM, et de laisser le reste à la mémoire RAM.
===Le décodage d'adresse sur les architectures Von Neumann===
Pour résumer, les architectures Harvard et Von Neumann se distinguent sur deux points :
* L'accès à la RAM et à la ROM se font par des bus séparés sur l'architecture Harvard, sur le même bus avec l'architecture Von Neumann.
* Les adresses pour la mémoire ROM et la mémoire RAM sont séparées sur les architectures Harvard, partagées sur l’architecture Von Neumann.
Les architectures Von Neumann utilisent donc un seul bus pour connecter la RAM et la ROM au processeur. Mais cela ne parait pas intuitif : comment deux composants peuvent se connecter aux mêmes fils ? Parce que c'est ce qu'implique le fait de partager un bus. Si je prends une mémoire RAM et une mémoire ROM, toutes deux de 8 bits, elles seront connectées à un bus mémoire de 8 bits. Intuitivement, on se dit qu'il y aura des conflits, du genre : la RAM et la ROM vont accéder au bus en même temps, comment savoir si une adresse est destinée à la RAM ou la ROM, etc ?
Tous ces problèmes sont résolus avec une solution très simple : à chaque instant, seule une mémoire est connectée au bus. L'idée est que les mémoires sont connectées ou déconnectées du bus selon les besoins. Si le processeur veut envoyer lire une donnée en mémoire RAM, il déconnecte la mémoire ROM du bus. Et inversement, s'il veut lire une instruction, il déconnecte la RAM et connecte la ROM.
Pour cela, les mémoires RAM et ROM possèdent une entrée ''Chip Select'' ou ''Output Enable'', qui agit comme une sorte de bouton ON/OFF. Lorsqu'on met un 1 sur cette entrée, la mémoire se connectera au bus. Ses entrées et sorties fonctionneront normalement, elle pourra recevoir des adresses, envoyer ou recevoir des données, tout sera normal. Par contre, si on met un 0 sur cette entrée, la mémoire se "désactive", ses entrée-sorties ne répondent plus aux sollicitations extérieures. Pire que ça : elles sont électriquement déconnectées.
Au total, tout cela demande de gérer deux bit ''Chip Select''/''Output Enable'' : un pour la RAM, un pour la ROM. Et ces deux bits sont configurés pour chaque accès mémoire, pour chaque lecture ou écriture. Pour cela, un circuit de '''décodage d'adresse''' prend en entrée l'adresse mémoire à lire/écrire, et active/désactive les mémoires RAM/ROM selon les besoins. Il prend l'adresse et configure les bits ''Chip Select''/''Output Enable''.
[[File:Décodage d'adresse sur une architecture Von Neumann.png|centre|vignette|upright=2|Décodage d'adresse sur une architecture Von Neumann.]]
L'implémentation la plus simple réserve la moitié des adresses pour la RAM, l'autre moitié pour la ROM. Typiquement, la ROM prend la moitié basse, la RAM la moitié haute. Dans ce cas, activer/désactiver la RAM et la ROM se fait avec seulement le bit de poids fort de l'adresse. Si le bit de poids fort est à 1, alors on accède à la RAM et la ROM doit être désactivée. Mais si ce bit est à 0, alors on accède à la moitié basse et il faut désactiver la RAM.
Une remarque intéressante : le fait de séparer la mémoire en deux parts égales permet de simuler une architecture Harvard à partir d'une architecture Von Neumann. Par exemple, le tout premier processeur d'Intel, le 4004, était l'un de ceux là. La RAM et la ROM sont reliés au même bus, et il y a donc un unique espace d'adressage, qui est séparé en deux parties égales. Le truc est que le processeur traite les deux parties égales comme deux espaces d'adressage séparés. Le processeur se débrouille pour cacher le fait qu'il y a un espace d'adressage unique coupé en deux, ce qui fait que les programmeurs voient bien deux espaces d'adressages distincts.
[[File:Décodage d'adresse sur une architecture Von Neumann basique.png|centre|vignette|upright=2|Décodage d'adresse sur une architecture Von Neumann basique.]]
Pour résumer, quand une adresse est envoyée sur le bus, les deux mémoires vont la recevoir mais une seule va répondre et se connecter au bus. Le décodage d'adresse garantit que seule la mémoire adéquate réponde à un accès mémoire. Le décodage d'adresse est réalisé par la carte mère, par un composant dédié.
Le mécanisme peut être utilisé pour combiner plusieurs RAM en une seule, idem avec les ROM. Pour comprendre l'idée, je vais prendre l'exemple de l'IBM PC, un des tout premier PC existant. Nous étudierons ce PC dans une section dédiée, à la fin du chapitre, aussi je vais passer rapidement dessus. Tout ce que je vais faire est vous présenter la carte mère du PC, et vous demander de faire est de compter les mémoires ROM et mémoires RAM sur la carte mère :
[[File:IBM 5150 Motherboard.svg|centre|vignette|upright=3|Carte mère de l'IBM 5150, un modèle de l'IBM PC.]]
Si vous remarquerez qu'il y a 5 mémoires ROM et 8 à 32 mémoires RAM. Le fait est que le processeur voit les différentes mémoires ROM comme une seule mémoire ROM. Idem avec les mémoires RAM : elle font chacune 2 kibioctets, et l'ensemble est vu par le processeur comme une seule RAM de 16 à 64 kibioctets. Et cela grâce aux circuits de décodage d'adresse, qui sont situés en haut à droite de la carte mère.
Pour comprendre l'idée, prenons l'exemple d'un processeur 16 bits, capable de gérer 64 kibioctets de mémoire. L'espace d'adressage est découpé en quatre portions, de 16 kibioctets chacune. Une portion est réservée à une ROM de 16 kibioctet, les autres sont chacune réservée à une RAM de 16 kibioctet. Le décodage d'adresse sélectionne alors la mémoire adéquate en utilisant les deux bits de poids fort de l'adresse.
* S'ils valent 00, alors c'est la mémoire ROM qui est activée, connectée au bus.
* S'ils valent 01, alors c'est la première mémoire RAM qui est connectée au bus.
* S'ils valent 10, alors c'est la seconde mémoire RAM qui est connectée au bus.
* S'ils valent 11, alors c'est la troisième mémoire RAM qui est connectée au bus.
[[File:Décodage d'adresse sur une architecture Von Neumann, utilisant plusieurs RAM et une ROM.png|centre|vignette|upright=3|Décodage d'adresse sur une architecture Von Neumann, utilisant plusieurs RAM et une ROM]]
===L'impact sur la conception du processeur===
Plus haut, j'ai parlé d'un des avantages des architectures Harvard : elles peuvent lire une instruction en même temps qu'elles accèdent à une donnée. La donnée est lue/écrite en RAM, alors que l'instruction est lue en ROM. Et cela permet de simplifier l'intérieur du processeur. Pas de beaucoup, mais c'est déjà ça de pris. Voyons maintenant comment cela impacte l'intérieur du processeur. Tout ce dont vous avez à vous rappeler est la séparation entre chemin de données et unité de contrôle, et que les registres généraux sont dans le premier, le ''program counter'' dans la seconde.
Avec une architecture Harvard, les instructions et les données passent par des bus différent : bus ROM pour les instructions, bus RAM pour les données. L'intuition nous dit que le bus pour la mémoire ROM est connecté à l'unité de contrôle, alors que le bus pour la RAM est connecté au chemin de données. Et dans les grandes lignes, c'est vrai. La logique est imparable pour ce qui est des bus de données. Mais il y a une petite subtilité pour les bus d'adresse.
Pour comprendre comment le processeur exploite ces deux bus, voyons ce qui transite dessus. Pour la mémoire ROM, elle reçoit l'adresse de l'instruction à lire, elle renvoie l'instruction adéquate. Pour cela, le ''program counter'' est envoyé sur le bus d'adresse, l'instruction sur le bus de données. Pour la mémoire RAM, elle échange des données avec les registres généraux, les registres pour les données. Les adresses utilisées pour la RAM viennent elles soit du chemin de données, soit de l'unité de contrôle, tout dépend du mode d'adressage. Mais le ''program counter'' n'est pas impliqué.
[[File:Architecture Harvard - échanges de données.png|centre|vignette|upright=2|Architecture Harvard - échanges de données]]
Les architectures Harvard modifiées doivent cependant rajouter une connexion entre le bus ROM et les registres généraux. C'est nécessaire pour charger une donnée constante depuis la mémoire ROM. Rappelons que la donnée constante est copiée dans un registre général, donc dans le chemin de données.
[[File:Architecture Harvard modifiée - implémentation du processeur.png|centre|vignette|upright=2|Architecture Harvard modifiée - implémentation du processeur]]
Avec les architectures Von Neumann, il y a un seul bus qui est relié à la fois au chemin de données et à l'unité de contrôle. Si le processeur lit une instruction, le bus doit être relié à l'unité de contrôle. Par contre, s'il accède à une donnée, il doit être relié au chemin de données (le bus d'adresse peut éventuellement être connecté au séquenceur, si celui-ci fournit l'adresse à lire). Il faut donc utiliser un paquet de multiplexeurs et de démultiplexeurs pour faire la connexion au bon endroit.
[[File:Architecture Von Neumann - implémentation du processeur.png|centre|vignette|upright=2|Architecture Von Neumann - implémentation du processeur]]
Une instruction se fait en deux temps : on charge l'instruction depuis la mémoire ROM, puis on l'exécute. Avec une architecture Harvard, tout cela se fait en un seul cycle d'horloge, vu que charger la ROM et accéder aux données peut se faire en même temps. Pas avec les architectures Von Neumann, qui doivent libérer le bus mémoire après avoir chargé une instruction. Elles n'ont pas le choix : elles chargent l'instruction lors d'un premier cycle d'horloge, puis l'exécutent lors du second.
Pour cela, ils incorporent un registre appelé le '''registre d'instruction''', qui mémorise l'instruction chargée. L'instruction est copiée dans ce registre lors du premier cycle, puis est utilisée lors du second cycle. Le registre permet de ne pas oublier l’instruction entre les deux cycles. Le registre d'instruction est obligatoire sur les architectures Von Neumann. En comparaison, il est facultatif sur les architectures Harvard. Elles peuvent en avoir un, pour des raisons techniques, mais ce n'est pas obligatoire.
[[File:Registre d'instruction.png|centre|vignette|upright=2|Registre d'instruction.]]
===Les architectures Von Neumann sont plus flexibles===
Sur les architectures Harvard, le processeur sait faire la distinction entre programme et données. Les données sont stockées dans la mémoire RAM, le programme est stocké dans la mémoire ROM. Les deux sont séparés, accédés par le processeur sur des bus séparés, et c'est ce qui permet de faire la différence entre les deux. Il est impossible que le processeur exécute des données ou modifie le programme. Du moins, tant que la mémoire qui stocke le programme est bien une ROM.
Par contre, sur les architectures Von Neumann, il est impossible de distinguer programme et données, sauf en ajoutant des techniques de protection mémoire avancées. La raison est qu'il est impossible de faire la différence entre donnée et instruction, vu que rien ne ressemble plus à une suite de bits qu'une autre suite de bits. Et c'est à l'origine d'un des avantages majeur de l'architecture Von Neumann : il est possible que des programmes soient copiés dans la mémoire RWM et exécutés dans celle-ci.
Un cas d'utilisation familier est celui de votre ordinateur personnel. Le système d'exploitation et les autres logiciels sont copiés en mémoire RAM à chaque fois que vous les lancez. Mais cet exemple implique un disque dur, ce qui rend les choses plus compliquées que prévu. Un autre exemple serait la compilation de code à la volée, mais il ne sera pas très parlant.
Un exemple plus adapté serait celui où la ROM mémorise un programme compressée dans la mémoire ROM, qui est décompressé pour être exécuté en mémoire RAM. Le programme de décompression est stocké en mémoire ROM et est exécuté au lancement de l’ordinateur. Cette méthode permet d'utiliser une mémoire ROM très petite et très lente, tout en ayant un programme rapide (si la mémoire RWM est rapide).
Il est aussi possible de créer des programmes qui modifient leurs propres instructions : cela s'appelle du '''code auto-modifiant'''. Ce genre de choses servait autrefois sur des ordinateurs rudimentaires, au tout début de l'informatique. À l'époque, les adresses à lire/écrire devaient être écrites en dur dans le programme, dans les instructions exécutées. Pour gérer certaines fonctionnalités des langages de programmation qui ont besoin d'adresses modifiables, comme les tableaux, on devait corriger les adresses au besoin avec du code auto-modifiant. De nos jours, le code automodifiant est utilisée occasionnellement pour rendre un programme indétectable dans la mémoire (les virus informatiques utilisent beaucoup ce genre de procédés).
L'impossibilité de séparer données et instructions est à l'origine de problèmes assez fâcheux. Il est parfaitement possible que le processeur charge et exécute des données, qu'il prend par erreur pour des instructions. C'est le cas quand des pirates informatiques arrivent à exploiter des bugs. Il arrive que des pirates informatiques vous fournissent des données corrompues, qui contiennent un virus ou un programme malveillant est caché dans les données. Les bugs en question permettent d'exécuter ces données, donc virus. Pour éviter cela, le système d'exploitation peut marquer certaines zones de la mémoire comme non-exécutable, c’est-à-dire que le système d'exploitation interdit d’exécution de quoi que ce soit qui est dans cette zone. Mais ce n'est pas parfait.
Toujours est-il que tout cela est impossible sur les architectures Harvard. Et ce serait très limitant. Imaginez : pas possible de lancer un programme depuis le disque dur ou une clé USB, le programme doit impérativement être dans une mémoire ROM, pas de compilation à la volée, etc. Que des techniques très utilisées dans l'informatique moderne. Malgré ses défauts, les architectures Von Neumann ne sont pas les plus utilisées pour rien. Les architectures Harvard sont concrètement utilisées uniquement dans l'informatique embarquée, sur des microcontrôleurs très spécifiques.
==Le bus de communication avec les entrées-sorties==
Le processeur, la mémoire et les entrées-sorties sont connectées par un ou plusieurs '''bus de communication'''. Ce bus n'est rien d'autre qu'un ensemble de fils électriques sur lesquels on envoie des zéros ou des uns. Pour communiquer avec la mémoire, il y a trois prérequis qu'un bus doit respecter : pouvoir sélectionner la case mémoire (ou l'entrée-sortie) dont on a besoin, préciser à la mémoire s'il s'agit d'une lecture ou d'une écriture, et enfin pouvoir transférer la donnée. Pour cela, on doit donc avoir trois bus spécialisés, bien distincts, qu'on nommera le bus de commande, le bus d'adresse, et le bus de donnée.
* Le '''bus de données''', sur lequel s'échangent les données entre les composants.
* Le '''bus de commande''' pour configurer la mémoire et les entrées-sorties.
* Le '''bus d'adresse''', facultatif, permet de préciser quelle adresse mémoire il faut lire/écrire.
Chaque composant possède des entrées séparées pour le bus d'adresse, le bus de commande et le bus de données. Par exemple, une mémoire RAM possédera des entrées sur lesquelles brancher le bus d'adresse, d'autres sur lesquelles brancher le bus de commande, et des broches d'entrée-sortie pour le bus de données. Précisons cependant que le bus de commande n'est pas exactement le même entre des mémoires RAM/ROM et des entrées-sorties.
[[File:Bus general schematic.svg|centre|vignette|upright=2|Contenu d'un bus, généralités.]]
===Le réseau d'interconnexion : généralités===
Reprenons où nous nous étions arrêté. Avant de voir les architectures Harvard et Von Neumann, nous avions dit que le processeur, les mémoires et les entrées-sorties sont reliées entre eux par un réseau d'interconnexion. Nous venons de voir qu'il est possible de mutualiser certains bus, notamment celui de la mémoire RAM et celui de la mémoire ROM. Mais il est possible de faire la même chose pour les entrées-sorties. Là encore, il est possible de regrouper le bus mémoire avec les bus pour les entrées-sorties. Voyons ce que cela implique.
{|
|[[File:Réseau d'interconnexion avec un processeur au centre.png|centre|vignette|upright=2|Réseau d'interconnexion avec une architecture Harvard.]]
|[[File:Réseau d'interconnexion avec un processeur au centre et une architecture Harvard.png|centre|vignette|upright=2|Interconnexions d'une architecture Von Neumann.]]
|}
Avant de poursuivre, nous devons préciser quelque chose d'important. Sur les ordinateurs modernes, les entrées-sorties peuvent accéder à la mémoire RAM. Les ordinateurs modernes intègrent des techniques de '''''Direct Memory Access''''' (DMA) qui permettent aux entrées-sorties de lire ou d'écrire en mémoire RAM. Les transferts DMA se font sans intervention du processeur. Ils permettent de copier un bloc de plusieurs octets, dans deux sens : de la mémoire RAM vers une entrée-sortie, ou inversement. Le DMA demande d'ajouter un circuit dédié sur la carte mère : le contrôleur DMA. Il effectue la copie d'un paquet d'octets de la RAM vers l'entrée-sortie ou dans l'autre sens.
[[File:Réseau d'interconnexion avec un processeur au centre, et direct memory access.png|centre|vignette|upright=2|Réseau d'interconnexion avec un processeur au centre, et direct memory access]]
===Les bus systèmes===
La première solution utilise un bus unique, celui-ci est appelé le '''bus système''', aussi appelé ''backplane bus''. Le bus système est connecté à la mémoire RAM, la mémoire ROM, au processeur, et aux entrées-sorties. Tous les composants présents dans l'ordinateur sont connectés à ce bus, sans exception. De tels bus avaient pour avantage la simplicité. Le processeur n'est connecté qu'à un seul bus, ce qui utilise peu de broches et économise des fils. La mutualisation des bus est totale, le câblage est plus simple, la fabrication aussi.
[[File:Architecture minimale d'un ordinateur.png|centre|vignette|upright=2|Architecture minimale d'un ordinateur.]]
Un bus système contient un bus d'adresse, de données et de commande. Un bus système se marie bien avec des entrées-sorties mappées en mémoire. La conséquence est que le bus d'adresse ne sert pas que pour l'accès à la mémoire RAM/ROM, mais aussi pour l'accès aux entrées-sorties. Il y a moyen d'implémenter un système d'adresse séparés avec, mais c'est pas l'idéal.
[[File:Architecture Von Neumann avec les bus.png|centre|vignette|upright=2|Architecture Von Neumann avec les bus.]]
Un bus système n'a pas de limitations quant aux échanges de données. Le processeur peut communiquer directement avec les mémoires et les entrées-sorties, les entrées-sorties peuvent communiquer avec la mémoire RAM, etc. Notamment, un bus système peut implémenter le ''Direct Memory Access''. Il suffit juste de connecter un contrôleur DMA sur le bus système. Le contrôleur DMA est considéré comme une entrée-sortie, ses registres sont mappés en mémoire et sont donc accessibles directement par le processeur.
[[File:Bus système avec controleur DMA.png|centre|vignette|upright=2|Bus système avec contrôleur DMA.]]
Si on suit la définition à la lettre, un bus système est systématiquement une architecture Von Neumann, vu que la mémoire ROM et la mémoire RAM sont reliées sur le bus système. La conséquence est que les circuits de décodage d'adresse sont présents. Ils sont toujours sur la carte mère, et sont plus ou moins à côté du bus système. Cependant, le décodage d'adresse est parfois étendu pour tenir compte des entrées-sorties.
Les entrées-sorties soudées sur la carte mère ont elles aussi des entrées ''Chip Select'' ou quelque chose de similaire. Le décodage d'adresse peut alors les activer ou les désactiver suivant l'adresse envoyée sur le bus d'adresse. C'est ce qui arrive quand le processeur écrit dans un registre d’interfaçage : il envoie l'adresse de ce registre sur le bus d'adresse, le circuit de décodage d'adresse active seulement l'entrée-sortie associée. Il faut noter que ce n'est pas systématique, il existe des techniques pour se passer de décodage d'adresse. Mais nous en reparlerons dans le chapitre sur les bus de communication.
[[File:Chipselectfr.png|centre|vignette|upright=1.5|Exemple détaillé.]]
Les bus systèmes sont certes très simples, mais ils ont aussi des désavantages. Par exemple, il faut éviter que le processeur et les entrées-sorties se marchent sur les pieds, ils ne peuvent pas utiliser le bus en même temps. De tels conflits d'accès au bus système sont fréquents et ils réduisent la performance, comme on le verra dans le chapitre sur les bus. De plus, un bus système a le fâcheux désavantage de relier des composants allant à des vitesses très différentes : il arrivait fréquemment qu'un composant rapide doive attendre qu'un composant lent libère le bus. Le processeur était le composant le plus touché par ces temps d'attente.
Elle était utilisée sur les tout premiers ordinateurs, pour sa simplicité. Elle était parfaitement adaptée aux anciens composants, qui allaient tous à la même vitesse. De nos jours, les ordinateurs à haute performance ne l'utilisent plus trop, mais elle est encore utilisée sur certains systèmes embarqués, en informatique industrielle dans des systèmes très peu puissants.
===Les bus d'entrées-sorties===
Les bus systèmes ont de nombreux problèmes, ce qui fait que d'anciens ordinateurs faisaient autrement. À la place d'un bus système unique, ils utilisent un bus séparé pour les mémoires, et un autre séparé pour les entrées-sorties. Le bus spécialisé pour la mémoire est appelé le '''bus mémoire''', l'autre bus est appelé le '''bus d'entrées-sorties'''. Le bus mémoire est généralement relié à la fois à la mémoire RAM et à la mémoire ROM, les exceptions ne sont pas rares, cependant.
[[File:Bus mémoire séparé du bus pour les IO.png|centre|vignette|upright=2|Bus mémoire séparé du bus pour les IO]]
Les bus d'entrée-sorties peuvent être spécialisés et simplifiés. Par exemple, ils peuvent avoir un bus de commande différent de celui de la mémoire, qui utilise nettement moins de fils. Le bus d'adresse peut aussi être réduit, et utiliser des adresses plus courtes que celles du bus mémoire. Les bus de données peuvent aussi être de taille différentes. Il est ainsi possible d'avoir un bus mémoire capable de lire/écrire 64 bits à la fois, alors que la communication avec les entrées-sorties se fait octet par octet ! En général, les bus d'entrée-sortie sont assez petits, ils ont une taille de 8 ou 16 bits, même si le bus mémoire est plus grand. Cela permet de ne pas gaspiller trop de broches. Ajouter un bus d'entrée-sortie n'est donc pas très gourmand en broches et en fils.
: Il est en théorie possible d'avoir une fréquence différente pour les deux bus, avec un bus mémoire ultra-rapide et un bus pour les entrées-sorties est un bus moins rapide. Mais il faut que le processeur soit prévu pour, et c'est très rare.
Niveau performances, le processeur peut théoriquement accéder à la mémoire en attendant qu'une entrée/sortie réponde, mais il faut que le processeur soit prévu pour, et ce n'est pas de la tarte. Par contre, cela implique d'avoir des adresses séparées pour les registres d’interfaçage et la mémoire. En clair : pas d'entrée-sortie mappée en mémoire ! Un autre problème est que les entrées-sorties ne peuvent pas communiquer avec la mémoire directement, elles doivent passer par l'intermédiaire du processeur. En clair : pas de ''Direct Memory Access'' ! Les deux sont des défauts rédhibitoires pour les programmeurs système, notamment pour ceux qui codent les pilotes de périphériques.
Pour résumer, les défauts sont assez problématiques : pas d'entrées-sorties mappées en mémoire, pas de ''Direct Memory Access'', économie de broches limitée. Les deux premiers sont des défauts majeurs, qui font que de tels bus ne sont pas utilisés dans les ordinateurs modernes. À la place, ils utilisent une troisième solution, distincte des bus systèmes et des bus d'entrée-sorties.
===Les bus avec répartiteur===
Il existe une méthode intermédiaire, qui garde deux bus séparés pour la mémoire et les entrées-sorties, mais élimine les problèmes de brochage sur le processeur. L'idée est d'intercaler, entre le processeur et les deux bus, un '''circuit répartiteur'''. Il récupère tous les accès et distribue ceux-ci soit sur le bus mémoire, soit sur le bus des périphériques. Le ou les répartiteurs s'appellent aussi le '''''chipset''''' de la carte mère.
C'était ce qui était fait à l'époque des premiers Pentium. À l'époque, la puce de gestion du bus PCI faisait office de répartiteur. Elle mémorisait des plages mémoires entières, certaines étant attribuées à la RAM, les autres aux périphériques mappés en mémoire. Elles utilisaient ces plages pour faire la répartition.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus]]
Niveau adresses des registres d'interfacage, il est possible d'avoir soit des adresses unifiées avec les adresses mémoire, soit des adresses séparées.
L'usage d'un répartiteur ne pose pas de problèmes particuliers pour implémenter le DMA. La seule contrainte est que le contrôleur DMA soit intégré dans le répartiteur. Les échanges entre IO et mémoire passent par le répartiteur, qui fait le pont entre bus mémoire et bus des IO.
[[File:Implémentation du DMA avec un répartiteur.png|centre|vignette|upright=2|Implémentation du DMA avec un répartiteur]]
==Les microcontrôleurs et ''system on chip''==
Parfois, on décide de regrouper la mémoire, les bus, le CPU et les ports d'entrée-sortie dans un seul circuit intégré, un seul boitier. L'ensemble forme alors ce qu'on appelle un '''''System on Chip''''' (système sur une puce), abrévié en SoC. Le nom est assez explicite : un SoC comprend un système informatique complet sur une seule puce de silicium, microprocesseurs, mémoires et périphériques inclus. Ils incorporent aussi des ''timers'', des compteurs, et d'autres circuits très utiles.
[[File:ARMSoCBlockDiagram.svg|centre|vignette|upright=2|SoC basé sur un processeur ARM, avec des entrées-sorties typiques de celles d'un µ-contrôleur. Le support du bus CAN, d'Ethernet, du bus SPI, d'un circuit de PWM (génération de signaux spécifiques), de convertisseurs analogique-digital et inverse, sont typiques des µ-contrôleurs.]]
Le terme SoC regroupe des circuits imprimés assez variés, aux usages foncièrement différents et à la conception distincte. Les plus simples d’entre eux sont des microcontrôleurs, qui sont utilisés pour des applications à basse performance. Les plus complexes sont utilisés pour des applications qui demandent plus de puissance, que nous appellerons SoC haute performance.
La relation entre SoC et microcontrôleurs est assez compliquée à expliquer, la terminologie n'étant pas clairement établie. Il existe quelques cours/livres qui séparent les deux, d'autres qui pensent que les deux sont très liés. Dans ce cours, nous allons partir du principe que tous les systèmes qui regroupent processeur, mémoire et quelques périphériques/entrées-sorties sont des SoC. En suivant cette définition, les microcontrôleurs sont donc un cas particulier de SoC, .
===Les microcontrôleurs===
Les '''microcontrôleurs''' sont des composants utilisés dans l'embarqué ou d'informatique industrielle. Leur nom trahit leur rôle. Ils sont utilisés pour contrôler de l'électroménager, des chaines de fabrication dans une usine, des applications robotiques, les alarmes domestiques ou ebcore les voitures. De manière générale, on les trouve dans tous les systèmes dits embarqués et/ou temps réel. Ils ont besoin de s'interconnecter à un grand nombre de composants et intègrent pour cela un grand nombre d'entrée-sorties. Les microcontrôleurs sont généralement peu puissants et doivent consommer peu d'énergie/électricité.
Fait amusant, on en trouve dans certains périphériques informatiques. Par exemple, les anciens disques durs intégraient un microcontrôleur qui contrôlait plusieurs moteurs : les moteurs pour faire tourner les plateaux magnétiques et les moteurs pour déplacer les têtes de lecture/écriture. Autre exemple : les claviers d'ordinateurs, qui intègrent un microcontrôleur connecté aux touches. Celui-ci détecte quand les touches sont appuyées et qui communique avec l'ordinateur. Nous détaillerons ces deux exemples dans les chapitres dédiés aux périphériques et aux disques durs, tout deviendra plus clair à ce moment là. La majorité des périphériques ou des composants internes à un ordinateur contiennent des microcontrôleurs.
Un microcontrôleur tend à intégrer des entrées-sorties assez spécifiques, qu'on ne retrouve pas dans les SoC destinés au grand public. Un microcontrôleur est typiquement relié à un paquet de senseurs et son rôle est de commander des moteurs ou d'autres composants. Et les entrées-sorties intégrées sont adaptées à cette tâche. Par exemple, ils tendent à intégrer de nombreux convertisseurs numériques-analogiques pour gérer des senseurs. Ils intègrent aussi des circuits de génération de signaux PWM spécialisés pour commander des moteurs, le processeur peut gérer des calculs trigonométriques (utiles pour commander la rotation d'un moteur), etc.
[[File:C8051F340-9 A B C D.png|centre|vignette|upright=2|Exemple d'entrées-sorties intégrées à un microcontroleur.]]
En plus des entrées-sorties intégrées, les microcontrôleurs ont souvent des '''ports d'entrées-sorties''' banalisés, à savoir qu'on peut brancher n'importe quoi dessus. Il était possible de brancher un capteur de température, un moteur à commander, un port série, un port parallèle, un écran, un clavier, une souris, peu importe. Les ports font souvent un octet et ils sont généralement reliées directement ou indirectement au processeur. Le logiciel qui s'exécute sur le processeur décide quoi envoyer sur des broches et comment interprète ce qui est reçu dessus.
Les ports banalisés de ce type sont parfois appelés des '''GPIO''', abréviation de ''General Purpose Input/Output'', mais nous utiliserons le terme de ''port I/O''. Un port regroupe plusieurs broches qui peuvent être utilisés à volonté. C'est le logiciel qui s'exécute sur le processeur qui décide de la fonction de broches. Un port IO peut fonctionner soit en tant qu'entrée, soit en tant que sortie. Il est possible de changer de sens en cours de fonctionnement, pour passer d'une entrée à une sortie ou inversement.
[[File:Afficheurs7seg.png|centre|vignette|upright=2|Exemple d'utilisation d'un port I/O. le port est connecté à un afficheur LCD dit 7-segments. Les 8 entrées de cet afficheur sont connectées à un port I/O d'un octet.]]
===Les SoC haute performance===
Les SoC les plus performants sont actuellement utilisés dans les téléphones mobiles, tablettes, ''Netbook'', ''smartphones'', ou tout appareil informatique grand public qui ne doit pas prendre beaucoup de place. La petite taille de ces appareils fait qu'ils gagnent à regrouper toute leur électronique dans un circuit imprimé unique. Mais les contraintes font qu'ils doivent être assez puissants. Ils incorporent des processeurs assez puissants, surtout ceux des ''smartphones''. C'est absolument nécessaire pour faire tourner le système d'exploitation du téléphone et les applications installées dessus.
Niveau entrées-sorties, ils incorporent souvent des interfaces WIFI et cellulaires (4G/5G), des ports USB, des ports audio, et même des cartes graphiques pour les plus puissants d'entre eux. Les SoC incorporent des cartes graphiques pour gérer tout ce qui a trait à l'écran LCD/OLED, mais aussi pour gérer la caméra, voire le visionnage de vidéo (avec des décodeurs/encodeurs matériel). Par exemple, les SoC Tegra de NVIDIA incorporent une carte graphique, avec des interfaces HDMI et VGA, avec des décodeurs vidéo matériel H.264 & VC-1 gérant le 720p. Pour résumer, les périphériques sont adaptés à leur utilisation et sont donc foncièrement différents de ceux des microcontrôleurs.
[[File:SOMblk.png|centre|vignette|upright=3|Exemple de SoC.]]
Un point important est que les processeurs d'un SoC haute performance sont... performants. Ils sont le plus souvent des processeurs de marque ARM, qui sont différents de ceux utilisés dans les PC fixe/portables grand public qui sont eux de type x86. Nous verrons dans quelques chapitres en quoi consistent ces différences, quand nous parlerons des jeux d'instruction du processeur. Autrefois réservé au monde des PCs, les processeurs multicœurs deviennent de plus en plus fréquents pour les SoC de haute performance. Il n'est pas rare qu'un SoC incorpore plusieurs cœurs. Il arrive même qu'ils soient foncièrement différents, avec plusieurs cœurs d'architecture différente.
La frontière entre SoC haute performance et microcontrôleur est de plus en plus floue. De nombreux appareils du quotidien intègrent des SoC haute performance, d'autres des microcontrôleurs. Par exemple, les lecteurs CD/DVD/BR et certains trackers GPS intègrent un SoC ou des processeurs dont la performance est assez pêchue. À l'opposé, les systèmes domotiques intègrent souvent des microcontrôleurs simples. Malgré tout, les deux cas d'utilisation font que le SoC/microcontrôleur est connecté à un grand nombre d'entrées-sorties très divers, comme des capteurs, des écrans, des LEDs, etc.
[[File:GPS tracker Hardware Architecture.png|centre|vignette|upright=2|Hardware d'un tracker GPS.]]
==Étude de l'architecture de quelques consoles de jeu==
Après avoir vu la théorie, nous allons voir des exemples réels d'ordinateurs. Dans ce qui suit, nous allons voir des ordinateurs qui collent assez bien à l''''architecture de base''' vue plus haut, avec un CPU, une RAM et une ROM, quelques entrées-sorties. Tous les ordinateurs modernes, mais aussi dans les smartphones, les consoles de jeu et autres, utilisent une architecture grandement modifiée et améliorée, avec un grand nombre de périphériques, des disques durs/SSD, un grand nombre de mémoires différentes, etc.
Il pourrait sembler pertinent d’étudier des microcontrôleurs ou des ''System On Chip'', en premier lieu. Mais nous éviterons soigneusement de tels systèmes pour le moment. La raison est qu'ils ont un grand nombre d'entrées-sorties, qui sont peu familières. Attendez-vous à avoir près d'une vingtaine ou centaine d'entrée-sorties différentes pour de tels systèmes. Le tout est très complexe, bien trop pour un premier exemple.
À la place, nous allons voir précisément des exemples plus simples : les premiers PC, et des consoles de jeu 8 et 16 bits. Bien que ce soit des systèmes très simples, ils sont cependant plus complexes que l'architecture de base. Et leur avantages/désavantages sont un peu inverse l'un de l'autre. Si on devait résumer les différences, on aurait ceci :
* Les PC ont plus d'entrées-sorties que les consoles, bien que nettement moins que pour les microcontrôleurs/SoC.
* Les PC utilisent des disques durs, les consoles font avec soit des cartouches de jeu, soit des CD/DVD.
* Les PC utilisent des cartes électroniques séparées pour le son et l'écran, les consoles utilisent des circuits soudés sur la carte mère, qui sont souvent des co-processeurs.
* Les PC ont une mémoire ROM soudées sur la carte mère, les consoles 8 bits font sans.
Les PC et micro-ordinateurs ont plus d'entrées-sorties que les consoles. Même si on mets de côté les périphériques, ils ont aussi beaucoup d'entrées-sorties soudées sur la carte mère. En comparaison, les consoles de jeu 8/16 bits se débrouillent avec : une cartouche de jeu et une manette en entrée, une sortie vidéo et une sortie son.
Un autre point important est l'absence de disque dur ou de lecteur CD. La présence d'un disque dur ou d'un lecteur CD/DVD complexifie tout de suite l'architecture des PC. Il faut leur réserver un bus dédié ou les connecter à un bus système, ajouter des circuits sur la carte mère, etc. Et surtout, il faut expliquer comment l'ordinateur exécute des programmes, ce qui demande de parler de l'interaction avec le disque dur et la ROM du BIOS. Rien de tout cela sur les consoles de jeu 8 et 16 bits. Elles utilisent à la place une mémoire ROM, dans la cartouche de jeu, pour mémoriser le code du jeu. Pas besoin de parler des mémoires de stockage, on est beaucoup plus proche de l'architecture de base avec une ROM unique.
Autre différence : les PC utilisent des cartes électroniques à brancher sur la carte mère pour alimenter l'écran et les hauts-parleurs/casques, alors que les consoles de jeu utilisent des co-processeurs dédiés pour le son et les graphismes. Nous avons déjà expliqué ce que sont les co-processeurs plus haut, aussi les co-processeurs des consoles nous paraitrons familiers. On n'a pas à s’embêter à expliquer ce que sont les cartes d'extension, les bus associés et tout ce qui va avec, cela peut être retardé pour la section sur l'architecture des PC.
Par contre, n'allez pas croire que tout est rose avec les consoles 8/16 bits. Il y a quelques différences qui font qu'elles sont plus complexes qu'un PC sur certains points. La gestion de la cartouche de jeu est notamment un peu subtile à comprendre, bien que ce soit bien plus simple à comprendre qu'un système avec un disque dur. Les cartouches de jeu intègrent une mémoire ROM, pour mémoriser les données du jeu, voire son code. Et le processeur doit exécuter le code depuis cette mémoire ROM. La conséquence est que les consoles 8/16 bits utilisent une architecture Harvard, avec un bus relié à la cartouche pour lire les instructions. Mais si ce n'était que ça... Les cartouches mémorisent aussi les données pour les graphismes, ce qui fait que le co-processeur vidéo doit lui aussi lire la cartouche pour récupérer ces données...
===L'architecture de la TurboGraphX-16===
La console PC Engine, aussi appelée TurboGraphX, est une ancienne console 8 bits. Elle contient un processeur 65C02, 8 kibioctets de RAM, un port manettes, une carte son et une carte vidéo. La '''carte son''' est le composant qui s'occupe de commander les haut-parleurs et de gérer tout ce qui a rapport au son. La '''carte graphique''' est le composant qui est en charge de calculer les graphismes, tout ce qui s'affiche à l'écran. Sur cette console, les cartes son et graphique ne sont PAS des co-processeurs, ce sont des circuits électroniques dits fixes. C'est totalement différent de ce qu'on a sur les consoles modernes, aussi le préciser est important.
Bien que la carte graphique ne soit pas un processeur, elle a 64 kibioctets de RAM rien que pour elle. La RAM en question est séparée de la RAM normale, c'est un circuit intégré séparé. Et c'est un cas très fréquent, qui reviendra par la suite. La majeure partie des cartes graphiques dispose de leur propre '''mémoire vidéo''', totalement réservée à la carte graphique. La RAM vidéo est connectée à la carte graphique via un bus séparé. Le processeur est souvent connecté à ce bus, afin de pouvoir écrire des données dedans, mais ce n'est pas le cas ici.
[[File:Architecture de la PC Engine, aussi appelée TurboGrafx-16.png|centre|vignette|upright=2.5|Architecture de la PC Engine, aussi appelée TurboGrafx-16]]
L'architecture de la console était particulièrement simple. Le processeur était le centre de l'architecture, tout était connecté dessus. Il y a un bus pour la cartouche de jeu, un autre pour la RAM, un autre pour les manettes, un autre pour carte son, et un dernier pour la carte graphique. Le fait d'avoir un bus par composant est assez rare et ce n'est le cas ici que parce des conditions particulières sont remplies. Déjà, il y a peu d'entrée-sorties. Ensuite, les bus font tous 8 bits, vu que le processeur est un CPU 8 bits. Avec 5 connexions de 8 bits, le tout utilise 40 broches, ce qui est beaucoup, mais totalement gérable. Par contre, les choses changerons pour les autres consoles.
Au final, l'organisation des bus peut s'expliquer avec ce qu'on a vu dans la section sur les bus de communication. La console utilise une architecture Harvard, car la ROM et la RAM utilisent des bus différents. De plus, il y a des bus dédiés aux entrées-sorties, séparés des bus mémoire. Enfin, la carte graphique a droit à ses propres bus pour lire dans la cartouche et dans sa RAM vidéo dédiée.
===L'architecture de la console de jeu NES===
Maintenant, nous allons voir la console de Jeu Famicom, aussi appelée la NES en occident. Elle a une architecture centrée sur un processeur Ricoh 2A03, similaire au processeur 6502, un ancien processeur autrefois très utilisé et très populaire. Le processeur est associé à 2 KB de mémoire RAM.
Sur certaines cartouches, on trouve une RAM utilisée pour les sauvegardes, qui est adressée par le processeur directement. Première variation par rapport à l'architecture de la console précédente : l'ajout de la RAM pour les sauvegardes dans les cartouches.
Niveau carte graphique, une différence importante est que la carte graphique est connectée à la cartouche de jeu via un autre bus, afin de pouvoir lire les sprites et textures du jeu dans la cartouche.
[[File:Architecture de la NES.png|centre|vignette|upright=2.5|Architecture de la NES]]
La différence avec l'architecture précédente est que des bus ont été fusionnés. Comme dit plus haut, le système utilise une architecture Harvard, vu que la ROM est dans la cartouche, alors que la RAM est soudée à la carte mère. Par contre, la Famicon utilise un bus dédié aux entrées-sorties. Il est utilisé pour la carte son et la carte graphique, seules les manettes sont sur un bus à part. Ce qui fait qu'on devrait plutôt parler de bus de sorties, mais passons... L'essentiel est qu'on n'est plus tout à fait dans le cas de la console précédente, avec un bus par composant.
===L'architecture de la SNES===
L'architecture de la SNES est illustrée ci-dessous. Les changements pour le processeur et la RAM sont mineurs.La RAM a augmenté en taille et passe à 128 KB. Pareil pour la RAM de la carte vidéo, qui passe à 64 KB. Par contre, on remarque un changement complet au niveau des bus, de la carte graphique et de la carte son.
[[File:Architecture de la SNES.png|centre|vignette|upright=2|Architecture de la SNES]]
La console utilise un '''bus système unique''', sur lequel tout est connecté : ROM, RAM, entrées-sorties, etc. La seule exception est pour les manettes, qui sont encore connectées directement sur le processeur, via un bus séparé. La transition vers un bus système s'explique par le fait que la console est maintenant de 16 bits, ce qui fait que les bus doivent être plus larges. Le processeur adresse des mémoires RAM et ROM plus grandes, ce qui double la taille de leurs bus. De plus, les entrées-sorties aussi ont besoin d'un bus plus large. Le processeur n'ayant pas un nombre illimité de broches, la seule solution est de fusionner les bus en un seul bus système.
Un autre changement est que la carte graphique est maintenant composée de deux circuits séparés. Encore une fois, il ne s'agit pas de coprocesseurs, mais de circuits non-programmables. Par contre, la carte son est remplacée par deux coprocesseurs audio ! De plus, les deux processeurs sont connectés à une mémoire RAM dédiée de 64 KB, comme pour la carte graphique. L'un est un processeur 8 bits (le DSP), l'autre est un processeur 16 bits.
Un point très intéressant : certains jeux intégraient des coprocesseurs dans leurs cartouches de jeu ! Par exemple, les cartouches de Starfox et de Super Mario 2 contenait un coprocesseur Super FX, qui gérait des calculs de rendu 2D/3D. Le Cx4 faisait plus ou moins la même chose, il était spécialisé dans les calculs trigonométriques, et diverses opérations de rendu 2D/3D. En tout, il y a environ 16 coprocesseurs d'utiliser et on en trouve facilement la liste sur le net. La console était conçue pour, des pins sur les ports cartouches étaient prévues pour des fonctionnalités de cartouche annexes, dont ces coprocesseurs. Ces pins connectaient le coprocesseur au bus des entrées-sorties. Les coprocesseurs des cartouches de NES avaient souvent de la mémoire rien que pour eux, qui était intégrée dans la cartouche.
===L'architecture de la Megadrive et de la néo-géo===
Passons maintenant à la console de jeu Megadrive, une console 16 bits. Elle a une architecture similaire à celle de la néo-géo, une autre console bien plus puissante, sorti à peu près en même temps. Elles intègrent deux processeurs : un Motorola 68000 qui sert de processeur principal, un Z80 qui sert de processeur dédié à l'audio. Le Z80 et le Motorola 68000 étaient deux processeurs très populaires à l'époque. Le Z80 est une sorte de version améliorée de l'Intel 8088 utilisé sur les anciens PC et de nombreuses consoles utilisaient des Z80 comme processeur principal. Il était familier pour les programmeurs de l'époque, pour son cout réduit, sa bonne disponibilité, et bien d'autres avantages liés à sa production de masse.
Le Z80 est utilisé comme co-processeur audio. Il commande un synthétiseur sonore, et est relié à sa propre mémoire, distincte de la mémoire principale. Le MC68000 est le processeur principal et a une relation maitre-esclave avec le Z80 : le MC68000 envoie des commandes au Z80, mais la communication ne va pas dans l'autre sens.
Le Motorola 68000 était un processeur 16 bits, alors que le Z80 est un processeur 8 bits. Et cette différence fait que l'on ne peut pas connecter directement les deux sur le même bus, ou du moins pas facilement. La solution retenue est d'utiliser deux bus séparés : un bus de 16 bits connecté au 68000, un bus de 8 bits connecté au Z80. Le premier bus est un bus système sur lequel est connecté le 68000, 64 kibioctets de RAM, la cartouche de jeu, et la carte graphique. Le second bus est un bus de 8 bits, plus court, relié au Z80, à un synthétiseur sonore, et 8 kibioctets de RAM
Les deux bus sont connectés à un '''''chipset''''', un circuit répartiteur, qui fait le pont entre les deux bus. Les manettes sont connectées sur le ''chipset''. Il contient un registre de 8 bits, dans lequel le MC68000 peut écrire dedans à sa guise, le registre étant adressable par le processeur. Lorsque le MC68000 écrit une valeur dedans, cela déclenche l’exécution automatique d'un programme pré-déterminé sur le Z80.
: Pour ceux qui savent ce qu'est une interruption, les valeurs écrites dans ce registre sont des numéros d'interruption, qui indiquent quelle routine d'interruption exécuter.
[[File:Architecture de la Megadrive et de la Néogeo.png|centre|vignette|upright=2.5|Architecture de la Megadrive et de la Néogeo]]
Cet exemple nous montre que les bus systèmes sont certes très simples, mais aussi inflexibles. Ils fonctionnent bien quand les composants branchés dessus sont tous des composants 8 bits, ou sont tous de 16 bits, ou tous 32 bits. Mais dès qu'on mélange composants 8, 16, 32 ou 64 bits, les choses deviennent plus compliquées. Il est alors préférable d'utiliser des bus séparés, avec des répartiteurs pour faire le pont entre les différents bus. Et nous verrons que le problème s'est posé lui aussi sur les PC.
===L'architecture des anciennes consoles Playstation : beaucoup de co-processeurs===
Les consoles que nous venons d'aborder étaient des consoles 8 ou 16 bits. A partir des consoles 32 bits, leur architecture s'est rapprochée de celle des PC, avec un usage plus complexes de répartiteurs. La XBOX était très semblable à un PC : le processeur était un Pentium 3 modifié, la carte graphique était une Geforce 3 modifiée, les 64 mébioctets de RAM était la même mémoire DDR que celle des PC, le répartiteur secondaire était un ''chipset'' nForce de NVIDIA, etc. Mais les Playstation 1, 2 et 3 se distinguent de leur contemporains. Elles disposent de très nombreux co-processeurs, qui sont en plus très variés.
La Playstation 1 a été une des premières console à utiliser les CD-ROM comme support de stockage, en remplacement des cartouches. La conséquence est que la console contient une mémoire ROM, soudée à la carte mère, de 512 kibioctets. Elle contient aussi 2 mébioctets de RAM, une carte graphique avec 1 mébioctet de mémoire vidéo, un processeur, et de quoi gérer les périphériques. Il y a un co-processeur audio spécialisé, avec 512 kibioctets de RAM, ce qui nous est familier. Par contre, les autres co-processeurs ne le sont pas.
Déjà, le lecteur de CD-ROM est associé à des circuits sur la carte mère, il y a tout un sous-système dédié au lecteur de CD. Il y a un contrôleur qui sert d'interface avec le lecteur proprement dit, mais aussi deux co-processeurs audio et 32 kibioctets de RAM. Les co-processeurs audio servent à lire des CD sans trop utiliser le second co-processeur audio, ils lui servent de complément.
Ensuite, le processeur incorpore plusieurs cœurs, avec un cœur principal et plusieurs co-processeurs. Le premier est un co-processeur système, qui est utilisé pour gérer la mémoire cache intégrée au processeur, pour des fonctionnalités appelées interruptions et exceptions, ainsi que pour configurer le processeur. Le second est un co-processeur arithmétique spécialisé dans les calculs en virgule flottante, très importants pour le rendu 3D. Enfin, il y a un décodeur vidéo, qui n'est pas un co-processeur, mais un circuit non-programmable, spécialisé dans le décodage vidéo. De nos jours, ce circuit aurait été intégré dans la carte graphique, mais il était intégré dans le processeur sur la Playstation 2.
Pour le reste, le processeur est la figure centrale de la console. Il est connecté à 4 bus : un pour la RAM, un pour la carte graphique, un pour les manettes, un autre pour le reste. Le dernier bus est connecté au système audio et au système pour le lecteur CD. Ce serait un bus d'entrée-sortie, s'il n'était pas connecté à la mémoire ROM. Vous avez bien lu : la mémoire ROM est reliée au bus d'entrée-sortie.
[[File:Architecture de la Playstation.png|centre|vignette|upright=2.5|Architecture de la Playstation]]
La Playstation 2 est composé d'un processeur, couplé à 32 Mébioctets de RAM, et d'un paquet de co-processeurs. Plus de co-processeurs que la PS1. Le processeur principal n'est pas la même que celui de la PS1, mais il a une architecture similaire. Il intègre un décodeur vidéo sur le même circuit intégré, ainsi que deux co-processeur. Les co-processeurs ne sont cependant pas les mêmes.
Le co-processeur système disparait et est remplacé par un second co-processeur arithmétique. Les deux co-processeurs arithmétiques sont spécialisés dans les nombres flottants, avec quelques différences entre les deux. Par exemple, le second co-processeur gérait des calculs trigonométriques, des exponentielles, des logarithmes, et d'autres fonctions complexes du genre ; mais pas le premier co-processeur. Ils sont reliés à 4 kibioctets de RAM pour le premier, 16 kibioctets de RAM pour le second ; qui sont intégrées dans le processeur et non-représentés dans le diagramme ci-dessous.
La PS2 intègre aussi un co-processeur d'entrées-sorties. Pour information, il s'agit du processeur principal de la Playstation 1, qui est ici utilisé différemment, suivant que l'on place un jeu PS1 ou PS1 dans la console. Si on met un jeu PS1, il est utilisé pour émuler la Playstation 1, afin de faire tourner le jeu PS1 sur la PS2. Si on met un jeu PS2, il est utilisé comme co-processeur d'entrée-sortie et fait l'interface entre CPU et entrées-sorties. Il est relié à 2 mébioctets de RAM, soit exactement la même quantité de mémoire que la Playstation 1.
Tous les périphériques sont connectés au co-processeur d'entrées-sortie. Pour cela, le co-processeur d'entrées-sortie est relié à deux bus dédiés aux périphériques. Le premier bus est relié aux manettes, aux ports USB et aux ports pour cartes mémoires. Le second bus est relié à la carte son, la carte réseau, le lecteur DVD, et un port PCMIA. Notons que la carte son intègre un co-processeur audio, qui n'est pas représenté dans le diagramme ci-dessous.
[[File:Playstation 2 architecture.png|centre|vignette|upright=2.5|Playstation 2 architecture]]
==L'architecture des PC et son évolution==
Après avoir vu les consoles, nous allons maintenant voir les anciens PC, des années 80 ou 90. Le tout premier PC était techniquement l''''IBM PC'''. Par la suite, de nombreux ordinateurs ont tenté de reproduire l'IBM PC originel, avec parfois quelques modifications mineures. De tels ordinateurs ''IBM PC compatibles'', ont été très nombreux, pour des raisons diverses. Le fait d'utiliser des composants banalisés, facilement disponibles, ainsi qu'une bonne documentation de l'IBM PC originel, a grandement aidé. Les IBM PC compatibles ont progressivement évolué pour donner les PC actuels. L'IBM PC compatible a donné naissance à de nombreux standards divers.
===L'IBM PC originel et l'IBM PC XT===
[[File:IBM PC XT 02.jpg|vignette|IBM PC XT.]]
Nous allons commencer par voir l'IBM PC originel, et son successeur : l'IBM Personal Computer XT. Nous les appelerons tous deux l'IBM PC. L'IBM PC utilisait un processeur Intel 8088, qui était un processeur 8 bits. Ils utilisaient un bus système unique, appelé le '''bus XT'''. Le bus système allait à 4.77 MHz, soit la même fréquence que le processeur. C'était un bus de 8 bits, ce qui collait parfaitement avec les processeurs 8 bits commercialisés par Intel à l'époque.
L'IBM PC comprenait une mémoire ROM avec de quoi faire fonctionner le PC. La ROM en question contenait un programme minimal, appelé le '''BIOS''', sans lequel le PC ne fonctionnait pas du tout. Il servait de base pour le système d'exploitation et MS-DOS ne fonctionnait pas sans elle. De nos jours, son rôle est plus limité : sans elle, le PC ne démarre pas. Mais nous détaillerons cela dans le prochain chapitre.
En plus de la ROM pour le BIOS, l'IBM PC avait quatre mémoires ROM dédiée au langage de programmation BASIC. Lorsque le PC démarrait, il ne bootait pas un système d'exploitation, mais lançait l'interpréteur pour le langage BASIC. De nos jours, ce serait l'équivalent d'un ordinateur qui boote directement sur du Python, à savoir la console Python que vous avez peut-être déjà utilisé si vous avez testé Python. Ceux qui ont déjà touché à un ordinateur de l'époque savent ce que ca veut dire, mais c'est malheureusement très difficile à expliquer sans ce genre d'expérience. Toujours est-il que c'était une sorte de norme à l'époque
: les ordinateurs bootaient généralement sur un interpréteur BASIC.
[[File:XT Bus pins.svg|vignette|Connecteur du bus XT.]]
Les PC étaient conçus pour qu'on branche des '''cartes d'extension''', à savoir des cartes électroniques qu'on branchait sur la carte mère, à l'intérieur du PC. Les cartes d'extension de l'époque étaient surtout des cartes son ou des cartes graphiques, mais aussi des cartes pour brancher des péripéhriques. par exemple, on pouvait ajouter deux cartes graphiques dans l'IBM PC originel : l'''IBM Monochrome Display Adapter'' et/ou la ''IBM Color Graphics Adapter''. De nos jours, les cartes son sont intégrées à la carte mère, mais les cartes graphiques sont restées des cartes d'extension.
Les cartes d'extension étaient branchées sur un '''connecteur XT''', qui était directement relié au bus XT. Le connecteur XT est illustré ci-contre, mais ne vous en souciez pas trop pour le moment. La carte mère de l'IBM PC avait 5 connecteurs de ce type, qu'on pouvait peupler avec autant de cartes d'extension. L'IBM Personal Computer XT est passé à 8 connecteurs XT, soit trois de plus.
Pour ce qui est des périphériques, l'IBM PC avait plusieurs connecteurs : un port série, un port parallèle, un port pour le clavier, et un port pour un lecteur cassette. Le clavier et le lecteur cassette étaient connectés directement sur la carte mère, qui contenait quelques circuits pour gérer le clavier. Par contre, les deux premiers n'étaient pas connectés à la carte mère. Le port série était en réalité une carte d'extension, branchée sur un connecteur XT. Et il en est de même pour le port parallèle.
Pour ce qui est des supports de stockage, l'IBM PC originel n'avait pas de disque dur et n'avait que des lecteurs de disquette. De plus, le lecteur de disquette n'était pas connecté directement sur la carte mère, mais était connecté à une carte d'extension, branchée sur un connecteur XT. La carte d'extension avait deux connecteurs, un par lecteur de disquette, ce qui fait que les deux lecteurs de disquettes pouvaient être branchés sur une seule carte d'extension. L'IBM Personal Computer XT a ajouté un disque dur, sauf sur quelques sous-modèles spécifiques.
Le PC avait aussi un petit haut-parleur capable de faire des bips.
Pour résumer, l'IBM PC originel se reposait beaucoup sur les cartes d'extension, sa carte mère contenait peu de choses. Enfin, peu de choses... Il y avait un processeur Intel 8088, éventuellement un coprocesseur flottant 8087, de la RAM, de la ROM, et des circuits intégrés assez divers. En voici la liste, certains vous seront familiers, d'autres vous seront inconnus à ce stade du cours :
* les circuits de décodage d'adresse ;
* un contrôleur DMA intel 8273 ;
* un contrôleur d'interruption 8259 ;
* un contrôleur de bus Intel 8288 pour gérer le bus XT ;
* un générateur d'horloge Intel 8284 et un diviseur de fréquence ;
* un ''timer'' Intel 8253, le même que celui étudié dans le chapitre sur les ''timers'' ;
* un contrôleur parallèle 8255.
Les multiplexeurs, registres et portes logiques, sont des circuits de décodage d'adresse, qui permettent de combiner plusieurs RAM en une seule, idem avec la mémoire ROM. Si vous verrez qu'il y a 5 mémoires ROM : une ROM pour le BIOS, et quatre autres ROM pour le BASIC. Les 4 ROM du BASIC sont combinées en une seule mémoire ROM. Pour les RAM, il y en a 8 à 32, qui sont combinées en une seule RAM de 16 à 64 kibioctets.
[[File:IBM 5150 Motherboard.svg|centre|vignette|upright=3|Carte mère de l'IBM 5150, un modèle de l'IBM PC.]]
===L'architecture d'un IBM PC compatible 16 bits===
Les PC suivants sont passés à des processeurs 16 bits, mais c'était toujours des processeurs x86 d'Intel, à savoir des Intel 286 et 386. La RAM a grossi, quelques entrées-sorties ont été ajoutées, mais l'architecture globale est plus moins resté le même. C'est surtout au niveau du bus et des périphériques que les changements majeurs ont eu lieu.
[[File:ISA Bus pins.svg|vignette|Connecteur ISA.]]
Les PC 16 bits utilisaient un bus système unique, sur lequel tout était connecté : le processeur, la RAM, la ROM, les cartes d'extension et tout le reste. Le bus en question s'appelait le '''bus AT''', mais il a rapidement été renommé en '''bus ISA''' (''Industry Standard Architecture''). Le bus ISA était prévu pour avoir une compatibilité avec le bus 8 bits de l'IBM PC originel. D'ailleurs, cela se ressent jusque dans le connecteur utilisé : le connecteur ISA est un connecteur XT qu'on a fusionné avec un second connecteur pour l'étendre de 8 à 16 bits.
Les PC 16 bits avaient toujours un port série, un port parallèle, un clavier, un lecteur de disquette et des cartes d'extension. Des disques durs pouvaient être ajoutés, aussi. Mais pour ces périphériques, un changement majeur a eu lieu comparé à l'IBM PC originel. L'IBM PC originel utilisait des cartes d'extension pour tout, sauf le clavier. Mais maintenant, les périphériques ne sont plus connectés à une carte d'extension. À la place, les circuits de la carte d'extension sont déplacés sur la carte mère. Mais n'allez pas croire qu'ils étaient connectés directement au bus ISA, il y avait des intermédiaires.
Le clavier était relié à un '''contrôleur de clavier''', qui faisait l'interface entre le connecteur du clavier et le bus ISA. Le contrôleur de clavier était appelé le ''Keyboard Controler'', abrévié en KB. Il recevait ce qui est tapé au clavier et traduisait cela en quelque chose de compréhensible par l'ordinateur.
Les autres périphériques étaient connectés à un circuit intégré dédié : l''''Intel 82091AA'''. Il était connecté au lecteur de disquette, au port série et au port parallèle. Il servait d'intermédiaire entre ces périphériques et le bus ISA. Vous pouvez le voir comme une sorte de répartiteur, mais qui ne serait pas connecté sur le processeur et la RAM
Enfin, il ne faut pas oublier les autres composants présents sur l'IBM PC originel. Le BIOS est toujours là, de même que les ''timers'' Intel 8253 PIT, le contrôleur d'interruption Intel 8259 et le contrôleur DMA Intel 8237. Les PC 16 bits ont aussi intégré une ''Real Time Clock'' (RTC). Pour rappel, c'est un composant qui permet au PC de mémoriser la date et l'heure courante, à la seconde près. Le tout est résumé dans le schéma ci-dessous.
[[File:Architecture de l'IBM PC compatible.png|centre|vignette|upright=2.5|Architecture de l'IBM PC compatible]]
Un point important est que le bus ISA allait à la même fréquence que le processeur, vu que c'était un bus système. Les processeurs de l'époque étaient des CPU 286 d'Intel, ou le 386 d'Intel. Les Intel 286 allaient de 4 MHz minimum, à 25 MHz maximum. Le 386, quant à lui, allait de 12 à 40 MHz. Le bus ISA devait aller à cette fréquence, il était synchrone avec le processeur.
Par la suite, les processeurs ont gagné en performance, ce qui fait que le bus ISA est devenu trop lent pour le processeur. Une idée a alors été de conserver le bus ISA, pour des raisons de compatibilité, mais de le reléguer comme bus secondaire. L'ordinateur contient alors deux bus : un bus système, et un bus ISA secondaire. Le lien entre les deux est réalisé par un '''pont ISA''', ''ISA Bridge'' en anglais. Le bus ISA fonctionnait alors sa fréquence usuelle, alors que le bus système était beaucoup plus rapide. Le bus système fonctionnait à une fréquence bien plus élevée, ce qui fait que le processeur pouvait communiquer à pleine vitesse, notamment avec la RAM. Le processeur n'était alors plus forcé à aller à la même fréquence que le bus ISA
[[File:Architecture de l'IBM PC compatible avec bridge ISA.png|centre|vignette|upright=2.5|Architecture de l'IBM PC compatible avec bridge ISA]]
Les PC de l'époque intégraient donc plusieurs bus séparés. Vous avez bien lu : plusieurs bus ! Ici, il s'agit de ce que j'appelle des '''bus en cascade''', à savoir qu'un bus est connecté à un autre bus par un intermédiaire. Au passage, si j'aborde ces exemples, car c'est pareil sur les ordinateurs modernes. Le pont ISA a été remplacé par des circuits différents, mais qui ont un rôle assez similaire. Le ''chipset'' de votre carte mère n'est qu'un lointain descendant du pont ISA, qui s'interface avec des bus différents.
===L'arrivée des standards AT et IDE pour les disques durs===
Initialement, les disques durs étaient placés dans l'ordinateur et étaient connectés sur le bus ISA, via une carte d'extension ISA. En clair, il fallait connecter le disque dur sur une carte d'extension, et non sur la carte mère. Les cartes d'extension en question permettaient de connecter un ou plusieurs disques durs, parfois des lecteurs de disquette supplémentaires. Les cartes ISA de ce type faisaient juste l'interface entre le bus ISA et les disques durs, rien de plus. L'interface en question a été standardisée, ce qui a donné le standard ''AT Bus Attachment'', qui a été abrévié en ATA.
Et ce n'était pas que pour les disques durs, de nombreux composants étaient dans ce cas. Une carte d'extension servait d'intermédiaire entre eux et la carte mère. Les cartes d'extension en question étaient appelées des ''Host bus adapter''.
[[File:Acculogic sIDE-4 Controller ISA.jpg|centre|vignette|upright=2|Carte ISA d'interface disque dur, de marque Acculogic.]]
Mais les choses ont rapidement évoluées, que ce soit du côté des cartes mères que du côté des disques durs. Le '''standard IDE''' a permis de brancher un disque dur directement sur la carte mère, sans passer par une carte d'interface ISA. Pour cela, la carte mère réservait un connecteur ISA pour le disque dur, renommé '''connecteur ATA'''. Pour que cela soit possible, il a fallu rajouter des circuits sur la carte mère. Tout ce qui était sur les cartes d'interface ISA s'est retrouvé sur la carte mère.
[[File:Ajout des ports IDE sur la carte mère.png|centre|vignette|upright=2|Ajout des ports IDE sur la carte mère]]
En réalité, les connecteurs ATA étaient des connecteurs ISA simplifiés. Un connecteur ISA avait en tout 98 broches, alors qu'un connecteur ATA n'en contient que 40. Les broches qui étaient inutiles pour les disques durs ont simplement été enlevées. Et qui dit connecteur spécialisé, dit câble spécialisé. Les disques durs étaient branchés sur le connecteur AT grâce à un câble ATA, sur lequel on pouvait connecter deux disques durs.
[[File:ATA Plug.svg|centre|vignette|upright=2|Connecteur ATA.]]
[[File:ATA cables.jpg|centre|vignette|upright=2|Cable ATA.]]
Il était donc possible de connecter deux disques durs sur un seul connecteur ATA. Et cette possibilité est devenue d'autant plus utile par la suite. A partir de la version 2, ATA supportait aussi les lecteurs de disquettes, les lecteurs de CD/DVD, et bien d'autres supports de stockage. Il était alors possible de connecter un lecteur CD et un disque dur sur un seul connecteur. Les cartes mères avaient généralement deux connecteurs ATA, et n'avaient pas besoin de plus. C'était suffisant pour connecter un disque dur, un lecteur de disquette et un lecteur CD, configuration courante entre les années 90 et 2000.
Un câble est donc connecté à deux supports de stockage. Pour distinguer les deux, le standard ATA ajoute une possibilité de configuration. Sur un câble, il doit y avoir un support de stockage "maitre" et un support "esclave". C'était la terminologie de l'époque, que je reproduis ici, même si elle est fortement trompeuse. N'allez pas croire que cela implique que l'un ait des avantages sur l'autre. Le support 'maitre" n'a pas droit à plus de bande passante, il n'a pas la priorité sur l'autre, rien du tout. Il s'agit juste d'un nombre qui permet de savoir avec qui le processeur communique, qui vaut 0 pour le premier support, 1 pour l'autre. Une sorte d'adresse de 1 bit, si l'on veut.
[[File:ATA-Konfiguration02.png|centre|vignette|upright=2|Configuration ATA.]]
Pour configurer un support de stockage en mode "maitre" ou "esclave", le support de stockage avait quelques pins dédiés. Il suffisait de placer un détrompeur en plastique sur les pins adéquats. Les pins se trouvaient à l'arrière du disque dur ou du lecteur de CD/DVD/Disquette/autre.
[[File:HDD Master and Slave Description.jpg|centre|vignette|upright=2|Configuration ''Master/Slave''.]]
===L'architecture d'un PC avec un processeur Intel 486===
Maintenant, passons aux ordinateurs 32 bits, avec l'exemple d'un PC avec un processeur 486 d'Intel. A cette époque, le bus ISA était devenu trop limité et était en place d'être remplacé par le bus PCI, qui avait la même fonction. De nombreuses cartes d'extension utilisaient déjà ce standard et étaient branchées sur des connecteurs PCI dédiés, différents des connecteurs ISA. Intuitivement, on se dit que le bus PCI remplaçait le bus ISA, mais les choses étaient plus compliquées. Les disques durs gardaient leur connecteur ATA, et ne passaient pas par le bus PCI. Ils avaient un bus IDE séparé, qui était un bus ISA modifié.
Là encore, les processeurs étaient devenus beaucoup plus rapides que le bus PCI. Les deux allaient à des fréquences assez différentes, ce qui fait que le bus PCI était séparé du bus système. Il y avait alors deux implémentations possibles.
* La première utilise un répartiteur unique, relié au processeur, à la RAM, au bus PCI, et au bus IDE.
* La seconde utilise un bus système séparé du bus PCI, avec un '''pont PCI''' pour faire l'interface entre les deux.
Le '''''System Controler''''' était un circuit intégré, placé sur la carte mère, qui peut servir soit de pont PCI, soit de répartiteur. Le répartiteur PCI sert d'intermédiaire avec le bus PCI, mais aussi avec le bus IDE, utilisé pour les disques durs, aussi appelé le bus ''Parallel ATA''. Il peut aussi être connecté au processeur, à la mémoire RAM, ainsi qu'à la mémoire cache, mais cela ne sert que quand il est utilisé comme répartiteur.
[[File:Architecture d'un PC utilisant un bus PCI, implémentation avec un répartiteur.png|centre|vignette|upright=2|Architecture d'un PC utilisant un bus PCI, implémentation avec un répartiteur]]
Pour des raisons de compatibilité, le bus ISA avait été conservé, aux côtés du bus PCI. Il y avait un pont ISA en plus du pont/répartiteur PCI. Une implémentation possible aurait été de connecter les deux ponts ISA et PCI à un bus système unique. Mais cette solution n'a pas été retenue. La raison est que le bus PCI et le bus ISA ont des performances très différentes. Le bus PCI est très rapide, le bus ISA beaucoup plus lent. La différence est d'un ordre de grandeur, environ. Dans ces conditions, il est possible de faire passer les communications ISA à travers le bus PCI. Pour cela, le pont ISA est directement connecté sur le pont PCI, comme illustré ci-dessous.
Et il en est de même pour le bus dédié aux disques durs. En effet, les disques durs étaient autrefois reliés au bus ISA, mais cela a changé depuis. Ils disposent maintenant de leur propre bus dédié, le '''bus IDE''', qui est un bus ISA simplifié. Et ce bus ISA simplifié était connecté directement sur le pont PCI.
[[File:Architecture de l'IBM PC compatible avec pont PCI.png|centre|vignette|upright=2|Architecture de l'IBM PC compatible avec pont PCI]]
Dans ce qui va suivre, nous allons étudier un exemple qui utilise un bus système séparé, avec un pont PCI, sans répartiteur. Voilà pour les grandes lignes, mais le schéma ci-dessous montre que tout est plus complexe. Vous remarquerez des connexions optionnelles entre le pont PCI et la mémoire RAM et la mémoire cache. La raison est que le pont PCI peut aussi servir de répartiteur en remplacement du bus système. Concrètement, on peut alors retirer le bus système. La mémoire, le bus PCI, le bus ISA, le bus IDE, le processeur et la RAM sont alors connectés au répartiteur PCI, qui sert d'intermédiaire central entre tous ces composants. Mais ce n'est pas la solution qui a été retenue dans notre exemple.
[[File:Intel486-Typ PCI System.png|centre|vignette|upright=2|PC IBM compatible avec un 486, un bus PCI et un bus ISA. Le ''host bus'' est le bus système.]]
Le pont ISA sert ici d'intermédiaire entre le bus système et le bus ISA. De plus, il a été amélioré sur de nombreux points. Il inclut notamment des circuits qui étaient autrefois sur la carte mère, à savoir le contrôleur DMA 82C87 et le contrôleur d'interruption 82C59, ainsi que les ''timers'' Intel 82C54. Les composants restants sont eux reliés sur un quatrième bus : le bus X, l'ancêtre du bus ''Low Pin Count''. Le bus X était celui du BIOS, du contrôleur de clavier, de la ''Real Time Clock'', et du contrôleur de périphérique 82091AA d'Intel.
[[File:ISA Bridge schematic.png|centre|vignette|upright=2|ISA Bridge.]]
===L'architecture des PC des années 90-2000===
Par la suite, les ponts PCI et ISA ont évolué avec l'évolution des bus de l'ordinateur. Le bus ISA a progressivement été remplacé par d'autres bus, comme le bus ''Low Pin Count'', le bus PCI a été remplacé par le PCI Express, d'autres bus ont été ajoutés, etc. Mais la séparation du ''chipset'' en deux a été conservée.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Le pont PCI et le pont ISA ont été remplacés respectivement par le '''pont nord''' et le '''pont sud''', plus connus par leurs noms anglais de ''northbridge'' et de ''southbridge''. Le pont nord servait d'interface entre le processeur, la mémoire et la carte graphique et est connecté à chacun par un bus dédié. Il intégrait aussi le contrôleur mémoire. Le pont sud est le répartiteur pour les composants lents, à savoir l'USB, l'Ethernet, etc. Le bus qui relie le processeur au pont nord était appelé le '''''Front Side Bus''''', abrévié en FSB.
[[File:IMac Chipset.png|centre|vignette|upright=2|Chipset séparé en northbridge et southbridge.]]
Un point important est que le bus PCI est devenu un bus assez lent, ce qui fait qu'il a finit par être connecté au pont sud. Le pont PCI est donc devenu le pont sud, dans le courant des années 2000. Durant un moment, un équivalent du pont ISA a subsisté dans un circuit de '''''Super IO'''''. Concrètement, il s'occupait du lecteur de disquette, du port parallèle, du port série, et des ports PS/2 pour le clavier et la souris. Mais il ne gérait pas le bus ISA, mais son remplaçant, le bus ''Low Pin Count''.
[[File:Motherboard diagram fr.svg|centre|vignette|upright=1.5|Carte mère avec circuit Super IO.]]
===L'architecture des PC depuis les années 2000===
Depuis la sortie du processeur AMD Athlon 64, le pont nord a été fusionné dans le processeur. La fusion ne s'est pas faite en une fois, des fonctionnalités ont progressivement été progressivement intégrées dans le processeur. Le pont sud est resté, mais il a alors été progressivement connecté directement au processeur. La raison derrière cette intégration est que les processeurs avaient de plus en plus de transistors à leur disposition. Ils en ont profité pour intégrer le pont nord. Et cela permettait de simplifier le câblage des cartes mères, sans pour autant rendre vraiment plus complexe la fabrication du processeur. Les industriels y trouvent leur compte.
La première étape a été l'intégration du contrôleur mémoire a été intégré au processeur. Concrètement, le résultat était que la mémoire RAM n'était plus connectée au pont nord, mais était connectée directement au processeur ! Il y a donc eu un retour d'un bus mémoire, mais spécialisé pour la mémoire RAM. En théorie, une telle intégration permet diverses optimisations quant aux transferts avec la mémoire RAM. Les transferts ne passent pas par un répartiteur, ce qui réduit le temps d'accès à la mémoire RAM. Ajoutons de sombres histoires de prefetching, d'optimisation des commandes, et j'en passe. Toujours est-il que le pont nord ne servait alors d'intermédiaire que pour les ports PCI Express, et le pont sud.
[[File:X58 Block Diagram.png|centre|vignette|upright=2|Chipset X58 d'Intel.]]
Par la suite, la carte graphique fût aussi connectée directement sur le processeur. Le processeur incorpore pour cela des contrôleurs PCI-Express. Le pont nord a alors disparu complétement, son intégration dans le processeur était complète. Sur les cartes mères Intel récentes, le pont sdud subsiste, il est appelé le ''Platform Controler Hub'', ou PCH. L'organisation des bus sur la carte mère qui résulte de cette connexion du processeur à la carte graphique, est illustrée ci-dessous, avec l'exemple du PCH.
[[File:Intel 5 Series architecture.png|centre|vignette|upright=2|Intel 5 Series architecture]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'interface électrique entre circuits intégrés et bus
| prevText=L'interface électrique entre circuits intégrés et bus
| next=La hiérarchie mémoire
| nextText=La hiérarchie mémoire
}}
</noinclude>
izskljsxgetn5byot24q99newvont6f
775632
775631
2026-11-02T15:03:04Z
Mewtow
31375
/* D'où viennent les adresses ? */
775632
wikitext
text/x-wiki
Dans les chapitres précédents, nous avons vu comment représenter de l'information, la traiter et la mémoriser avec des circuits. Mais un ordinateur n'est pas qu'un amoncellement de circuits et est organisé d'une manière bien précise. Il est structuré autour de trois circuits principaux :
* un '''processeur''', qui manipule l'information et donne un résultat ;
* une '''mémoire''' qui mémorise les données à manipuler ;
* les '''entrées/sorties''', qui permettent à l'ordinateur de communiquer avec l'extérieur.
[[File:Architecture Von Neumann.png|centre|vignette|upright=2|Architecture d'un système à mémoire.]]
Pour faire simple, le processeur est un circuit qui s'occupe de faire des calculs. Rien d'étonnant à cela. Je rappelle que tout est codé par des nombres dans un ordinateur, ce qui fait que manipuler des nombres revient simplement à faire des calculs. Un ordinateur n'est donc qu'une grosse calculatrice améliorée, et le processeur est le composant qui fait les calculs.
La mémoire s'occupe purement de la mémorisation des données, des nombres sur lesquelles faire des calculs. Pour être plus précis, il y a deux mémoires : une pour les données proprement dites, une autre pour le programme à exécuter. La première est la '''mémoire RAM''', la seconde est la '''mémoire ROM'''. Nous détaillerons ce que sont ces deux mémoires dans la suite du chapitre, mais sachez que nous avions déjà rencontré ces deux types de mémoires dans les chapitres sur les registres et les mémoires adressables.
Les entrées-sorties permettent au processeur et à la mémoire de communiquer avec l'extérieur et d'échanger des informations avec des périphériques. Les '''périphériques''' regroupent, pour rappel, tout ce est branché sur un ordinateur, mais n'est pas à l'intérieur de celui-ci.
Le processeur, les mémoires et les entrées-sorties communiquent ensemble via un '''réseau d'interconnexions'''. Le terme est assez barbare, mais rien de compliqué sur le principe. C'est juste un ensemble de fils électriques qui relie les différents éléments d'un ordinateur. Les interconnexions sont souvent appelées le bus de communication, mais le terme est un abus de langage, comme on le verra plus bas.
Afin de simplifier les explications, on va supposer que le réseau d'interconnexion est le suivant. Tout est connecté au processeur. Il y a des interconnexions entre le processeur et la mémoire RAM, d'autres interconnexions entre processeur et mémoire ROM, et d'autres entre le processeur et les entrées-sorties. Nous verrons que d'autres réseaux d'interconnexions fusionnent certaines interconnexions, pour les partager entre la ROM et la RAM, par exemple. Mais pour le moment, gardez le schéma ci-dessous en tête.
[[File:Réseau d'interconnexion avec un processeur au centre.png|centre|vignette|upright=2|Réseau d'interconnexion avec un processeur au centre]]
==Les mémoires RAM et ROM==
La mémoire est le composant qui mémorise des informations, des données. Dans la majorité des cas, la mémoire est composée de plusieurs '''cases mémoire''', chacune mémorisant plusieurs bits, le nombre de bits étant identique pour toutes les cases mémoire. Dans le cas le plus simple, une case mémoire mémorise un '''octet''', un groupe de 8 bits. Mais les mémoires modernes mémorisent plusieurs octets par case mémoire : elles ont des cases mémoires de 16, 32 ou 64 bits, soit respectivement 2/4/8 octets. De rares mémoires assez anciennes utilisaient des cases mémoires contenant 1, 2, 3, 4, 5, 6 7, 13, 17, 23, 36 ou 48 bits. Mais ce n'était pas des mémoires électroniques, aussi nous allons les passer sous silence.
Tout ce qu'il faut savoir est que la quasi-totalité des mémoires électronique a un ou plusieurs octets par case mémoire. Pour simplifier, vous pouvez imaginer qu'une mémoire RAM est un regroupement de registre, chacun étant une case mémoire. C'est une description pas trop mauvaise pour décrire les mémoires RAM, qu'on abordera dans ce qui suit.
{|class="wikitable"
|+ Contenu d'une mémoire, case mémoire de 16 bits (deux octets)
|-
! Case mémoire N°1
| 0001 0110 1111 1110
|-
! Case mémoire N°2
| 1111 1110 0110 1111
|-
! Case mémoire N°3
| 0001 0000 0110 0001
|-
! Case mémoire N°4
| 1000 0110 0001 0000
|-
! Case mémoire N°5
| 1100 1010 0110 0001
|-
! ...
| ...
|-
! Case mémoire N°1023
| 0001 0110 0001 0110
|-
! Case mémoire N°1024
| 0001 0110 0001 0110
|}
Dans ce cours, il nous arrivera de partir du principe qu'il y a un octet par case mémoire, par souci de simplification. Mais ce ne sera pas systématique. De plus, il nous arrivera d'utiliser le terme adresse pour parler en réalité de la case mémoire associée, par métonymie.
===La capacité mémoire===
Bien évidemment, une mémoire ne peut stocker qu'une quantité finie de données. Et à ce petit jeu, certaines mémoires s'en sortent mieux que d'autres et peuvent stocker beaucoup plus de données que les autres. La '''capacité''' d'une mémoire correspond à la quantité d'informations que celle-ci peut mémoriser. Plus précisément, il s'agit du nombre maximal de bits qu'une mémoire peut contenir. Elle est le produit entre le nombre de cases mémoire, et la taille en bit d'une case mémoire.
Toutes les mémoires actuelles utilisant des cases mémoire d'un ou plusieurs octets, ce qui nous arrange pour compter la capacité d'une mémoire. Au lieu de compter cette capacité en bits, on préfère mesurer la capacité d'une mémoire avec le nombre d'octets qu'elle contient. Mais les mémoires des PC font plusieurs millions ou milliards d'octets. Pour se faciliter la tâche, on utilise des préfixes pour désigner les différentes capacités mémoires. Vous connaissez sûrement ces préfixes : kibioctets, mébioctets et gibioctets, notés respectivement Kio, Mio et Gio.
{|class="wikitable"
|-
!Préfixe!!Capacité mémoire en octets!!Puissance de deux
|-
||Kio||1024||2<sup>10</sup> octets
|-
||Mio||1 048 576||2<sup>20</sup> octets
|-
||Gio||1 073 741 824||2<sup>30</sup> octets
|}
On peut se demander pourquoi utiliser des puissances de 1024, et ne pas utiliser des puissances un peu plus communes ? Dans la majorité des situations, les électroniciens préfèrent manipuler des puissances de deux pour se faciliter la vie. Par convention, on utilise souvent des puissances de 1024, qui est la puissance de deux la plus proche de 1000. Or, dans le langage courant, kilo, méga et giga sont des multiples de 1000. Quand vous vous pesez sur votre balance et que celle-ci vous indique 58 kilogrammes, cela veut dire que vous pesez 58 000 grammes. De même, un kilomètre est égal à 1000 mètres, et non 1024 mètres.
Autrefois, on utilisait les termes kilo, méga et giga à la place de nos kibi, mebi et gibi, par abus de langage. Mais peu de personnes sont au courant de l'existence de ces nouvelles unités, et celles-ci sont rarement utilisées. Et cette confusion permet aux fabricants de disques durs de nous « arnaquer » : Ceux-ci donnent la capacité des disques durs qu'ils vendent en kilo, méga ou giga octets : l’acheteur croit implicitement avoir une capacité exprimée en kibi, mébi ou gibi octets, et se retrouve avec un disque dur qui contient moins de mémoire que prévu.
===Lecture et écriture : mémoires ROM et RWM===
Pour simplifier grandement, on peut grossièrement classer les mémoires en deux types : les ''Read Only Memory'' et les ''Read Write Memory'', aussi appelées mémoires ROM et mémoires RWM. Pour les '''mémoires ROM''', on ne peut pas modifier leur contenu. On peut y récupérer une donnée ou une instruction : on dit qu'on y accède en lecture. Mais on ne peut pas modifier les données qu'elles contiennent. Quant aux '''mémoires RWM''', on peut y accéder en lecture (récupérer une donnée stockée en mémoire), mais aussi en écriture : on peut stocker une donnée dans la mémoire, ou modifier une donnée existante.
Tout ordinateur contient au minimum une ROM et une RWM (souvent une mémoire RAM), les deux n'ont pas exactement le même rôle. Pour simplifier, la mémoire ROM mémorise le programme à exécuter, la mémoire RWM stocke des données. Il a existé des ordinateurs où la mémoire RWM était une mémoire magnétique, voire acoustique, mais ce n'est plus le cas de nos jours. Pour les ordinateurs modernes, la mémoire RWM est une mémoire électronique appelée la '''mémoire RAM'''. La mémoire RAM est utilisée pour stocker temporairement des données que le processeur doit manipuler. La mémoire RAM est donc une mémoire RWN qui a les caractéristiques suivantes :
* La mémoire RAM est une mémoire électronique, fabriquée avec des semi-conducteurs.
* La mémoire RAM s'efface complètement quand on coupe l'alimentation de l'ordinateur (on dit qu'elle est volatile).
* La mémoire RAM possède un temps d'accès approximativement constant aux données (quelle que soit l'adresse).
Et ce sont des caractéristiques que d'autres mémoires RWM n'ont pas :
* Une mémoire RWM peut être magnétique (mémoires à tore de ferrite), acoustique, basées sur des CRTs (tubes Wlliams).
* Une mémoire RWM peut ne pas être volatile et donc conserver les données écrites. L'exemple type étant les mémoires à tore de ferrite. Précisons cependant qu'il existe des prototypes de mémoires FERAM, MRAM et autres, qui sont des RAM non-volatiles.
* Une mémoire RWN n'a pas forcément un temps d'accès constant aux données, ce n'est pas garanti pour les mémoires RWM. Par exemple, les anciens tambours magnétiques des anciens ''mainframes'' étaient des mémoires RWM non-volatiles, utilisées comme stockage temporaire.
Outre le programme à exécuter, la mémoire ROM peut mémoriser des constantes, des données qui ne changent pas. Elles ne sont jamais modifiées et gardent la même valeur quoi qu'il se passe lors de l'exécution du programme. En conséquence, elles ne sont jamais accédées en écriture durant l'exécution du programme, ce qui fait que leur place est dans une mémoire ROM. La mémoire RWM est alors destinée aux données temporaires, qui changent ou sont modifiées lors de l'exécution du programme, et qui sont donc manipulées aussi bien en lecture et en écriture. La mémoire RWM mémorise alors les variables du programme à exécuter, qui sont des données que le programme va manipuler. Pour les systèmes les plus simples, la mémoire RWM ne sert à rien de plus.
[[File:Espaces d'adressage sur une archi harvard modifiée.png|centre|vignette|upright=2.5|Espaces d'adressage sur une archi harvard modifiée]]
Pour donner un exemple de données stockées en ROM, on peut prendre l'exemple des anciennes consoles de jeu 8 et 16 bits. Les jeux vidéos sur ces consoles étaient placés dans des cartouches de jeu, précisément dans une mémoire ROM à l'intérieur de la cartouche de jeu. La ROM mémorisait non seulement le code du jeu, le programme du jeu vidéo, mais aussi les niveaux et les ''sprites'' et autres données graphiques.
Une conséquence est que les consoles 8/16 bits n'avaient pas besoin de beaucoup de RAM, comparé aux ordinateurs de l'époque, vu qu'une grande partie des données utiles étaient dans une ROM directement accessible par le processeur. À l'opposé, les micro-ordinateurs devaient copier les données d'un jeu depuis une disquette dans la mémoire RAM, ce qui demandait d'avoir plus de RAM. Le passage au support CD sur les consoles 32 bits a eu la même conséquence. Le processeur ne pouvant pas lire directement le CD à sa guise, il fallait copier les données du CD en RAM. D'où l'apparition de temps de chargement assez longs, inexistants sur support cartouche.
===L'adressage mémoire===
Sur une mémoire RAM ou ROM, on ne peut lire ou écrire qu'une case mémoire, qu'un registre à la fois : une lecture ou écriture ne peut lire ou modifier qu'une seule case mémoire. Techniquement, le processeur doit préciser à quel case mémoire il veut accéder à chaque lecture/écriture. Pour cela, chaque case mémoire se voit attribuer un nombre binaire unique, l''''adresse''', qui va permettre de le sélectionner et de l'identifier celle-ci parmi toutes les autres. En fait, on peut comparer une adresse à un numéro de téléphone (ou à une adresse d'appartement) : chacun de vos correspondants a un numéro de téléphone et vous savez que pour appeler telle personne, vous devez composer tel numéro. Les adresses mémoires en sont l'équivalent pour les cases mémoire.
[[File:Adressage mémoire.png|centre|vignette|upright=2|Exemple : on demande à la mémoire de sélectionner la case mémoire d'adresse 1002 et on récupère son contenu (ici, 17).]]
L'adresse mémoire est générée par le processeur. Le processeur peut parfaitement calculer des adresses, en extraire du programme qu'il exécute, et bien d'autres choses. Nous détaillerons d'ailleurs les mécanismes pour dans les chapitres portant sur les modes d'adressage du processeur. Mais pour le moment, nous avons juste besoin de savoir que c'est le processeur qui envoie des adresses aux mémoires RAM et ROM.
Les adresses générées par le processeur sont alors envoyées à la RAM ou la ROM via une connexion dédiée, un ensemble de fils qui connecte le processeur à la mémoire : le '''bus d'adresse mémoire'''. L'adresse sélectionne une case mémoire, le processeur peut alors récupérer la donnée dedans pour une lecture, écrire une donnée pour l'écriture. Pour cela, un second ensemble de fil connecte le processeur à la RAM/ROM, mais cette fois-ci pour échanger des données. Il s'agit du '''bus de données mémoire'''. Les deux sont souvent regroupés sous le terme de '''bus mémoire'''.
Un ordinateur contient toujours une RAM et une ROM, ce qui demande aux bus mémoire de s'adapter à la présence de deux mémoires. Il y a alors deux solutions, illustrées dans les deux schémas ci-dessous. Avec la première, il y a un seul bus mémoire partagé entre la RAM et la ROM, comme illustré ci-dessous. Une autre solution utilise deux bus séparés : un pour la RAM et un autre pour la ROM. Nous verrons les différences pratiques entre les deux à la fin du chapitre. Pour le moment, nous allons partir du principe qu'il y a un bus pour la mémoire ROM, et un autre bus pour la RAM.
[[File:CPT-System-Architecture-gapfill1-ANS.svg|centre|vignette|upright=2|Architecture avec une ROM et une RAM.]]
[[File:Réseau d'interconnexion avec un processeur au centre.png|centre|vignette|upright=2|Réseau d'interconnexion avec un processeur au centre]]
===L'alignement mémoire : introduction===
Plus haut, nous avions dit qu'il y a une adresse par case mémoire, chaque case mémoire contenant un ou plusieurs octets. Mais les processeurs modernes partent du principe que la mémoire a un octet par adresse, pas plus. Et ce même si la mémoire reliée au processeur utilise des cases mémoires de 2, 3, 4 octets ou plus. D'ailleurs, la majorité des mémoires RAM actuelle a des cases mémoires de 64 bits, soit 8 octets par case mémoire. Les raisons à cela sont multiple, mais nous les verrons en détail dans le chapitre sur l'alignement mémoire. Toujours est-il qu'il faut distinguer les '''adresses mémoire''' et les '''adresses d'octet''' gérées par le processeur.
Le processeur génère des adresses d'octet, qui permettent de sélectionner un octet bien précis. L'adresse d'octet permet de sélectionner un octet parmi tous les autres. Mais la mémoire ne comprend pas directement cette adresse d'octet. Heureusement, l'octet en question est dans une case mémoire bien précise, qui a elle-même une adresse mémoire bien précise. L'adresse d'octet est alors convertie en une adresse mémoire, qui sélectionne la case mémoire adéquate, celle qui contient l'octet voulu. La case mémoire entière est lue, puis le processeur ne récupère que les données adéquates. Pour cela, des circuits d'alignement mémoire se chargent de faire la conversion entre adresses du processeur et adresse mémoire. Nous verrons cela dans le détail dans le chapitre sur l'alignement mémoire.
Il existe des mémoires qui n'utilisent pas d'adresses mémoire, mais passons : ce sera pour la suite du cours.
==Le processeur==
Dans les ordinateurs, l'unité de traitement porte le nom de '''processeur''', ou encore de '''''Central Processing Unit''''', abrévié en CPU. Le rôle principal du processeur est de faire des calculs. La plupart des processeurs actuels supportent au minimum l'addition, la soustraction et la multiplication. Quelques processeurs ne gèrent pas la division, qui est une opération très gourmande en circuit, peu utilisée, très lente. Il arrive que des processeurs très peu performants ne gèrent pas la multiplication, mais c'est assez rare.
Un processeur ne fait pas que des calculs. Tout processeur est conçu pour effectuer un nombre limité d'opérations bien précises, comme des calculs, des échanges de données avec la mémoire, etc. Ces opérations sont appelées des '''instructions'''. Les plus intuitives sont les '''instructions arithmétiques''', qui font des calculs, comme l'addition, la soustraction, la multiplication, la division. Mais il y a aussi des '''instructions d'accès mémoire''', qui échangent des données entre la mémoire RAM et le processeur. Les autres instructions ne sont pas très intuitives, aussi passons-les sous silence pour le moment, tout deviendra plus clair dans les chapitres sur le processeur.
===Le processeur exécute un programme, une suite d'instructions===
Tout processeur est conçu pour exécuter une suite d'instructions dans l'ordre demandé, cette suite s'appelant un '''programme'''. Ce que fait le processeur est défini par la suite d'instructions qu'il exécute, par le programme qu'on lui demande de faire. Les instructions sont exécutées dans un ordre bien précis, les unes après les autres. L'ordre en question est décidé par le programmeur. La totalité des logiciels présents sur un ordinateur sont des programmes comme les autres.
Le programme à exécuter est stockée dans la mémoire de l'ordinateur. C'est ainsi que l'ordinateur est rendu programmable : modifier le contenu de la mémoire permet de changer le programme exécuté. Mine de rien, cette idée de stocker le programme en mémoire est ce qui a fait que l’informatique est ce qu'elle est aujourd’hui. C'est la définition même d'ordinateur : appareil programmable qui stocke son programme dans une mémoire modifiable.
Une instruction est codée comme les données : sous la forme de suites de bits. Telle suite de bit indique qu'il faut faire une addition, telle autre demande de faire une soustraction, etc. Pour simplifier, nous allons supposer qu'il y a une instruction par adresse mémoire. Sur la grosse majorité des ordinateurs, les instructions sont placées les unes à la suite des autres dans l'ordre où elles doivent être exécutées. Un programme informatique n'est donc qu'une vulgaire suite d'instructions stockée quelque part dans la mémoire de l'ordinateur.
{|class="wikitable"
|+ Exemple de programme informatique
|-
! Adresse
! Instruction
|-
! 0
| Copier le contenu de l'adresse 0F05 dans le registre numéro 5
|-
! 1
| Charger le contenu de l'adresse 0555 dans le registre numéro 4
|-
! 2
| Additionner ces deux nombres
|-
! 3
| Charger le contenu de l'adresse 0555
|-
! 4
| Faire en XOR avec le résultat antérieur
|-
! ...
| ...
|-
! 5464
| Instruction d'arrêt
|}
Pour exécuter une suite d'instructions dans le bon ordre, le processeur détermine à chaque cycle quelle est la prochaine instruction à exécuter. Pour cela, le processeur mémorise l'adresse de l'instruction en cours dans un registre : le '''Program Counter'''. Je rappelle que des instructions consécutives sont dans des adresses consécutives. Pour passer à la prochaine instruction, il suffit donc d'incrémenter le ''program counter''.
: Si une instruction prend plusieurs octets, plusieurs adresses, il suffit de l'incrémenter du nombre d'octets/adresses.
D'autres processeurs font autrement : chaque instruction précise l'adresse de la suivante, directement dans la suite de bit représentant l'instruction en mémoire. Ces processeurs n'ont pas besoin de calculer une adresse qui leur est fournie sur un plateau d'argent. Sur des processeurs aussi bizarres, pas besoin de stocker les instructions en mémoire dans l'ordre dans lesquelles elles sont censées être exécutées. Mais ces processeurs sont très très rares et peuvent être considérés comme des exceptions à la règle.
Nous venons de voir qu'un processeur contient un registre appelé le ''program counter''. Mais il n'est pas le seul. Pour pouvoir fonctionner, tout processeur doit mémoriser un certain nombre d’informations nécessaires à son fonctionnement, qui sont mémorisées dans des '''registres de contrôle'''. La plupart ont des noms assez barbares (registre d'état, ''program counter'') et nous ne pouvons pas en parler à ce moment du cours. Nous les verrons en temps voulu, mais il est important de préciser qu'ils existent.
===L'intérieur d'un processeur===
Fort de ce que nous savons, nous pouvons expliquer ce qu'il y a à l'intérieur d'un processeur. Le premier point est qu'un processeur fait des calculs, ce qui implique qu'il contient des circuits de calcul. Ils sont regroupés dans une ou plusieurs '''unités de calcul'''. Nous avons déjà vu comment fabriquer une unité de calcul simple, dans un chapitre dédié, et c'est la même qui est présente dans un processeur. Du moins dans les grandes lignes, les circuits des processeurs modernes étant particulièrement optimisés. Il en est de même pour les autres circuits de calcul comme ceux pour les multiplications/division/autres.
Si le processeur fait des calculs, qu'en est-il des opérandes ? Et où sont mémorisés les résultats des opérations ? Pour cela, le processeur incorpore des '''registres généraux''', qui mémorisent les opérandes et résultats des instructions. Ils mémorisent des données, contrairement aux registres de contrôle mentionnés plus haut. Le nombre de registres généraux dépend grandement du processeur. Les tout premiers processeurs se débrouillaient avec un seul registre, mais les processeurs actuels utilisent plusieurs registres, pour mémoriser plusieurs opérandes/résultats. Mais la présence de registres est source de pas mal de petites complications que nous expliquerons dans les chapitres sur le processeur.
Le processeur contient aussi de quoi communiquer avec la mémoire RAM. Il y a une unité dédiée, un circuit dédié pour, appelé l'''unité mémoire'''. Elle permet d'échanger les données entre la RAM et les registres, mais aussi de simplement lire les instructions à exécuter.
Le processeur contient enfin un circuit pour interpréter les instructions, appelé l''''unité de contrôle'''. Elle lit les instructions depuis la mémoire, interprète la suite de bit associée, et commande le reste du processeur pour qu'il exécute l'instruction. Ses fonctions sont assez variées, mais nous allons simplifier en disant qu'elle configure l'unité de calcul et les registres pour faire le bon calcul. Son rôle est d'analyser la suite de bit qui constitue l'instruction, et d'en déduire quelle opération effectuer. Elle gère aussi l'accès à la mémoire RAM, et notamment ce qui est envoyé sur son bus d'adresse.
: Dans ce qui suit, on suppose que le ''program counter'' fait partie de l'unité de contrôle.
Pour résumer, un processeur contient une unité de calcul, des registres et une interface avec la mémoire RAM. Le tout est interconnecté, afin de pouvoir échanger des données. L’ensemble forme le '''chemin de données''', nom qui trahit le fait que c'est là que les données se déplacent et sont traitées. Il faut aussi ajouter l'unité de contrôle pour commander le tout. Elle lit les instructions en mémoire, puis commande le chemin de données pour que l'instruction soit exécutée correctement.
[[File:Microarchitecture d'un processeur.png|centre|vignette|upright=2|Microarchitecture d'un processeur]]
Un processeur parait donc assez simple expliqué comme ça, mais il y a de nombreuses subtilités. L'une d'entre elle est liée aux registres, et notamment à la manière dont on les utilise. Pour expliquer ces subtilités, nous allons voir comment les premiers processeurs fonctionnaient, avant de passer aux processeurs un peu plus modernes. Les tout premiers processeurs n'utilisaient qu'un seul registre, ce qui fait que l'utilisation des registres était très simple. Le passage à plusieurs registres a complexifié le tout.
===D'où viennent les adresses ?===
Il est maintenant temps de répondre à une question qui s'était posée dans la section sur l'adressage : d'où viennent les adresses envoyées à la mémoire ? Pour ce qui est des adresses des instructions, on connait déjà la réponse : c'est le ''program counter'' qui gère tout. Mais pour les données, il y a deux possibilités, qui correspondent à deux types de données : les données statiques et les données dynamiques.
Les '''données statiques''' sont les plus simples : elles existent durant toute la durée de vie du programme. Tant que celui-ci s'exécute, il aura besoin de ces données. En conséquence, il leur réserve une place en mémoire, qui est toujours la même. La donnée se situe donc à une adresse bien précise, qui ne change jamais. Attention cependant : les données peuvent être modifiées, changer de valeur. Le programme écrit dans les donnée statiques, c'est même assez fréquent. Ce sont ne sont pas forcément des données constantes !
Pour les données statiques, elles sont toujours placées à la même adresse mémoire. Pour le dire autrement, l'adresse mémoire est une constante, qui ne change pas. L'adresse est connue avant d’exécuter le programme, le programme a été codé pour utiliser cette adresse pour telle donnée, on a réservé une adresse pour la donnée voulue. Et même si vous quittez le programme et vous le relancez plusieurs jours après, l'adresse mémoire sera la même avant et après.
Et cela permet d'utiliser l''''adressage direct'''. L'idée est que les instructions précisent donc l'adresse à lire ou écrire. Pour cela, l'adresse est intégrée dans l’instruction elle-même. Pour rappel, l'instruction est codée par une suite de bit en mémoire RAM/ROM, dont certains précisent l'opération à faire, les autres servant à autre chose. L'idée est que certains bits précisent l'adresse mémoire de la donnée à lire. Les instructions sont donc du genre :
* ''LOAD 56 -> R9'' - lit l'adresse numéro 56 et copie son contenu dans le registre numéro 9 ;
* ''STORE R5 , adress 99'', copie le registre R5 dans l'adresse 99 ;
* ''ADD R5 , adress 209'' : additionne le registre R5 avec le contenu de l'adresse 209.
S'il existe des données statiques, c'est signe qu'il existe des '''données dynamiques'''. Ces dernières sont des données qui sont créées ou détruites selon les besoins. Pour comprendre d'où viennent ces données dynamiques, prenons le cas d'une personne qui écrit du texte sur un ordinateur. Le texte qu'il écrit est mémorisé dans la RAM de l’ordinateur, puis est sauvegardé sur le disque dur quand il sauvegarde son document. Au fur et à mesure qu'il écrit du texte, la RAM utilisée par ce texte augmente. Donc, ce texte est une donnée dynamique, dont la taille varie dans le temps.
Pour gérer des données dynamiques, la plupart des systèmes d'exploitation incorporent des fonctionnalités d''''allocation mémoire'''. Derrière ce nom barbare, se cache quelque chose de simple : les programmes peuvent réclamer de la mémoire au système d'exploitation, pour y placer les données qu'ils souhaitent. Les langages de programmation bas niveau supportent des fonctions comme malloc(), qui permettent de demander un bloc de mémoire de N octets à l'OS, qui doit alors accommoder la demande. De même, un programme peut libérer de la mémoire qu'il n'utilise plus avec des fonctions comme free().
Avec l'allocation mémoire, les données n'ont pas de place fixe en mémoire. Leur adresse mémoire peut varier d'une exécution du programme à l'autre. Pire que ça : les données peuvent être déplacées dans la mémoire RAM, si besoin. En clair : l'adresse est déterminée lors de l'exécution du programme. L'adresse est alors soit calculée, soit lue depuis la mémoire RAM, soit déterminée autrement. Toujours est-il qu'elle se retrouve dans un registre général. Pour gérer ces adresse variables, les processeurs utilisent l''''adressage indirect'''. L'adressage indirect permet d'utiliser une adresse qui est dans un registre de données. L'adresse en question peut être envoyée à la mémoire RAM sans problème, le processeur peut automatiquement connecter le registre adéquat sur le bus d'adresse.
Au tout début de l'informatique, les processeurs ne supportaient que l'adressage direct, pas plus. L'adressage indirect n'était tout simplement pas possible. Avec l'adressage direct, l'adresse à lire est extraite directement des instructions, par l'unité de contrôle. Le bus d'adresse de la RAM est alors relié directement à l'unité de contrôle, le bus de données est relié aux registres.
[[File:Architecture Harvard avec adressage direct uniquement.png|centre|vignette|upright=2|Architecture Harvard avec adressage direct uniquement]]
Mais dès les années 70, l'adressage indirect est apparu, rendant la programmation bien plus simple. Mais cela a demandé quelques adaptation au niveau du processeur. Avec l'adressage indirect, le bus d'adresse est connecté aux registres. Il a donc fallu rajouter un multiplexeur pour que le processeur décide de relier le bus d'adresse soit aux registres, soit à l'unité de contrôle.
===Un ordinateur peut avoir plusieurs processeurs===
La plupart des ordinateurs n'ont qu'un seul processeur, ce qui fait qu'on désigne avec le terme d''''ordinateurs mono-processeur'''. Mais il a existé (et existe encore) des '''ordinateurs multi-processeurs''', avec plusieurs processeurs sur la même carte mère. L'idée était de gagner en performance : deux processeurs permettent de faire deux fois plus de calcul qu'un seul, quatre permettent d'en faire quatre fois plus, etc. C'est très courant sur les supercalculateurs, des ordinateurs très puissants conçus pour du calcul industriel ou scientifique, mais aussi sur les serveurs ! Dans le cas le plus courant, ils utilisent plusieurs processeurs identiques : on utilise deux processeurs Core i3 de même modèle, ou quatre Pentium 3, etc.
Pour utiliser plusieurs processeurs, les programmes doivent être adaptés. Pour cela, il y a plusieurs possibilités :
* Une première possibilité, assez intuitive, est d’exécuter des programmes différents sur des processeurs différents. Par exemple, on exécute le navigateur web sur un processeur, le lecteur vidéo sur un autre, etc.
* La seconde option est de créer des programmes spéciaux, qui utilisent plusieurs processeurs. Ils répartissent les calculs à faire sur les différents processeurs. Un exemple est la lecture d'une vidéo sur le web : un processeur peut télécharger la vidéo pendant le visionnage et bufferiser celle-ci, un autre processeur peut décoder la vidéo, un autre décoder l'audio. De tels programmes restent des suites d'instructions, mais ils sont plus complexes que les programmes normaux, aussi nous les passons sous silence.
* La troisième option est d’exécuter le même programme sur les différents processeurs, mais chaque processeur traite son propre ensemble de données. Par exemple, pour un programme de rendu 3D, quatre processeurs peuvent s'occuper chacun d'une portion de l'image.
[[File:Architecture de Von Neumann Princeton multi processeurs.svg|centre|vignette|upright=2|Architecture de Von Neumann Princeton multi processeurs]]
De nos jours, les ordinateurs grand public les plus utilisés sont dans un cas intermédiaire, ils ne sont ni mono-, ni multi-processeur. Ils n'ont qu'un seul processeur, dans le sens où si on ouvre l'ordinateur et qu'on regarde la carte mère, il n'y a qu'un seul processeur. Mais ce processeur est en réalité assez similaire à un regroupement de plusieurs processeurs dans le même boitier. Il s'agit de '''processeurs multicœurs''', qui contiennent plusieurs cœurs, chaque cœur pouvant exécuter un programme tout seul.
La différence entre cœur et processeur est assez difficile à saisir, mais pour simplifier : un cœur est l'ensemble des circuits nécessaires pour exécuter un programme. Chaque cœur dispose de toute la machinerie électronique pour exécuter un programme, à savoir des circuits aux noms barbares comme : un séquenceur d'instruction, des registres, une unité de calcul. Par contre, certains circuits d'un processeur ne sont présents qu'en un seul exemplaire dans un processeur multicœur, comme les circuits de communication avec la mémoire ou les circuits d’interfaçage avec la carte mère.
Suivant le nombre de cœurs présents dans notre processeur, celui-ci sera appelé un processeur double-cœur (deux cœurs), quadruple-cœur (4 cœurs), octuple-cœur (8 cœurs), etc. Un processeur double-cœur est équivalent à avoir deux processeurs dans l'ordinateur, un processeur quadruple-cœur est équivalent à avoir quatre processeurs dans l'ordinateur, etc. Ces processeurs sont devenus la norme dans les ordinateurs grand public et les logiciels et systèmes d'exploitation se sont adaptés.
===Les coprocesseurs===
Quelques ordinateurs assez anciens disposaient de '''coprocesseurs''', des processeurs qui complémentaient un processeur principal. Les ordinateurs de ce type avaient un processeur principal, le '''CPU''', qui était secondé par un ou plusieurs coprocesseurs. Sauf exception, le CPU et le coprocesseur exécutent des programmes différents, ils travaillent en parallèle.
Les coprocesseurs les plus connus sont les '''coprocesseurs pour le rendu 2D/3D'''. Ils ont eu leur heure de gloire sur les anciennes consoles de jeux vidéo, comme Super Nintendo, la Playstation et autres consoles de cette génération ou antérieure. Ils s'occupaient respectivement de calculer les graphismes des jeux vidéos. De nos jours, ils ont été remplacés par des cartes graphiques, ou des ''Graphic Processing Units'', qui ne sont pas considérées comme des coprocesseurs.
Les '''coprocesseurs sonores''' sont une sorte d'ancêtre des cartes son, utilisés sur les anciennes consoles de jeux vidéo, comme La Nintendo 64, la Playstation et autres consoles antérieures. Ils s'occupaient respectivement de calculer tout ce qui a trait au son. Pour donner un exemple, on peut citer la console Neo-géo, qui disposait de deux processeurs travaillant en parallèle : un processeur principal, et un coprocesseur sonore. Le processeur principal était un Motorola 68000, alors que le coprocesseur sonore était un processeur Z80.
L'accès aux périphériques est quelque chose sur lequel nous passerons plusieurs chapitres dans ce cours. Mais sachez que l'accès aux périphériques peut demander pas mal de puissance de calculs. Le CPU principal peut faire ce genre de calculs par lui-même, mais il n'est pas rare qu'un '''coprocesseur d'IO''' soit dédié à l'accès aux périphériques. Un exemple assez récent est celui de la console de jeu Nintendo 3DS. Elle disposait d'un processeur principal de type ARM9, d'un coprocesseur pour les divisions qu'on abordera plus bas, et d'un second processeur ARM7. L'ARM 7 était utilisé comme coprocesseur d'I/O, ainsi que pour l'émulation de la console GBA.
[[File:Asmp 2.gif|centre|vignette|upright=2|Co-processeur pour l'accès aux entrées-sorties.]]
Les '''coprocesseurs arithmétiques''' sont un peu à part des autres. Ils sont spécialisés dans les calculs en virgule flottante. Ils étaient utilisés à une époque où les CPU ne géraient que des calculs entiers (en binaire ou en BCD). Un exemple est le coprocesseur flottant x87, complémentaire des premiers processeurs Intel x86. Il y a eu la même chose sur les processeurs Motorola 68000, avec deux coprocesseurs flottants appelés les Motorola 68881 et les Motorola 68882.
Un exemple récent de coprocesseur est celui utilisé sur la console de jeu Nintendo DS. La console utilisait deux processeurs, un ARM9 et un ARM7, qui ne pouvaient pas faire de division entière. Il s'agit pourtant d'opérations importantes dans le cas du rendu 3D, ce qui fait que les concepteurs de la console ont rajouté un coprocesseur spécialisé dans les divisions entières et les racines carrées. Le coprocesseur était adressable directement par le processeur, comme peuvent l'être la RAM ou les périphériques.
==Les entrées-sorties==
Tous les circuits vus précédemment traitent des données codées en binaire. Ceci dit, les données ne sortent pas de n'importe où : l'ordinateur contient des composants électroniques qui traduisent des informations venant de l’extérieur en nombres. Ces composants sont ce qu'on appelle des '''entrées'''. Par exemple, le clavier est une entrée : l'électronique du clavier attribue un nombre entier (''scancode'') à une touche, nombre qui sera communiqué à l’ordinateur lors de l'appui d'une touche. Pareil pour la souris : quand vous bougez la souris, celle-ci envoie des informations sur la position ou le mouvement du curseur, informations qui sont codées sous la forme de nombres. La carte son évoquée il y a quelques chapitres est bien sûr une entrée : elle est capable d'enregistrer un son, et de le restituer sous la forme de nombres.
S’il y a des entrées, on trouve aussi des '''sorties''', des composants électroniques qui transforment des nombres présents dans l'ordinateur en quelque chose d'utile. Ces sorties effectuent la traduction inverse de celle faite par les entrées : si les entrées convertissent une information en nombre, les sorties font l'inverse : là où les entrées encodent, les sorties décodent. Par exemple, un écran LCD est un circuit de sortie : il reçoit des informations, et les transforme en image affichée à l'écran. Même chose pour une imprimante : elle reçoit des documents texte encodés sous forme de nombres, et permet de les imprimer sur du papier. Et la carte son est aussi une sortie, vu qu'elle transforme les sons d'un fichier audio en tensions destinées à un haut-parleur : c'est à la fois une entrée, et une sortie.
Les '''entrées-sorties''' incluent toutes les entrées et sorties, et même certains composants qui sont les deux à la fois. Il s'agit d'un terme générique, qui regroupe des composants forts différents. Dans ce qui va suivre, nous allons parfois parler de périphériques au lieu d'entrées-sorties, mais les deux termes ne sont pas équivalents. Dans le détail, les entrées-sorties regroupent :
* Les '''périphériques''' sont les composants connectés sur l'unité centrale. Exemple : les claviers, souris, webcam, imprimantes, écrans, clés USB, disques durs externes, la Box internet, etc.
* Les '''cartes d'extension''', qui se connectent sur la carte mère via un connecteur, comme les cartes son ou les cartes graphiques.
* D'autres composants sont soudés à la carte mère mais sont techniquement des entrées-sorties : les cartes sons soudées sur les cartes mères actuelles, par exemple.
===L'interface avec le reste de l'ordinateur===
Les entrées-sorties sont très diverses, fonctionnent très différemment les unes des autres. Mais du point de vue du reste de l'ordinateur, les choses sont relativement standardisées. Du point de vue du processeur, les entrées-sorties sont juste des paquets de registres ! Tous les périphériques, toutes les entrées-sorties contiennent des '''registres d’interfaçage''', qui permettent de faire l'intermédiaire entre l'entrée/sortie et le reste de l'ordinateur. L'entrée/sortie est conçu pour réagir automatiquement quand on écrit dans ces registres.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage sont assez variés. Les plus évidents sont les '''registres de données''', qui permettent l'échange de données entre le processeur et les périphériques. Pour échanger des données avec l'entrée/sortie, le processeur a juste à lire ou écrire dans ces registres de données. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données. Si le processeur veut envoyer une donnée à une entrée/sortie, il a juste à écrire dans ces registres. Inversement, s'il veut lire une donnée, il a juste à lire le registre adéquat.
Mais le processeur ne fait pas que transmettre des données à l'entrée/sortie. Le processeur lui envoie aussi des « commandes », des valeurs numériques auxquelles l'entrée/sortie répond en effectuant un ensemble d'actions préprogrammées. En clair, ce sont l'équivalent des instructions du processeur, mais pour l'entrée/sortie. Par exemple, les commandes envoyées à une carte graphique peuvent être : affiche l'image présente à cette adresse mémoire, calcule le rendu 3D à partir des données présentes dans ta mémoire, etc. Pour recevoir les commandes, l'entrée/sortie contient des ''registres de commande'' qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande à l'entrée/sortie, il écrit la commande en question dans ce ou ces registres.
Enfin, beaucoup d'entrée/sortie ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état de l'entrée/sortie. Ils servent notamment à indiquer au processeur que l'entrée/sortie est disponible, qu'il est en train d’exécuter une commande, qu'il est occupé, qu'il y a un problème, qu'il y a une erreur de configuration, etc.
===Les adresses des registres d’interfaçage===
Les registres des périphériques sont identifiés par des adresses mémoires. Et les adresses sont conçues de façon à ce que les adresses des différentes entrées/sorties ne se marchent pas sur les pieds. Chaque entrée/sortie, chaque registre, chaque contrôleur a sa propre adresse. D'ordinaire, certains bits de l'adresse indiquent quel est le destinataire. Certains indiquent quel est l'entrée/sortie voulue, les restants indiquant le registre de destination.
Il existe deux organisations possibles pour les adresses des registres d’interfaçages. La première possibilité est de séparer les adresses pour les registres d’interfaçage et les adresses pour la mémoire. Le processeur doit avoir des instructions séparées pour gérer les périphériques et adresser la mémoire. Il a des instructions de lecture/écriture pour lire/écrire en mémoire, et d'autres pour lire/écrire les registres d’interfaçage. Sans cela, le processeur ne saurait pas si une adresse est destinée à un périphérique ou à la mémoire.
[[File:Espaces d'adressages séparés entre mémoire et périphérique.png|centre|vignette|upright=2.5|Espaces d'adressages séparés entre mémoire et périphérique]]
L'autre méthode mélange les adresses mémoire et des entrées-sorties. Si on prend par exemple un processeur de 16 bits, où les adresses font 16 bits, alors les 65536 adresses possibles seront découpées en deux portions : une partie ira adresser la RAM/ROM, l'autre les périphériques. On parle alors d''''entrées-sorties mappées en mémoire'''. L'avantage est que le processeur n'a pas besoin d'avoir des instructions séparées pour les deux.
[[File:IO mappées en mémoire.png|centre|vignette|upright=2.0|IO mappées en mémoire]]
Pour résumer, communiquer avec une entrée/sortie est similaire à ce qu'on a avec les mémoires. Il suffit de lire ou écrire dans des registres d’interfaçage, qui ont chacun une adresse mémoire. Le problème est que le système d'exploitation ne connaît pas toujours le fonctionnement d'une entrée/sortie : il faut installer un programme qui va s'exécuter quand on souhaite communiquer avec l'entrée/sortie, et qui s'occupera de tout ce qui est nécessaire pour le transfert des données, l'adressage du périphérique, etc. Ce petit programme est appelé un driver ou '''pilote de périphérique'''. La « programmation » périphérique est très simple : il suffit de savoir quoi mettre dans les registres, et c'est le pilote qui s'en charge.
==Les architectures Harvard et Von Neumann==
Après avoir vu le processeur, les mémoires et les entrées-sorties, voyons voir comment le tout est interconnecté. Tous les ordinateurs ne sont pas organisés de la même manière, pour ce qui est de leurs bus. Mais pour comprendre pourquoi, nous devons regarder qui communique avec qui, dans un ordinateur. Pour rappel, les données sont placées en mémoire RAM, alors que les instructions sont placées en mémoire ROM. Le processeur lit des instructions dans la mémoire ROM, il lit et écrit dans la mémoire RAM, et accède aux registres d’interfaçage des entrées-sorties. Il y a donc besoins de trois interconnexions : CPU-ROM, CPU-RAM et CPU-IO.
[[File:Réseau d'interconnexion avec un processeur au centre.png|centre|vignette|upright=2|Réseau d'interconnexion avec un processeur au centre]]
Il parait intéressant d'utiliser trois interconnexions, au minimum CPU-ROM, CPU-RAM et CPU-IO. Néanmoins, faire ainsi a de nombreux désavantages. Déjà, il faut pouvoir brancher tout ça sur le processeur. Et celui-ci n'a pas forcément assez de broches pour. Aussi, il est parfois préférable de mutualiser des bus, à savoir de connecter plusieurs composants sur un même bus. Par exemple, on peut mutualiser le bus pour la mémoire RAM et pour la mémoire ROM. Il faut dire que les deux bus sont des bus mémoire, avec un bus d'adresse, un bus de données, et surtout : des bus de commande similaires. Les mutualiser est alors très simple, et permet d'économiser pas mal de broches.
[[File:Réseau d'interconnexion avec un processeur au centre et une architecture Harvard.png|centre|vignette|upright=2|Réseau d'interconnexion avec un processeur au centre et une architecture Harvard]]
Cette mutualisation nous amène naturellement à parler de la distinction entre les architectures Harvard d'un côté et les architectures Von Neumann de l'autre. Elle est très liée au fait d'utiliser soit un bus mémoire unique, soit des bus séparés pour la ROM et la RAM. Voyons cela en détail.
===Les architectures Harvard et Von Neumann : des bus séparés ou unifiés===
Avec l''''architecture Harvard''', la mémoire ROM et la mémoire RAM sont reliées au processeur par deux bus séparés. Il y a un bus RAM pour la mémoire RAM, un bus ROM pour la mémoire ROM. L'avantage de cette architecture est qu'elle permet de charger une instruction et une donnée simultanément : une instruction chargée sur le bus relié à la mémoire programme, et une donnée chargée sur le bus relié à la mémoire de données. Et cela simplifie fortement la conception du processeur.
[[File:Harvard Architecture.png|centre|vignette|upright=2|Architecture Harvard, avec une ROM et une RAM séparées.]]
Avec l''''architecture Von Neumann''', mémoire ROM et mémoire RAM sont reliées au processeur par un bus unique. Le bus unique qui relie processeur, RAM et ROM, s'appelle le '''bus mémoire'''. Un défaut de ces architecture est qu'elles ne peuvent pas charger une instruction et une donnée en même temps. Et cela pose quelques problèmes pour la conception du processeur. Par contre, nous verrons dans ce qui suit qu'utiliser un bus mémoire partagé est bien plus flexible et permet des choses que les architectures Harvard ne peuvent pas faire.
[[File:Architecture Von Neumann, avec deux bus séparés.png|centre|vignette|upright=2|Architecture Von Neumann, avec deux bus séparés.]]
===Les architectures Harvard et Von Neumann : des espaces d'adressage séparés ou unifiés===
La distinction précédente se base sur les connexions entre RAM, ROM et processeur. Mais il existe une autre distinction, très liée, qui est souvent utilisée comme seconde définition des architectures Harvard/Von Neumann. Elle est liée aux adresses mémoire que le processeur peut gérer. Prenons un processeur 16 bits, par exemple, qui gère naturellement des adresses de 16 bits. Il peut gérer 2^16 adresses, soit 64 kibioctets de mémoire. L'ensemble de ces adresses est appelé un '''espace d'adressage'''. Mais comment cet espace d'adressage est utilisé pour adresser une RAM et une ROM ?
Sur les architectures Harvard, le processeur voit deux mémoires séparées avec leur lot d'adresses distinctes. Une même adresse peut donc correspondre soit à la mémoire ROM, soit à la mémoire RAM, suivant le bus utilisé. L'espace d'adressage est donc doublé, dupliqué, avec un pour la ROM, un autre pour la RAM. Rien d'étonnant à cela : il y a deux bus d'adresses, chacun correspondant à un espace d'adressage.
[[File:Vision de la mémoire par un processeur sur une architecture Harvard.png|centre|vignette|upright=2|Vision de la mémoire par un processeur sur une architecture Harvard.]]
Avec l'architecture Von Neumann, la RAM et la ROM doivent se partager les adresses mémoires disponibles. Il n'y a qu'un seul espace d'adressage qui est coupé en deux, avec une partie pour la ROM et une autre pour la RAM. Une adresse correspond soit à la mémoire RAM, soit à la mémoire ROM, mais pas aux deux. Typiquement, la mémoire ROM occupe une partie des adresses, la mémoire RAM utilise le reste. La répartition des adresses est réalisée par les circuits de décodage d'adresse mentionnés plus haut.
[[File:Vision de la mémoire par un processeur sur une architecture Von Neumann.png|centre|vignette|upright=2|Vision de la mémoire par un processeur sur une architecture Von Neumann.]]
Les '''architectures Harvard modifiées''' sont des intermédiaires entre architectures Harvard et architectures Von Neumann, bien qu'elles penchent bien plus du côté des architectures Harvard. Précisons que la terminologie n'est pas claire, beaucoup d'auteurs mettent des définitions différentes derrière ces deux termes. Mais dans ce cours, nous utiliserons une définition très stricte de ce qu'est une architecture Harvard modifiée.
Une architecture Harvard modifiée est une architecture Harvard, où le processeur peut lire des données constantes depuis la mémoire ROM. Nous avions vu plus haut que les mémoires ROM peuvent mémoriser, en plus d'un programme exécutable, des données constantes, qui ne varient pas. Les architectures Harvard pures ne permettent pas de lire des données de ce genre depuis la mémoire ROM, alors que les architectures Harvard modifiées le permettent.
Une architecture Harvard modifiée dispose d'une instruction pour lire les données en mémoire RWM, et d'une instruction pour lire des données en mémoire ROM. Il y a donc deux versions de l'instruction LOAD, qui copient la donnée dans un registre général, mais dont la source de la donnée est différente. Une autre possibilité, plus rare, est que une instruction de copie, qui copie une constante depuis la mémoire ROM vers la mémoire RAM. Le cas le plus commun est l'utilisation de deux instructions LOAD séparées.
[[File:Espaces d'adressage sur une archi harvard modifiée.png|centre|vignette|upright=2.5|Espaces d'adressage sur une archi harvard modifiée]]
Ceci étant dit, revenons à la distinction entre architecture Harvard et Von Neumann. Il faut noter que la RAM et la ROM n'ont pas forcément la même taille. Et ce que ce soit sur une architecture Harvard que sur une architecture Von Neumann, mais c'est plus facile à expliquer sur une architecture Harvard.
On peut par exemple imaginer une architecture Harvard qui utilise des adresses de 16 bits pour la ROM, et seulement 8 bits pour la RAM. Le résultat est qu'il peut adresser 64 kibioctets de ROM, mais seulement 256 octets de RAM. Les deux bus d'adresse sont alors de taille différente, l'un faisant 8 bits, l'autre 16. Quelques processeurs 8 bits étaient dans ce cas, comme on le verra dans le chapitre sur les CPU 8bits. Mais d'autres processeurs utilisent des valeurs différentes, avec par exemple des adresses de 16 bits pour la RAM, mais de 20 bits pour la ROM, ou inversement.
Sur une architecture Von Neumann, tout dépend de comment les adresses sont réparties. La solution la plus simple découpe l'espace d'adressage en deux parties égales, avec la RAM qui est dans la moitié basse (qui part de l'adresse 0 jusqu'à l'adresse au milieu), alors que la ROM est dans la moitié haute (entre l'adresse du milieu et l'adresse maximale). Mais ce n'est pas la seule possibilité, la limite entre RAM et ROM peut être mise n'importe où. Prenons par exemple un processeur 32 bits, capable de gérer 4 milliards d'adresse. Il est parfaitement possible de réserver 128 mébioctets de poids fort à la mémoire ROM, et de laisser le reste à la mémoire RAM.
===Le décodage d'adresse sur les architectures Von Neumann===
Pour résumer, les architectures Harvard et Von Neumann se distinguent sur deux points :
* L'accès à la RAM et à la ROM se font par des bus séparés sur l'architecture Harvard, sur le même bus avec l'architecture Von Neumann.
* Les adresses pour la mémoire ROM et la mémoire RAM sont séparées sur les architectures Harvard, partagées sur l’architecture Von Neumann.
Les architectures Von Neumann utilisent donc un seul bus pour connecter la RAM et la ROM au processeur. Mais cela ne parait pas intuitif : comment deux composants peuvent se connecter aux mêmes fils ? Parce que c'est ce qu'implique le fait de partager un bus. Si je prends une mémoire RAM et une mémoire ROM, toutes deux de 8 bits, elles seront connectées à un bus mémoire de 8 bits. Intuitivement, on se dit qu'il y aura des conflits, du genre : la RAM et la ROM vont accéder au bus en même temps, comment savoir si une adresse est destinée à la RAM ou la ROM, etc ?
Tous ces problèmes sont résolus avec une solution très simple : à chaque instant, seule une mémoire est connectée au bus. L'idée est que les mémoires sont connectées ou déconnectées du bus selon les besoins. Si le processeur veut envoyer lire une donnée en mémoire RAM, il déconnecte la mémoire ROM du bus. Et inversement, s'il veut lire une instruction, il déconnecte la RAM et connecte la ROM.
Pour cela, les mémoires RAM et ROM possèdent une entrée ''Chip Select'' ou ''Output Enable'', qui agit comme une sorte de bouton ON/OFF. Lorsqu'on met un 1 sur cette entrée, la mémoire se connectera au bus. Ses entrées et sorties fonctionneront normalement, elle pourra recevoir des adresses, envoyer ou recevoir des données, tout sera normal. Par contre, si on met un 0 sur cette entrée, la mémoire se "désactive", ses entrée-sorties ne répondent plus aux sollicitations extérieures. Pire que ça : elles sont électriquement déconnectées.
Au total, tout cela demande de gérer deux bit ''Chip Select''/''Output Enable'' : un pour la RAM, un pour la ROM. Et ces deux bits sont configurés pour chaque accès mémoire, pour chaque lecture ou écriture. Pour cela, un circuit de '''décodage d'adresse''' prend en entrée l'adresse mémoire à lire/écrire, et active/désactive les mémoires RAM/ROM selon les besoins. Il prend l'adresse et configure les bits ''Chip Select''/''Output Enable''.
[[File:Décodage d'adresse sur une architecture Von Neumann.png|centre|vignette|upright=2|Décodage d'adresse sur une architecture Von Neumann.]]
L'implémentation la plus simple réserve la moitié des adresses pour la RAM, l'autre moitié pour la ROM. Typiquement, la ROM prend la moitié basse, la RAM la moitié haute. Dans ce cas, activer/désactiver la RAM et la ROM se fait avec seulement le bit de poids fort de l'adresse. Si le bit de poids fort est à 1, alors on accède à la RAM et la ROM doit être désactivée. Mais si ce bit est à 0, alors on accède à la moitié basse et il faut désactiver la RAM.
Une remarque intéressante : le fait de séparer la mémoire en deux parts égales permet de simuler une architecture Harvard à partir d'une architecture Von Neumann. Par exemple, le tout premier processeur d'Intel, le 4004, était l'un de ceux là. La RAM et la ROM sont reliés au même bus, et il y a donc un unique espace d'adressage, qui est séparé en deux parties égales. Le truc est que le processeur traite les deux parties égales comme deux espaces d'adressage séparés. Le processeur se débrouille pour cacher le fait qu'il y a un espace d'adressage unique coupé en deux, ce qui fait que les programmeurs voient bien deux espaces d'adressages distincts.
[[File:Décodage d'adresse sur une architecture Von Neumann basique.png|centre|vignette|upright=2|Décodage d'adresse sur une architecture Von Neumann basique.]]
Pour résumer, quand une adresse est envoyée sur le bus, les deux mémoires vont la recevoir mais une seule va répondre et se connecter au bus. Le décodage d'adresse garantit que seule la mémoire adéquate réponde à un accès mémoire. Le décodage d'adresse est réalisé par la carte mère, par un composant dédié.
Le mécanisme peut être utilisé pour combiner plusieurs RAM en une seule, idem avec les ROM. Pour comprendre l'idée, je vais prendre l'exemple de l'IBM PC, un des tout premier PC existant. Nous étudierons ce PC dans une section dédiée, à la fin du chapitre, aussi je vais passer rapidement dessus. Tout ce que je vais faire est vous présenter la carte mère du PC, et vous demander de faire est de compter les mémoires ROM et mémoires RAM sur la carte mère :
[[File:IBM 5150 Motherboard.svg|centre|vignette|upright=3|Carte mère de l'IBM 5150, un modèle de l'IBM PC.]]
Si vous remarquerez qu'il y a 5 mémoires ROM et 8 à 32 mémoires RAM. Le fait est que le processeur voit les différentes mémoires ROM comme une seule mémoire ROM. Idem avec les mémoires RAM : elle font chacune 2 kibioctets, et l'ensemble est vu par le processeur comme une seule RAM de 16 à 64 kibioctets. Et cela grâce aux circuits de décodage d'adresse, qui sont situés en haut à droite de la carte mère.
Pour comprendre l'idée, prenons l'exemple d'un processeur 16 bits, capable de gérer 64 kibioctets de mémoire. L'espace d'adressage est découpé en quatre portions, de 16 kibioctets chacune. Une portion est réservée à une ROM de 16 kibioctet, les autres sont chacune réservée à une RAM de 16 kibioctet. Le décodage d'adresse sélectionne alors la mémoire adéquate en utilisant les deux bits de poids fort de l'adresse.
* S'ils valent 00, alors c'est la mémoire ROM qui est activée, connectée au bus.
* S'ils valent 01, alors c'est la première mémoire RAM qui est connectée au bus.
* S'ils valent 10, alors c'est la seconde mémoire RAM qui est connectée au bus.
* S'ils valent 11, alors c'est la troisième mémoire RAM qui est connectée au bus.
[[File:Décodage d'adresse sur une architecture Von Neumann, utilisant plusieurs RAM et une ROM.png|centre|vignette|upright=3|Décodage d'adresse sur une architecture Von Neumann, utilisant plusieurs RAM et une ROM]]
===L'impact sur la conception du processeur===
Plus haut, j'ai parlé d'un des avantages des architectures Harvard : elles peuvent lire une instruction en même temps qu'elles accèdent à une donnée. La donnée est lue/écrite en RAM, alors que l'instruction est lue en ROM. Et cela permet de simplifier l'intérieur du processeur. Pas de beaucoup, mais c'est déjà ça de pris. Voyons maintenant comment cela impacte l'intérieur du processeur. Tout ce dont vous avez à vous rappeler est la séparation entre chemin de données et unité de contrôle, et que les registres généraux sont dans le premier, le ''program counter'' dans la seconde.
Avec une architecture Harvard, les instructions et les données passent par des bus différent : bus ROM pour les instructions, bus RAM pour les données. L'intuition nous dit que le bus pour la mémoire ROM est connecté à l'unité de contrôle, alors que le bus pour la RAM est connecté au chemin de données. Et dans les grandes lignes, c'est vrai. La logique est imparable pour ce qui est des bus de données. Mais il y a une petite subtilité pour les bus d'adresse.
Pour comprendre comment le processeur exploite ces deux bus, voyons ce qui transite dessus. Pour la mémoire ROM, elle reçoit l'adresse de l'instruction à lire, elle renvoie l'instruction adéquate. Pour cela, le ''program counter'' est envoyé sur le bus d'adresse, l'instruction sur le bus de données. Pour la mémoire RAM, elle échange des données avec les registres généraux, les registres pour les données. Les adresses utilisées pour la RAM viennent elles soit du chemin de données, soit de l'unité de contrôle, tout dépend du mode d'adressage. Mais le ''program counter'' n'est pas impliqué.
[[File:Architecture Harvard - échanges de données.png|centre|vignette|upright=2|Architecture Harvard - échanges de données]]
Les architectures Harvard modifiées doivent cependant rajouter une connexion entre le bus ROM et les registres généraux. C'est nécessaire pour charger une donnée constante depuis la mémoire ROM. Rappelons que la donnée constante est copiée dans un registre général, donc dans le chemin de données.
[[File:Architecture Harvard modifiée - implémentation du processeur.png|centre|vignette|upright=2|Architecture Harvard modifiée - implémentation du processeur]]
Avec les architectures Von Neumann, il y a un seul bus qui est relié à la fois au chemin de données et à l'unité de contrôle. Si le processeur lit une instruction, le bus doit être relié à l'unité de contrôle. Par contre, s'il accède à une donnée, il doit être relié au chemin de données (le bus d'adresse peut éventuellement être connecté au séquenceur, si celui-ci fournit l'adresse à lire). Il faut donc utiliser un paquet de multiplexeurs et de démultiplexeurs pour faire la connexion au bon endroit.
[[File:Architecture Von Neumann - implémentation du processeur.png|centre|vignette|upright=2|Architecture Von Neumann - implémentation du processeur]]
Une instruction se fait en deux temps : on charge l'instruction depuis la mémoire ROM, puis on l'exécute. Avec une architecture Harvard, tout cela se fait en un seul cycle d'horloge, vu que charger la ROM et accéder aux données peut se faire en même temps. Pas avec les architectures Von Neumann, qui doivent libérer le bus mémoire après avoir chargé une instruction. Elles n'ont pas le choix : elles chargent l'instruction lors d'un premier cycle d'horloge, puis l'exécutent lors du second.
Pour cela, ils incorporent un registre appelé le '''registre d'instruction''', qui mémorise l'instruction chargée. L'instruction est copiée dans ce registre lors du premier cycle, puis est utilisée lors du second cycle. Le registre permet de ne pas oublier l’instruction entre les deux cycles. Le registre d'instruction est obligatoire sur les architectures Von Neumann. En comparaison, il est facultatif sur les architectures Harvard. Elles peuvent en avoir un, pour des raisons techniques, mais ce n'est pas obligatoire.
[[File:Registre d'instruction.png|centre|vignette|upright=2|Registre d'instruction.]]
===Les architectures Von Neumann sont plus flexibles===
Sur les architectures Harvard, le processeur sait faire la distinction entre programme et données. Les données sont stockées dans la mémoire RAM, le programme est stocké dans la mémoire ROM. Les deux sont séparés, accédés par le processeur sur des bus séparés, et c'est ce qui permet de faire la différence entre les deux. Il est impossible que le processeur exécute des données ou modifie le programme. Du moins, tant que la mémoire qui stocke le programme est bien une ROM.
Par contre, sur les architectures Von Neumann, il est impossible de distinguer programme et données, sauf en ajoutant des techniques de protection mémoire avancées. La raison est qu'il est impossible de faire la différence entre donnée et instruction, vu que rien ne ressemble plus à une suite de bits qu'une autre suite de bits. Et c'est à l'origine d'un des avantages majeur de l'architecture Von Neumann : il est possible que des programmes soient copiés dans la mémoire RWM et exécutés dans celle-ci.
Un cas d'utilisation familier est celui de votre ordinateur personnel. Le système d'exploitation et les autres logiciels sont copiés en mémoire RAM à chaque fois que vous les lancez. Mais cet exemple implique un disque dur, ce qui rend les choses plus compliquées que prévu. Un autre exemple serait la compilation de code à la volée, mais il ne sera pas très parlant.
Un exemple plus adapté serait celui où la ROM mémorise un programme compressée dans la mémoire ROM, qui est décompressé pour être exécuté en mémoire RAM. Le programme de décompression est stocké en mémoire ROM et est exécuté au lancement de l’ordinateur. Cette méthode permet d'utiliser une mémoire ROM très petite et très lente, tout en ayant un programme rapide (si la mémoire RWM est rapide).
Il est aussi possible de créer des programmes qui modifient leurs propres instructions : cela s'appelle du '''code auto-modifiant'''. Ce genre de choses servait autrefois sur des ordinateurs rudimentaires, au tout début de l'informatique. À l'époque, les adresses à lire/écrire devaient être écrites en dur dans le programme, dans les instructions exécutées. Pour gérer certaines fonctionnalités des langages de programmation qui ont besoin d'adresses modifiables, comme les tableaux, on devait corriger les adresses au besoin avec du code auto-modifiant. De nos jours, le code automodifiant est utilisée occasionnellement pour rendre un programme indétectable dans la mémoire (les virus informatiques utilisent beaucoup ce genre de procédés).
L'impossibilité de séparer données et instructions est à l'origine de problèmes assez fâcheux. Il est parfaitement possible que le processeur charge et exécute des données, qu'il prend par erreur pour des instructions. C'est le cas quand des pirates informatiques arrivent à exploiter des bugs. Il arrive que des pirates informatiques vous fournissent des données corrompues, qui contiennent un virus ou un programme malveillant est caché dans les données. Les bugs en question permettent d'exécuter ces données, donc virus. Pour éviter cela, le système d'exploitation peut marquer certaines zones de la mémoire comme non-exécutable, c’est-à-dire que le système d'exploitation interdit d’exécution de quoi que ce soit qui est dans cette zone. Mais ce n'est pas parfait.
Toujours est-il que tout cela est impossible sur les architectures Harvard. Et ce serait très limitant. Imaginez : pas possible de lancer un programme depuis le disque dur ou une clé USB, le programme doit impérativement être dans une mémoire ROM, pas de compilation à la volée, etc. Que des techniques très utilisées dans l'informatique moderne. Malgré ses défauts, les architectures Von Neumann ne sont pas les plus utilisées pour rien. Les architectures Harvard sont concrètement utilisées uniquement dans l'informatique embarquée, sur des microcontrôleurs très spécifiques.
==Le bus de communication avec les entrées-sorties==
Le processeur, la mémoire et les entrées-sorties sont connectées par un ou plusieurs '''bus de communication'''. Ce bus n'est rien d'autre qu'un ensemble de fils électriques sur lesquels on envoie des zéros ou des uns. Pour communiquer avec la mémoire, il y a trois prérequis qu'un bus doit respecter : pouvoir sélectionner la case mémoire (ou l'entrée-sortie) dont on a besoin, préciser à la mémoire s'il s'agit d'une lecture ou d'une écriture, et enfin pouvoir transférer la donnée. Pour cela, on doit donc avoir trois bus spécialisés, bien distincts, qu'on nommera le bus de commande, le bus d'adresse, et le bus de donnée.
* Le '''bus de données''', sur lequel s'échangent les données entre les composants.
* Le '''bus de commande''' pour configurer la mémoire et les entrées-sorties.
* Le '''bus d'adresse''', facultatif, permet de préciser quelle adresse mémoire il faut lire/écrire.
Chaque composant possède des entrées séparées pour le bus d'adresse, le bus de commande et le bus de données. Par exemple, une mémoire RAM possédera des entrées sur lesquelles brancher le bus d'adresse, d'autres sur lesquelles brancher le bus de commande, et des broches d'entrée-sortie pour le bus de données. Précisons cependant que le bus de commande n'est pas exactement le même entre des mémoires RAM/ROM et des entrées-sorties.
[[File:Bus general schematic.svg|centre|vignette|upright=2|Contenu d'un bus, généralités.]]
===Le réseau d'interconnexion : généralités===
Reprenons où nous nous étions arrêté. Avant de voir les architectures Harvard et Von Neumann, nous avions dit que le processeur, les mémoires et les entrées-sorties sont reliées entre eux par un réseau d'interconnexion. Nous venons de voir qu'il est possible de mutualiser certains bus, notamment celui de la mémoire RAM et celui de la mémoire ROM. Mais il est possible de faire la même chose pour les entrées-sorties. Là encore, il est possible de regrouper le bus mémoire avec les bus pour les entrées-sorties. Voyons ce que cela implique.
{|
|[[File:Réseau d'interconnexion avec un processeur au centre.png|centre|vignette|upright=2|Réseau d'interconnexion avec une architecture Harvard.]]
|[[File:Réseau d'interconnexion avec un processeur au centre et une architecture Harvard.png|centre|vignette|upright=2|Interconnexions d'une architecture Von Neumann.]]
|}
Avant de poursuivre, nous devons préciser quelque chose d'important. Sur les ordinateurs modernes, les entrées-sorties peuvent accéder à la mémoire RAM. Les ordinateurs modernes intègrent des techniques de '''''Direct Memory Access''''' (DMA) qui permettent aux entrées-sorties de lire ou d'écrire en mémoire RAM. Les transferts DMA se font sans intervention du processeur. Ils permettent de copier un bloc de plusieurs octets, dans deux sens : de la mémoire RAM vers une entrée-sortie, ou inversement. Le DMA demande d'ajouter un circuit dédié sur la carte mère : le contrôleur DMA. Il effectue la copie d'un paquet d'octets de la RAM vers l'entrée-sortie ou dans l'autre sens.
[[File:Réseau d'interconnexion avec un processeur au centre, et direct memory access.png|centre|vignette|upright=2|Réseau d'interconnexion avec un processeur au centre, et direct memory access]]
===Les bus systèmes===
La première solution utilise un bus unique, celui-ci est appelé le '''bus système''', aussi appelé ''backplane bus''. Le bus système est connecté à la mémoire RAM, la mémoire ROM, au processeur, et aux entrées-sorties. Tous les composants présents dans l'ordinateur sont connectés à ce bus, sans exception. De tels bus avaient pour avantage la simplicité. Le processeur n'est connecté qu'à un seul bus, ce qui utilise peu de broches et économise des fils. La mutualisation des bus est totale, le câblage est plus simple, la fabrication aussi.
[[File:Architecture minimale d'un ordinateur.png|centre|vignette|upright=2|Architecture minimale d'un ordinateur.]]
Un bus système contient un bus d'adresse, de données et de commande. Un bus système se marie bien avec des entrées-sorties mappées en mémoire. La conséquence est que le bus d'adresse ne sert pas que pour l'accès à la mémoire RAM/ROM, mais aussi pour l'accès aux entrées-sorties. Il y a moyen d'implémenter un système d'adresse séparés avec, mais c'est pas l'idéal.
[[File:Architecture Von Neumann avec les bus.png|centre|vignette|upright=2|Architecture Von Neumann avec les bus.]]
Un bus système n'a pas de limitations quant aux échanges de données. Le processeur peut communiquer directement avec les mémoires et les entrées-sorties, les entrées-sorties peuvent communiquer avec la mémoire RAM, etc. Notamment, un bus système peut implémenter le ''Direct Memory Access''. Il suffit juste de connecter un contrôleur DMA sur le bus système. Le contrôleur DMA est considéré comme une entrée-sortie, ses registres sont mappés en mémoire et sont donc accessibles directement par le processeur.
[[File:Bus système avec controleur DMA.png|centre|vignette|upright=2|Bus système avec contrôleur DMA.]]
Si on suit la définition à la lettre, un bus système est systématiquement une architecture Von Neumann, vu que la mémoire ROM et la mémoire RAM sont reliées sur le bus système. La conséquence est que les circuits de décodage d'adresse sont présents. Ils sont toujours sur la carte mère, et sont plus ou moins à côté du bus système. Cependant, le décodage d'adresse est parfois étendu pour tenir compte des entrées-sorties.
Les entrées-sorties soudées sur la carte mère ont elles aussi des entrées ''Chip Select'' ou quelque chose de similaire. Le décodage d'adresse peut alors les activer ou les désactiver suivant l'adresse envoyée sur le bus d'adresse. C'est ce qui arrive quand le processeur écrit dans un registre d’interfaçage : il envoie l'adresse de ce registre sur le bus d'adresse, le circuit de décodage d'adresse active seulement l'entrée-sortie associée. Il faut noter que ce n'est pas systématique, il existe des techniques pour se passer de décodage d'adresse. Mais nous en reparlerons dans le chapitre sur les bus de communication.
[[File:Chipselectfr.png|centre|vignette|upright=1.5|Exemple détaillé.]]
Les bus systèmes sont certes très simples, mais ils ont aussi des désavantages. Par exemple, il faut éviter que le processeur et les entrées-sorties se marchent sur les pieds, ils ne peuvent pas utiliser le bus en même temps. De tels conflits d'accès au bus système sont fréquents et ils réduisent la performance, comme on le verra dans le chapitre sur les bus. De plus, un bus système a le fâcheux désavantage de relier des composants allant à des vitesses très différentes : il arrivait fréquemment qu'un composant rapide doive attendre qu'un composant lent libère le bus. Le processeur était le composant le plus touché par ces temps d'attente.
Elle était utilisée sur les tout premiers ordinateurs, pour sa simplicité. Elle était parfaitement adaptée aux anciens composants, qui allaient tous à la même vitesse. De nos jours, les ordinateurs à haute performance ne l'utilisent plus trop, mais elle est encore utilisée sur certains systèmes embarqués, en informatique industrielle dans des systèmes très peu puissants.
===Les bus d'entrées-sorties===
Les bus systèmes ont de nombreux problèmes, ce qui fait que d'anciens ordinateurs faisaient autrement. À la place d'un bus système unique, ils utilisent un bus séparé pour les mémoires, et un autre séparé pour les entrées-sorties. Le bus spécialisé pour la mémoire est appelé le '''bus mémoire''', l'autre bus est appelé le '''bus d'entrées-sorties'''. Le bus mémoire est généralement relié à la fois à la mémoire RAM et à la mémoire ROM, les exceptions ne sont pas rares, cependant.
[[File:Bus mémoire séparé du bus pour les IO.png|centre|vignette|upright=2|Bus mémoire séparé du bus pour les IO]]
Les bus d'entrée-sorties peuvent être spécialisés et simplifiés. Par exemple, ils peuvent avoir un bus de commande différent de celui de la mémoire, qui utilise nettement moins de fils. Le bus d'adresse peut aussi être réduit, et utiliser des adresses plus courtes que celles du bus mémoire. Les bus de données peuvent aussi être de taille différentes. Il est ainsi possible d'avoir un bus mémoire capable de lire/écrire 64 bits à la fois, alors que la communication avec les entrées-sorties se fait octet par octet ! En général, les bus d'entrée-sortie sont assez petits, ils ont une taille de 8 ou 16 bits, même si le bus mémoire est plus grand. Cela permet de ne pas gaspiller trop de broches. Ajouter un bus d'entrée-sortie n'est donc pas très gourmand en broches et en fils.
: Il est en théorie possible d'avoir une fréquence différente pour les deux bus, avec un bus mémoire ultra-rapide et un bus pour les entrées-sorties est un bus moins rapide. Mais il faut que le processeur soit prévu pour, et c'est très rare.
Niveau performances, le processeur peut théoriquement accéder à la mémoire en attendant qu'une entrée/sortie réponde, mais il faut que le processeur soit prévu pour, et ce n'est pas de la tarte. Par contre, cela implique d'avoir des adresses séparées pour les registres d’interfaçage et la mémoire. En clair : pas d'entrée-sortie mappée en mémoire ! Un autre problème est que les entrées-sorties ne peuvent pas communiquer avec la mémoire directement, elles doivent passer par l'intermédiaire du processeur. En clair : pas de ''Direct Memory Access'' ! Les deux sont des défauts rédhibitoires pour les programmeurs système, notamment pour ceux qui codent les pilotes de périphériques.
Pour résumer, les défauts sont assez problématiques : pas d'entrées-sorties mappées en mémoire, pas de ''Direct Memory Access'', économie de broches limitée. Les deux premiers sont des défauts majeurs, qui font que de tels bus ne sont pas utilisés dans les ordinateurs modernes. À la place, ils utilisent une troisième solution, distincte des bus systèmes et des bus d'entrée-sorties.
===Les bus avec répartiteur===
Il existe une méthode intermédiaire, qui garde deux bus séparés pour la mémoire et les entrées-sorties, mais élimine les problèmes de brochage sur le processeur. L'idée est d'intercaler, entre le processeur et les deux bus, un '''circuit répartiteur'''. Il récupère tous les accès et distribue ceux-ci soit sur le bus mémoire, soit sur le bus des périphériques. Le ou les répartiteurs s'appellent aussi le '''''chipset''''' de la carte mère.
C'était ce qui était fait à l'époque des premiers Pentium. À l'époque, la puce de gestion du bus PCI faisait office de répartiteur. Elle mémorisait des plages mémoires entières, certaines étant attribuées à la RAM, les autres aux périphériques mappés en mémoire. Elles utilisaient ces plages pour faire la répartition.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus]]
Niveau adresses des registres d'interfacage, il est possible d'avoir soit des adresses unifiées avec les adresses mémoire, soit des adresses séparées.
L'usage d'un répartiteur ne pose pas de problèmes particuliers pour implémenter le DMA. La seule contrainte est que le contrôleur DMA soit intégré dans le répartiteur. Les échanges entre IO et mémoire passent par le répartiteur, qui fait le pont entre bus mémoire et bus des IO.
[[File:Implémentation du DMA avec un répartiteur.png|centre|vignette|upright=2|Implémentation du DMA avec un répartiteur]]
==Les microcontrôleurs et ''system on chip''==
Parfois, on décide de regrouper la mémoire, les bus, le CPU et les ports d'entrée-sortie dans un seul circuit intégré, un seul boitier. L'ensemble forme alors ce qu'on appelle un '''''System on Chip''''' (système sur une puce), abrévié en SoC. Le nom est assez explicite : un SoC comprend un système informatique complet sur une seule puce de silicium, microprocesseurs, mémoires et périphériques inclus. Ils incorporent aussi des ''timers'', des compteurs, et d'autres circuits très utiles.
[[File:ARMSoCBlockDiagram.svg|centre|vignette|upright=2|SoC basé sur un processeur ARM, avec des entrées-sorties typiques de celles d'un µ-contrôleur. Le support du bus CAN, d'Ethernet, du bus SPI, d'un circuit de PWM (génération de signaux spécifiques), de convertisseurs analogique-digital et inverse, sont typiques des µ-contrôleurs.]]
Le terme SoC regroupe des circuits imprimés assez variés, aux usages foncièrement différents et à la conception distincte. Les plus simples d’entre eux sont des microcontrôleurs, qui sont utilisés pour des applications à basse performance. Les plus complexes sont utilisés pour des applications qui demandent plus de puissance, que nous appellerons SoC haute performance.
La relation entre SoC et microcontrôleurs est assez compliquée à expliquer, la terminologie n'étant pas clairement établie. Il existe quelques cours/livres qui séparent les deux, d'autres qui pensent que les deux sont très liés. Dans ce cours, nous allons partir du principe que tous les systèmes qui regroupent processeur, mémoire et quelques périphériques/entrées-sorties sont des SoC. En suivant cette définition, les microcontrôleurs sont donc un cas particulier de SoC, .
===Les microcontrôleurs===
Les '''microcontrôleurs''' sont des composants utilisés dans l'embarqué ou d'informatique industrielle. Leur nom trahit leur rôle. Ils sont utilisés pour contrôler de l'électroménager, des chaines de fabrication dans une usine, des applications robotiques, les alarmes domestiques ou ebcore les voitures. De manière générale, on les trouve dans tous les systèmes dits embarqués et/ou temps réel. Ils ont besoin de s'interconnecter à un grand nombre de composants et intègrent pour cela un grand nombre d'entrée-sorties. Les microcontrôleurs sont généralement peu puissants et doivent consommer peu d'énergie/électricité.
Fait amusant, on en trouve dans certains périphériques informatiques. Par exemple, les anciens disques durs intégraient un microcontrôleur qui contrôlait plusieurs moteurs : les moteurs pour faire tourner les plateaux magnétiques et les moteurs pour déplacer les têtes de lecture/écriture. Autre exemple : les claviers d'ordinateurs, qui intègrent un microcontrôleur connecté aux touches. Celui-ci détecte quand les touches sont appuyées et qui communique avec l'ordinateur. Nous détaillerons ces deux exemples dans les chapitres dédiés aux périphériques et aux disques durs, tout deviendra plus clair à ce moment là. La majorité des périphériques ou des composants internes à un ordinateur contiennent des microcontrôleurs.
Un microcontrôleur tend à intégrer des entrées-sorties assez spécifiques, qu'on ne retrouve pas dans les SoC destinés au grand public. Un microcontrôleur est typiquement relié à un paquet de senseurs et son rôle est de commander des moteurs ou d'autres composants. Et les entrées-sorties intégrées sont adaptées à cette tâche. Par exemple, ils tendent à intégrer de nombreux convertisseurs numériques-analogiques pour gérer des senseurs. Ils intègrent aussi des circuits de génération de signaux PWM spécialisés pour commander des moteurs, le processeur peut gérer des calculs trigonométriques (utiles pour commander la rotation d'un moteur), etc.
[[File:C8051F340-9 A B C D.png|centre|vignette|upright=2|Exemple d'entrées-sorties intégrées à un microcontroleur.]]
En plus des entrées-sorties intégrées, les microcontrôleurs ont souvent des '''ports d'entrées-sorties''' banalisés, à savoir qu'on peut brancher n'importe quoi dessus. Il était possible de brancher un capteur de température, un moteur à commander, un port série, un port parallèle, un écran, un clavier, une souris, peu importe. Les ports font souvent un octet et ils sont généralement reliées directement ou indirectement au processeur. Le logiciel qui s'exécute sur le processeur décide quoi envoyer sur des broches et comment interprète ce qui est reçu dessus.
Les ports banalisés de ce type sont parfois appelés des '''GPIO''', abréviation de ''General Purpose Input/Output'', mais nous utiliserons le terme de ''port I/O''. Un port regroupe plusieurs broches qui peuvent être utilisés à volonté. C'est le logiciel qui s'exécute sur le processeur qui décide de la fonction de broches. Un port IO peut fonctionner soit en tant qu'entrée, soit en tant que sortie. Il est possible de changer de sens en cours de fonctionnement, pour passer d'une entrée à une sortie ou inversement.
[[File:Afficheurs7seg.png|centre|vignette|upright=2|Exemple d'utilisation d'un port I/O. le port est connecté à un afficheur LCD dit 7-segments. Les 8 entrées de cet afficheur sont connectées à un port I/O d'un octet.]]
===Les SoC haute performance===
Les SoC les plus performants sont actuellement utilisés dans les téléphones mobiles, tablettes, ''Netbook'', ''smartphones'', ou tout appareil informatique grand public qui ne doit pas prendre beaucoup de place. La petite taille de ces appareils fait qu'ils gagnent à regrouper toute leur électronique dans un circuit imprimé unique. Mais les contraintes font qu'ils doivent être assez puissants. Ils incorporent des processeurs assez puissants, surtout ceux des ''smartphones''. C'est absolument nécessaire pour faire tourner le système d'exploitation du téléphone et les applications installées dessus.
Niveau entrées-sorties, ils incorporent souvent des interfaces WIFI et cellulaires (4G/5G), des ports USB, des ports audio, et même des cartes graphiques pour les plus puissants d'entre eux. Les SoC incorporent des cartes graphiques pour gérer tout ce qui a trait à l'écran LCD/OLED, mais aussi pour gérer la caméra, voire le visionnage de vidéo (avec des décodeurs/encodeurs matériel). Par exemple, les SoC Tegra de NVIDIA incorporent une carte graphique, avec des interfaces HDMI et VGA, avec des décodeurs vidéo matériel H.264 & VC-1 gérant le 720p. Pour résumer, les périphériques sont adaptés à leur utilisation et sont donc foncièrement différents de ceux des microcontrôleurs.
[[File:SOMblk.png|centre|vignette|upright=3|Exemple de SoC.]]
Un point important est que les processeurs d'un SoC haute performance sont... performants. Ils sont le plus souvent des processeurs de marque ARM, qui sont différents de ceux utilisés dans les PC fixe/portables grand public qui sont eux de type x86. Nous verrons dans quelques chapitres en quoi consistent ces différences, quand nous parlerons des jeux d'instruction du processeur. Autrefois réservé au monde des PCs, les processeurs multicœurs deviennent de plus en plus fréquents pour les SoC de haute performance. Il n'est pas rare qu'un SoC incorpore plusieurs cœurs. Il arrive même qu'ils soient foncièrement différents, avec plusieurs cœurs d'architecture différente.
La frontière entre SoC haute performance et microcontrôleur est de plus en plus floue. De nombreux appareils du quotidien intègrent des SoC haute performance, d'autres des microcontrôleurs. Par exemple, les lecteurs CD/DVD/BR et certains trackers GPS intègrent un SoC ou des processeurs dont la performance est assez pêchue. À l'opposé, les systèmes domotiques intègrent souvent des microcontrôleurs simples. Malgré tout, les deux cas d'utilisation font que le SoC/microcontrôleur est connecté à un grand nombre d'entrées-sorties très divers, comme des capteurs, des écrans, des LEDs, etc.
[[File:GPS tracker Hardware Architecture.png|centre|vignette|upright=2|Hardware d'un tracker GPS.]]
==Étude de l'architecture de quelques consoles de jeu==
Après avoir vu la théorie, nous allons voir des exemples réels d'ordinateurs. Dans ce qui suit, nous allons voir des ordinateurs qui collent assez bien à l''''architecture de base''' vue plus haut, avec un CPU, une RAM et une ROM, quelques entrées-sorties. Tous les ordinateurs modernes, mais aussi dans les smartphones, les consoles de jeu et autres, utilisent une architecture grandement modifiée et améliorée, avec un grand nombre de périphériques, des disques durs/SSD, un grand nombre de mémoires différentes, etc.
Il pourrait sembler pertinent d’étudier des microcontrôleurs ou des ''System On Chip'', en premier lieu. Mais nous éviterons soigneusement de tels systèmes pour le moment. La raison est qu'ils ont un grand nombre d'entrées-sorties, qui sont peu familières. Attendez-vous à avoir près d'une vingtaine ou centaine d'entrée-sorties différentes pour de tels systèmes. Le tout est très complexe, bien trop pour un premier exemple.
À la place, nous allons voir précisément des exemples plus simples : les premiers PC, et des consoles de jeu 8 et 16 bits. Bien que ce soit des systèmes très simples, ils sont cependant plus complexes que l'architecture de base. Et leur avantages/désavantages sont un peu inverse l'un de l'autre. Si on devait résumer les différences, on aurait ceci :
* Les PC ont plus d'entrées-sorties que les consoles, bien que nettement moins que pour les microcontrôleurs/SoC.
* Les PC utilisent des disques durs, les consoles font avec soit des cartouches de jeu, soit des CD/DVD.
* Les PC utilisent des cartes électroniques séparées pour le son et l'écran, les consoles utilisent des circuits soudés sur la carte mère, qui sont souvent des co-processeurs.
* Les PC ont une mémoire ROM soudées sur la carte mère, les consoles 8 bits font sans.
Les PC et micro-ordinateurs ont plus d'entrées-sorties que les consoles. Même si on mets de côté les périphériques, ils ont aussi beaucoup d'entrées-sorties soudées sur la carte mère. En comparaison, les consoles de jeu 8/16 bits se débrouillent avec : une cartouche de jeu et une manette en entrée, une sortie vidéo et une sortie son.
Un autre point important est l'absence de disque dur ou de lecteur CD. La présence d'un disque dur ou d'un lecteur CD/DVD complexifie tout de suite l'architecture des PC. Il faut leur réserver un bus dédié ou les connecter à un bus système, ajouter des circuits sur la carte mère, etc. Et surtout, il faut expliquer comment l'ordinateur exécute des programmes, ce qui demande de parler de l'interaction avec le disque dur et la ROM du BIOS. Rien de tout cela sur les consoles de jeu 8 et 16 bits. Elles utilisent à la place une mémoire ROM, dans la cartouche de jeu, pour mémoriser le code du jeu. Pas besoin de parler des mémoires de stockage, on est beaucoup plus proche de l'architecture de base avec une ROM unique.
Autre différence : les PC utilisent des cartes électroniques à brancher sur la carte mère pour alimenter l'écran et les hauts-parleurs/casques, alors que les consoles de jeu utilisent des co-processeurs dédiés pour le son et les graphismes. Nous avons déjà expliqué ce que sont les co-processeurs plus haut, aussi les co-processeurs des consoles nous paraitrons familiers. On n'a pas à s’embêter à expliquer ce que sont les cartes d'extension, les bus associés et tout ce qui va avec, cela peut être retardé pour la section sur l'architecture des PC.
Par contre, n'allez pas croire que tout est rose avec les consoles 8/16 bits. Il y a quelques différences qui font qu'elles sont plus complexes qu'un PC sur certains points. La gestion de la cartouche de jeu est notamment un peu subtile à comprendre, bien que ce soit bien plus simple à comprendre qu'un système avec un disque dur. Les cartouches de jeu intègrent une mémoire ROM, pour mémoriser les données du jeu, voire son code. Et le processeur doit exécuter le code depuis cette mémoire ROM. La conséquence est que les consoles 8/16 bits utilisent une architecture Harvard, avec un bus relié à la cartouche pour lire les instructions. Mais si ce n'était que ça... Les cartouches mémorisent aussi les données pour les graphismes, ce qui fait que le co-processeur vidéo doit lui aussi lire la cartouche pour récupérer ces données...
===L'architecture de la TurboGraphX-16===
La console PC Engine, aussi appelée TurboGraphX, est une ancienne console 8 bits. Elle contient un processeur 65C02, 8 kibioctets de RAM, un port manettes, une carte son et une carte vidéo. La '''carte son''' est le composant qui s'occupe de commander les haut-parleurs et de gérer tout ce qui a rapport au son. La '''carte graphique''' est le composant qui est en charge de calculer les graphismes, tout ce qui s'affiche à l'écran. Sur cette console, les cartes son et graphique ne sont PAS des co-processeurs, ce sont des circuits électroniques dits fixes. C'est totalement différent de ce qu'on a sur les consoles modernes, aussi le préciser est important.
Bien que la carte graphique ne soit pas un processeur, elle a 64 kibioctets de RAM rien que pour elle. La RAM en question est séparée de la RAM normale, c'est un circuit intégré séparé. Et c'est un cas très fréquent, qui reviendra par la suite. La majeure partie des cartes graphiques dispose de leur propre '''mémoire vidéo''', totalement réservée à la carte graphique. La RAM vidéo est connectée à la carte graphique via un bus séparé. Le processeur est souvent connecté à ce bus, afin de pouvoir écrire des données dedans, mais ce n'est pas le cas ici.
[[File:Architecture de la PC Engine, aussi appelée TurboGrafx-16.png|centre|vignette|upright=2.5|Architecture de la PC Engine, aussi appelée TurboGrafx-16]]
L'architecture de la console était particulièrement simple. Le processeur était le centre de l'architecture, tout était connecté dessus. Il y a un bus pour la cartouche de jeu, un autre pour la RAM, un autre pour les manettes, un autre pour carte son, et un dernier pour la carte graphique. Le fait d'avoir un bus par composant est assez rare et ce n'est le cas ici que parce des conditions particulières sont remplies. Déjà, il y a peu d'entrée-sorties. Ensuite, les bus font tous 8 bits, vu que le processeur est un CPU 8 bits. Avec 5 connexions de 8 bits, le tout utilise 40 broches, ce qui est beaucoup, mais totalement gérable. Par contre, les choses changerons pour les autres consoles.
Au final, l'organisation des bus peut s'expliquer avec ce qu'on a vu dans la section sur les bus de communication. La console utilise une architecture Harvard, car la ROM et la RAM utilisent des bus différents. De plus, il y a des bus dédiés aux entrées-sorties, séparés des bus mémoire. Enfin, la carte graphique a droit à ses propres bus pour lire dans la cartouche et dans sa RAM vidéo dédiée.
===L'architecture de la console de jeu NES===
Maintenant, nous allons voir la console de Jeu Famicom, aussi appelée la NES en occident. Elle a une architecture centrée sur un processeur Ricoh 2A03, similaire au processeur 6502, un ancien processeur autrefois très utilisé et très populaire. Le processeur est associé à 2 KB de mémoire RAM.
Sur certaines cartouches, on trouve une RAM utilisée pour les sauvegardes, qui est adressée par le processeur directement. Première variation par rapport à l'architecture de la console précédente : l'ajout de la RAM pour les sauvegardes dans les cartouches.
Niveau carte graphique, une différence importante est que la carte graphique est connectée à la cartouche de jeu via un autre bus, afin de pouvoir lire les sprites et textures du jeu dans la cartouche.
[[File:Architecture de la NES.png|centre|vignette|upright=2.5|Architecture de la NES]]
La différence avec l'architecture précédente est que des bus ont été fusionnés. Comme dit plus haut, le système utilise une architecture Harvard, vu que la ROM est dans la cartouche, alors que la RAM est soudée à la carte mère. Par contre, la Famicon utilise un bus dédié aux entrées-sorties. Il est utilisé pour la carte son et la carte graphique, seules les manettes sont sur un bus à part. Ce qui fait qu'on devrait plutôt parler de bus de sorties, mais passons... L'essentiel est qu'on n'est plus tout à fait dans le cas de la console précédente, avec un bus par composant.
===L'architecture de la SNES===
L'architecture de la SNES est illustrée ci-dessous. Les changements pour le processeur et la RAM sont mineurs.La RAM a augmenté en taille et passe à 128 KB. Pareil pour la RAM de la carte vidéo, qui passe à 64 KB. Par contre, on remarque un changement complet au niveau des bus, de la carte graphique et de la carte son.
[[File:Architecture de la SNES.png|centre|vignette|upright=2|Architecture de la SNES]]
La console utilise un '''bus système unique''', sur lequel tout est connecté : ROM, RAM, entrées-sorties, etc. La seule exception est pour les manettes, qui sont encore connectées directement sur le processeur, via un bus séparé. La transition vers un bus système s'explique par le fait que la console est maintenant de 16 bits, ce qui fait que les bus doivent être plus larges. Le processeur adresse des mémoires RAM et ROM plus grandes, ce qui double la taille de leurs bus. De plus, les entrées-sorties aussi ont besoin d'un bus plus large. Le processeur n'ayant pas un nombre illimité de broches, la seule solution est de fusionner les bus en un seul bus système.
Un autre changement est que la carte graphique est maintenant composée de deux circuits séparés. Encore une fois, il ne s'agit pas de coprocesseurs, mais de circuits non-programmables. Par contre, la carte son est remplacée par deux coprocesseurs audio ! De plus, les deux processeurs sont connectés à une mémoire RAM dédiée de 64 KB, comme pour la carte graphique. L'un est un processeur 8 bits (le DSP), l'autre est un processeur 16 bits.
Un point très intéressant : certains jeux intégraient des coprocesseurs dans leurs cartouches de jeu ! Par exemple, les cartouches de Starfox et de Super Mario 2 contenait un coprocesseur Super FX, qui gérait des calculs de rendu 2D/3D. Le Cx4 faisait plus ou moins la même chose, il était spécialisé dans les calculs trigonométriques, et diverses opérations de rendu 2D/3D. En tout, il y a environ 16 coprocesseurs d'utiliser et on en trouve facilement la liste sur le net. La console était conçue pour, des pins sur les ports cartouches étaient prévues pour des fonctionnalités de cartouche annexes, dont ces coprocesseurs. Ces pins connectaient le coprocesseur au bus des entrées-sorties. Les coprocesseurs des cartouches de NES avaient souvent de la mémoire rien que pour eux, qui était intégrée dans la cartouche.
===L'architecture de la Megadrive et de la néo-géo===
Passons maintenant à la console de jeu Megadrive, une console 16 bits. Elle a une architecture similaire à celle de la néo-géo, une autre console bien plus puissante, sorti à peu près en même temps. Elles intègrent deux processeurs : un Motorola 68000 qui sert de processeur principal, un Z80 qui sert de processeur dédié à l'audio. Le Z80 et le Motorola 68000 étaient deux processeurs très populaires à l'époque. Le Z80 est une sorte de version améliorée de l'Intel 8088 utilisé sur les anciens PC et de nombreuses consoles utilisaient des Z80 comme processeur principal. Il était familier pour les programmeurs de l'époque, pour son cout réduit, sa bonne disponibilité, et bien d'autres avantages liés à sa production de masse.
Le Z80 est utilisé comme co-processeur audio. Il commande un synthétiseur sonore, et est relié à sa propre mémoire, distincte de la mémoire principale. Le MC68000 est le processeur principal et a une relation maitre-esclave avec le Z80 : le MC68000 envoie des commandes au Z80, mais la communication ne va pas dans l'autre sens.
Le Motorola 68000 était un processeur 16 bits, alors que le Z80 est un processeur 8 bits. Et cette différence fait que l'on ne peut pas connecter directement les deux sur le même bus, ou du moins pas facilement. La solution retenue est d'utiliser deux bus séparés : un bus de 16 bits connecté au 68000, un bus de 8 bits connecté au Z80. Le premier bus est un bus système sur lequel est connecté le 68000, 64 kibioctets de RAM, la cartouche de jeu, et la carte graphique. Le second bus est un bus de 8 bits, plus court, relié au Z80, à un synthétiseur sonore, et 8 kibioctets de RAM
Les deux bus sont connectés à un '''''chipset''''', un circuit répartiteur, qui fait le pont entre les deux bus. Les manettes sont connectées sur le ''chipset''. Il contient un registre de 8 bits, dans lequel le MC68000 peut écrire dedans à sa guise, le registre étant adressable par le processeur. Lorsque le MC68000 écrit une valeur dedans, cela déclenche l’exécution automatique d'un programme pré-déterminé sur le Z80.
: Pour ceux qui savent ce qu'est une interruption, les valeurs écrites dans ce registre sont des numéros d'interruption, qui indiquent quelle routine d'interruption exécuter.
[[File:Architecture de la Megadrive et de la Néogeo.png|centre|vignette|upright=2.5|Architecture de la Megadrive et de la Néogeo]]
Cet exemple nous montre que les bus systèmes sont certes très simples, mais aussi inflexibles. Ils fonctionnent bien quand les composants branchés dessus sont tous des composants 8 bits, ou sont tous de 16 bits, ou tous 32 bits. Mais dès qu'on mélange composants 8, 16, 32 ou 64 bits, les choses deviennent plus compliquées. Il est alors préférable d'utiliser des bus séparés, avec des répartiteurs pour faire le pont entre les différents bus. Et nous verrons que le problème s'est posé lui aussi sur les PC.
===L'architecture des anciennes consoles Playstation : beaucoup de co-processeurs===
Les consoles que nous venons d'aborder étaient des consoles 8 ou 16 bits. A partir des consoles 32 bits, leur architecture s'est rapprochée de celle des PC, avec un usage plus complexes de répartiteurs. La XBOX était très semblable à un PC : le processeur était un Pentium 3 modifié, la carte graphique était une Geforce 3 modifiée, les 64 mébioctets de RAM était la même mémoire DDR que celle des PC, le répartiteur secondaire était un ''chipset'' nForce de NVIDIA, etc. Mais les Playstation 1, 2 et 3 se distinguent de leur contemporains. Elles disposent de très nombreux co-processeurs, qui sont en plus très variés.
La Playstation 1 a été une des premières console à utiliser les CD-ROM comme support de stockage, en remplacement des cartouches. La conséquence est que la console contient une mémoire ROM, soudée à la carte mère, de 512 kibioctets. Elle contient aussi 2 mébioctets de RAM, une carte graphique avec 1 mébioctet de mémoire vidéo, un processeur, et de quoi gérer les périphériques. Il y a un co-processeur audio spécialisé, avec 512 kibioctets de RAM, ce qui nous est familier. Par contre, les autres co-processeurs ne le sont pas.
Déjà, le lecteur de CD-ROM est associé à des circuits sur la carte mère, il y a tout un sous-système dédié au lecteur de CD. Il y a un contrôleur qui sert d'interface avec le lecteur proprement dit, mais aussi deux co-processeurs audio et 32 kibioctets de RAM. Les co-processeurs audio servent à lire des CD sans trop utiliser le second co-processeur audio, ils lui servent de complément.
Ensuite, le processeur incorpore plusieurs cœurs, avec un cœur principal et plusieurs co-processeurs. Le premier est un co-processeur système, qui est utilisé pour gérer la mémoire cache intégrée au processeur, pour des fonctionnalités appelées interruptions et exceptions, ainsi que pour configurer le processeur. Le second est un co-processeur arithmétique spécialisé dans les calculs en virgule flottante, très importants pour le rendu 3D. Enfin, il y a un décodeur vidéo, qui n'est pas un co-processeur, mais un circuit non-programmable, spécialisé dans le décodage vidéo. De nos jours, ce circuit aurait été intégré dans la carte graphique, mais il était intégré dans le processeur sur la Playstation 2.
Pour le reste, le processeur est la figure centrale de la console. Il est connecté à 4 bus : un pour la RAM, un pour la carte graphique, un pour les manettes, un autre pour le reste. Le dernier bus est connecté au système audio et au système pour le lecteur CD. Ce serait un bus d'entrée-sortie, s'il n'était pas connecté à la mémoire ROM. Vous avez bien lu : la mémoire ROM est reliée au bus d'entrée-sortie.
[[File:Architecture de la Playstation.png|centre|vignette|upright=2.5|Architecture de la Playstation]]
La Playstation 2 est composé d'un processeur, couplé à 32 Mébioctets de RAM, et d'un paquet de co-processeurs. Plus de co-processeurs que la PS1. Le processeur principal n'est pas la même que celui de la PS1, mais il a une architecture similaire. Il intègre un décodeur vidéo sur le même circuit intégré, ainsi que deux co-processeur. Les co-processeurs ne sont cependant pas les mêmes.
Le co-processeur système disparait et est remplacé par un second co-processeur arithmétique. Les deux co-processeurs arithmétiques sont spécialisés dans les nombres flottants, avec quelques différences entre les deux. Par exemple, le second co-processeur gérait des calculs trigonométriques, des exponentielles, des logarithmes, et d'autres fonctions complexes du genre ; mais pas le premier co-processeur. Ils sont reliés à 4 kibioctets de RAM pour le premier, 16 kibioctets de RAM pour le second ; qui sont intégrées dans le processeur et non-représentés dans le diagramme ci-dessous.
La PS2 intègre aussi un co-processeur d'entrées-sorties. Pour information, il s'agit du processeur principal de la Playstation 1, qui est ici utilisé différemment, suivant que l'on place un jeu PS1 ou PS1 dans la console. Si on met un jeu PS1, il est utilisé pour émuler la Playstation 1, afin de faire tourner le jeu PS1 sur la PS2. Si on met un jeu PS2, il est utilisé comme co-processeur d'entrée-sortie et fait l'interface entre CPU et entrées-sorties. Il est relié à 2 mébioctets de RAM, soit exactement la même quantité de mémoire que la Playstation 1.
Tous les périphériques sont connectés au co-processeur d'entrées-sortie. Pour cela, le co-processeur d'entrées-sortie est relié à deux bus dédiés aux périphériques. Le premier bus est relié aux manettes, aux ports USB et aux ports pour cartes mémoires. Le second bus est relié à la carte son, la carte réseau, le lecteur DVD, et un port PCMIA. Notons que la carte son intègre un co-processeur audio, qui n'est pas représenté dans le diagramme ci-dessous.
[[File:Playstation 2 architecture.png|centre|vignette|upright=2.5|Playstation 2 architecture]]
==L'architecture des PC et son évolution==
Après avoir vu les consoles, nous allons maintenant voir les anciens PC, des années 80 ou 90. Le tout premier PC était techniquement l''''IBM PC'''. Par la suite, de nombreux ordinateurs ont tenté de reproduire l'IBM PC originel, avec parfois quelques modifications mineures. De tels ordinateurs ''IBM PC compatibles'', ont été très nombreux, pour des raisons diverses. Le fait d'utiliser des composants banalisés, facilement disponibles, ainsi qu'une bonne documentation de l'IBM PC originel, a grandement aidé. Les IBM PC compatibles ont progressivement évolué pour donner les PC actuels. L'IBM PC compatible a donné naissance à de nombreux standards divers.
===L'IBM PC originel et l'IBM PC XT===
[[File:IBM PC XT 02.jpg|vignette|IBM PC XT.]]
Nous allons commencer par voir l'IBM PC originel, et son successeur : l'IBM Personal Computer XT. Nous les appelerons tous deux l'IBM PC. L'IBM PC utilisait un processeur Intel 8088, qui était un processeur 8 bits. Ils utilisaient un bus système unique, appelé le '''bus XT'''. Le bus système allait à 4.77 MHz, soit la même fréquence que le processeur. C'était un bus de 8 bits, ce qui collait parfaitement avec les processeurs 8 bits commercialisés par Intel à l'époque.
L'IBM PC comprenait une mémoire ROM avec de quoi faire fonctionner le PC. La ROM en question contenait un programme minimal, appelé le '''BIOS''', sans lequel le PC ne fonctionnait pas du tout. Il servait de base pour le système d'exploitation et MS-DOS ne fonctionnait pas sans elle. De nos jours, son rôle est plus limité : sans elle, le PC ne démarre pas. Mais nous détaillerons cela dans le prochain chapitre.
En plus de la ROM pour le BIOS, l'IBM PC avait quatre mémoires ROM dédiée au langage de programmation BASIC. Lorsque le PC démarrait, il ne bootait pas un système d'exploitation, mais lançait l'interpréteur pour le langage BASIC. De nos jours, ce serait l'équivalent d'un ordinateur qui boote directement sur du Python, à savoir la console Python que vous avez peut-être déjà utilisé si vous avez testé Python. Ceux qui ont déjà touché à un ordinateur de l'époque savent ce que ca veut dire, mais c'est malheureusement très difficile à expliquer sans ce genre d'expérience. Toujours est-il que c'était une sorte de norme à l'époque
: les ordinateurs bootaient généralement sur un interpréteur BASIC.
[[File:XT Bus pins.svg|vignette|Connecteur du bus XT.]]
Les PC étaient conçus pour qu'on branche des '''cartes d'extension''', à savoir des cartes électroniques qu'on branchait sur la carte mère, à l'intérieur du PC. Les cartes d'extension de l'époque étaient surtout des cartes son ou des cartes graphiques, mais aussi des cartes pour brancher des péripéhriques. par exemple, on pouvait ajouter deux cartes graphiques dans l'IBM PC originel : l'''IBM Monochrome Display Adapter'' et/ou la ''IBM Color Graphics Adapter''. De nos jours, les cartes son sont intégrées à la carte mère, mais les cartes graphiques sont restées des cartes d'extension.
Les cartes d'extension étaient branchées sur un '''connecteur XT''', qui était directement relié au bus XT. Le connecteur XT est illustré ci-contre, mais ne vous en souciez pas trop pour le moment. La carte mère de l'IBM PC avait 5 connecteurs de ce type, qu'on pouvait peupler avec autant de cartes d'extension. L'IBM Personal Computer XT est passé à 8 connecteurs XT, soit trois de plus.
Pour ce qui est des périphériques, l'IBM PC avait plusieurs connecteurs : un port série, un port parallèle, un port pour le clavier, et un port pour un lecteur cassette. Le clavier et le lecteur cassette étaient connectés directement sur la carte mère, qui contenait quelques circuits pour gérer le clavier. Par contre, les deux premiers n'étaient pas connectés à la carte mère. Le port série était en réalité une carte d'extension, branchée sur un connecteur XT. Et il en est de même pour le port parallèle.
Pour ce qui est des supports de stockage, l'IBM PC originel n'avait pas de disque dur et n'avait que des lecteurs de disquette. De plus, le lecteur de disquette n'était pas connecté directement sur la carte mère, mais était connecté à une carte d'extension, branchée sur un connecteur XT. La carte d'extension avait deux connecteurs, un par lecteur de disquette, ce qui fait que les deux lecteurs de disquettes pouvaient être branchés sur une seule carte d'extension. L'IBM Personal Computer XT a ajouté un disque dur, sauf sur quelques sous-modèles spécifiques.
Le PC avait aussi un petit haut-parleur capable de faire des bips.
Pour résumer, l'IBM PC originel se reposait beaucoup sur les cartes d'extension, sa carte mère contenait peu de choses. Enfin, peu de choses... Il y avait un processeur Intel 8088, éventuellement un coprocesseur flottant 8087, de la RAM, de la ROM, et des circuits intégrés assez divers. En voici la liste, certains vous seront familiers, d'autres vous seront inconnus à ce stade du cours :
* les circuits de décodage d'adresse ;
* un contrôleur DMA intel 8273 ;
* un contrôleur d'interruption 8259 ;
* un contrôleur de bus Intel 8288 pour gérer le bus XT ;
* un générateur d'horloge Intel 8284 et un diviseur de fréquence ;
* un ''timer'' Intel 8253, le même que celui étudié dans le chapitre sur les ''timers'' ;
* un contrôleur parallèle 8255.
Les multiplexeurs, registres et portes logiques, sont des circuits de décodage d'adresse, qui permettent de combiner plusieurs RAM en une seule, idem avec la mémoire ROM. Si vous verrez qu'il y a 5 mémoires ROM : une ROM pour le BIOS, et quatre autres ROM pour le BASIC. Les 4 ROM du BASIC sont combinées en une seule mémoire ROM. Pour les RAM, il y en a 8 à 32, qui sont combinées en une seule RAM de 16 à 64 kibioctets.
[[File:IBM 5150 Motherboard.svg|centre|vignette|upright=3|Carte mère de l'IBM 5150, un modèle de l'IBM PC.]]
===L'architecture d'un IBM PC compatible 16 bits===
Les PC suivants sont passés à des processeurs 16 bits, mais c'était toujours des processeurs x86 d'Intel, à savoir des Intel 286 et 386. La RAM a grossi, quelques entrées-sorties ont été ajoutées, mais l'architecture globale est plus moins resté le même. C'est surtout au niveau du bus et des périphériques que les changements majeurs ont eu lieu.
[[File:ISA Bus pins.svg|vignette|Connecteur ISA.]]
Les PC 16 bits utilisaient un bus système unique, sur lequel tout était connecté : le processeur, la RAM, la ROM, les cartes d'extension et tout le reste. Le bus en question s'appelait le '''bus AT''', mais il a rapidement été renommé en '''bus ISA''' (''Industry Standard Architecture''). Le bus ISA était prévu pour avoir une compatibilité avec le bus 8 bits de l'IBM PC originel. D'ailleurs, cela se ressent jusque dans le connecteur utilisé : le connecteur ISA est un connecteur XT qu'on a fusionné avec un second connecteur pour l'étendre de 8 à 16 bits.
Les PC 16 bits avaient toujours un port série, un port parallèle, un clavier, un lecteur de disquette et des cartes d'extension. Des disques durs pouvaient être ajoutés, aussi. Mais pour ces périphériques, un changement majeur a eu lieu comparé à l'IBM PC originel. L'IBM PC originel utilisait des cartes d'extension pour tout, sauf le clavier. Mais maintenant, les périphériques ne sont plus connectés à une carte d'extension. À la place, les circuits de la carte d'extension sont déplacés sur la carte mère. Mais n'allez pas croire qu'ils étaient connectés directement au bus ISA, il y avait des intermédiaires.
Le clavier était relié à un '''contrôleur de clavier''', qui faisait l'interface entre le connecteur du clavier et le bus ISA. Le contrôleur de clavier était appelé le ''Keyboard Controler'', abrévié en KB. Il recevait ce qui est tapé au clavier et traduisait cela en quelque chose de compréhensible par l'ordinateur.
Les autres périphériques étaient connectés à un circuit intégré dédié : l''''Intel 82091AA'''. Il était connecté au lecteur de disquette, au port série et au port parallèle. Il servait d'intermédiaire entre ces périphériques et le bus ISA. Vous pouvez le voir comme une sorte de répartiteur, mais qui ne serait pas connecté sur le processeur et la RAM
Enfin, il ne faut pas oublier les autres composants présents sur l'IBM PC originel. Le BIOS est toujours là, de même que les ''timers'' Intel 8253 PIT, le contrôleur d'interruption Intel 8259 et le contrôleur DMA Intel 8237. Les PC 16 bits ont aussi intégré une ''Real Time Clock'' (RTC). Pour rappel, c'est un composant qui permet au PC de mémoriser la date et l'heure courante, à la seconde près. Le tout est résumé dans le schéma ci-dessous.
[[File:Architecture de l'IBM PC compatible.png|centre|vignette|upright=2.5|Architecture de l'IBM PC compatible]]
Un point important est que le bus ISA allait à la même fréquence que le processeur, vu que c'était un bus système. Les processeurs de l'époque étaient des CPU 286 d'Intel, ou le 386 d'Intel. Les Intel 286 allaient de 4 MHz minimum, à 25 MHz maximum. Le 386, quant à lui, allait de 12 à 40 MHz. Le bus ISA devait aller à cette fréquence, il était synchrone avec le processeur.
Par la suite, les processeurs ont gagné en performance, ce qui fait que le bus ISA est devenu trop lent pour le processeur. Une idée a alors été de conserver le bus ISA, pour des raisons de compatibilité, mais de le reléguer comme bus secondaire. L'ordinateur contient alors deux bus : un bus système, et un bus ISA secondaire. Le lien entre les deux est réalisé par un '''pont ISA''', ''ISA Bridge'' en anglais. Le bus ISA fonctionnait alors sa fréquence usuelle, alors que le bus système était beaucoup plus rapide. Le bus système fonctionnait à une fréquence bien plus élevée, ce qui fait que le processeur pouvait communiquer à pleine vitesse, notamment avec la RAM. Le processeur n'était alors plus forcé à aller à la même fréquence que le bus ISA
[[File:Architecture de l'IBM PC compatible avec bridge ISA.png|centre|vignette|upright=2.5|Architecture de l'IBM PC compatible avec bridge ISA]]
Les PC de l'époque intégraient donc plusieurs bus séparés. Vous avez bien lu : plusieurs bus ! Ici, il s'agit de ce que j'appelle des '''bus en cascade''', à savoir qu'un bus est connecté à un autre bus par un intermédiaire. Au passage, si j'aborde ces exemples, car c'est pareil sur les ordinateurs modernes. Le pont ISA a été remplacé par des circuits différents, mais qui ont un rôle assez similaire. Le ''chipset'' de votre carte mère n'est qu'un lointain descendant du pont ISA, qui s'interface avec des bus différents.
===L'arrivée des standards AT et IDE pour les disques durs===
Initialement, les disques durs étaient placés dans l'ordinateur et étaient connectés sur le bus ISA, via une carte d'extension ISA. En clair, il fallait connecter le disque dur sur une carte d'extension, et non sur la carte mère. Les cartes d'extension en question permettaient de connecter un ou plusieurs disques durs, parfois des lecteurs de disquette supplémentaires. Les cartes ISA de ce type faisaient juste l'interface entre le bus ISA et les disques durs, rien de plus. L'interface en question a été standardisée, ce qui a donné le standard ''AT Bus Attachment'', qui a été abrévié en ATA.
Et ce n'était pas que pour les disques durs, de nombreux composants étaient dans ce cas. Une carte d'extension servait d'intermédiaire entre eux et la carte mère. Les cartes d'extension en question étaient appelées des ''Host bus adapter''.
[[File:Acculogic sIDE-4 Controller ISA.jpg|centre|vignette|upright=2|Carte ISA d'interface disque dur, de marque Acculogic.]]
Mais les choses ont rapidement évoluées, que ce soit du côté des cartes mères que du côté des disques durs. Le '''standard IDE''' a permis de brancher un disque dur directement sur la carte mère, sans passer par une carte d'interface ISA. Pour cela, la carte mère réservait un connecteur ISA pour le disque dur, renommé '''connecteur ATA'''. Pour que cela soit possible, il a fallu rajouter des circuits sur la carte mère. Tout ce qui était sur les cartes d'interface ISA s'est retrouvé sur la carte mère.
[[File:Ajout des ports IDE sur la carte mère.png|centre|vignette|upright=2|Ajout des ports IDE sur la carte mère]]
En réalité, les connecteurs ATA étaient des connecteurs ISA simplifiés. Un connecteur ISA avait en tout 98 broches, alors qu'un connecteur ATA n'en contient que 40. Les broches qui étaient inutiles pour les disques durs ont simplement été enlevées. Et qui dit connecteur spécialisé, dit câble spécialisé. Les disques durs étaient branchés sur le connecteur AT grâce à un câble ATA, sur lequel on pouvait connecter deux disques durs.
[[File:ATA Plug.svg|centre|vignette|upright=2|Connecteur ATA.]]
[[File:ATA cables.jpg|centre|vignette|upright=2|Cable ATA.]]
Il était donc possible de connecter deux disques durs sur un seul connecteur ATA. Et cette possibilité est devenue d'autant plus utile par la suite. A partir de la version 2, ATA supportait aussi les lecteurs de disquettes, les lecteurs de CD/DVD, et bien d'autres supports de stockage. Il était alors possible de connecter un lecteur CD et un disque dur sur un seul connecteur. Les cartes mères avaient généralement deux connecteurs ATA, et n'avaient pas besoin de plus. C'était suffisant pour connecter un disque dur, un lecteur de disquette et un lecteur CD, configuration courante entre les années 90 et 2000.
Un câble est donc connecté à deux supports de stockage. Pour distinguer les deux, le standard ATA ajoute une possibilité de configuration. Sur un câble, il doit y avoir un support de stockage "maitre" et un support "esclave". C'était la terminologie de l'époque, que je reproduis ici, même si elle est fortement trompeuse. N'allez pas croire que cela implique que l'un ait des avantages sur l'autre. Le support 'maitre" n'a pas droit à plus de bande passante, il n'a pas la priorité sur l'autre, rien du tout. Il s'agit juste d'un nombre qui permet de savoir avec qui le processeur communique, qui vaut 0 pour le premier support, 1 pour l'autre. Une sorte d'adresse de 1 bit, si l'on veut.
[[File:ATA-Konfiguration02.png|centre|vignette|upright=2|Configuration ATA.]]
Pour configurer un support de stockage en mode "maitre" ou "esclave", le support de stockage avait quelques pins dédiés. Il suffisait de placer un détrompeur en plastique sur les pins adéquats. Les pins se trouvaient à l'arrière du disque dur ou du lecteur de CD/DVD/Disquette/autre.
[[File:HDD Master and Slave Description.jpg|centre|vignette|upright=2|Configuration ''Master/Slave''.]]
===L'architecture d'un PC avec un processeur Intel 486===
Maintenant, passons aux ordinateurs 32 bits, avec l'exemple d'un PC avec un processeur 486 d'Intel. A cette époque, le bus ISA était devenu trop limité et était en place d'être remplacé par le bus PCI, qui avait la même fonction. De nombreuses cartes d'extension utilisaient déjà ce standard et étaient branchées sur des connecteurs PCI dédiés, différents des connecteurs ISA. Intuitivement, on se dit que le bus PCI remplaçait le bus ISA, mais les choses étaient plus compliquées. Les disques durs gardaient leur connecteur ATA, et ne passaient pas par le bus PCI. Ils avaient un bus IDE séparé, qui était un bus ISA modifié.
Là encore, les processeurs étaient devenus beaucoup plus rapides que le bus PCI. Les deux allaient à des fréquences assez différentes, ce qui fait que le bus PCI était séparé du bus système. Il y avait alors deux implémentations possibles.
* La première utilise un répartiteur unique, relié au processeur, à la RAM, au bus PCI, et au bus IDE.
* La seconde utilise un bus système séparé du bus PCI, avec un '''pont PCI''' pour faire l'interface entre les deux.
Le '''''System Controler''''' était un circuit intégré, placé sur la carte mère, qui peut servir soit de pont PCI, soit de répartiteur. Le répartiteur PCI sert d'intermédiaire avec le bus PCI, mais aussi avec le bus IDE, utilisé pour les disques durs, aussi appelé le bus ''Parallel ATA''. Il peut aussi être connecté au processeur, à la mémoire RAM, ainsi qu'à la mémoire cache, mais cela ne sert que quand il est utilisé comme répartiteur.
[[File:Architecture d'un PC utilisant un bus PCI, implémentation avec un répartiteur.png|centre|vignette|upright=2|Architecture d'un PC utilisant un bus PCI, implémentation avec un répartiteur]]
Pour des raisons de compatibilité, le bus ISA avait été conservé, aux côtés du bus PCI. Il y avait un pont ISA en plus du pont/répartiteur PCI. Une implémentation possible aurait été de connecter les deux ponts ISA et PCI à un bus système unique. Mais cette solution n'a pas été retenue. La raison est que le bus PCI et le bus ISA ont des performances très différentes. Le bus PCI est très rapide, le bus ISA beaucoup plus lent. La différence est d'un ordre de grandeur, environ. Dans ces conditions, il est possible de faire passer les communications ISA à travers le bus PCI. Pour cela, le pont ISA est directement connecté sur le pont PCI, comme illustré ci-dessous.
Et il en est de même pour le bus dédié aux disques durs. En effet, les disques durs étaient autrefois reliés au bus ISA, mais cela a changé depuis. Ils disposent maintenant de leur propre bus dédié, le '''bus IDE''', qui est un bus ISA simplifié. Et ce bus ISA simplifié était connecté directement sur le pont PCI.
[[File:Architecture de l'IBM PC compatible avec pont PCI.png|centre|vignette|upright=2|Architecture de l'IBM PC compatible avec pont PCI]]
Dans ce qui va suivre, nous allons étudier un exemple qui utilise un bus système séparé, avec un pont PCI, sans répartiteur. Voilà pour les grandes lignes, mais le schéma ci-dessous montre que tout est plus complexe. Vous remarquerez des connexions optionnelles entre le pont PCI et la mémoire RAM et la mémoire cache. La raison est que le pont PCI peut aussi servir de répartiteur en remplacement du bus système. Concrètement, on peut alors retirer le bus système. La mémoire, le bus PCI, le bus ISA, le bus IDE, le processeur et la RAM sont alors connectés au répartiteur PCI, qui sert d'intermédiaire central entre tous ces composants. Mais ce n'est pas la solution qui a été retenue dans notre exemple.
[[File:Intel486-Typ PCI System.png|centre|vignette|upright=2|PC IBM compatible avec un 486, un bus PCI et un bus ISA. Le ''host bus'' est le bus système.]]
Le pont ISA sert ici d'intermédiaire entre le bus système et le bus ISA. De plus, il a été amélioré sur de nombreux points. Il inclut notamment des circuits qui étaient autrefois sur la carte mère, à savoir le contrôleur DMA 82C87 et le contrôleur d'interruption 82C59, ainsi que les ''timers'' Intel 82C54. Les composants restants sont eux reliés sur un quatrième bus : le bus X, l'ancêtre du bus ''Low Pin Count''. Le bus X était celui du BIOS, du contrôleur de clavier, de la ''Real Time Clock'', et du contrôleur de périphérique 82091AA d'Intel.
[[File:ISA Bridge schematic.png|centre|vignette|upright=2|ISA Bridge.]]
===L'architecture des PC des années 90-2000===
Par la suite, les ponts PCI et ISA ont évolué avec l'évolution des bus de l'ordinateur. Le bus ISA a progressivement été remplacé par d'autres bus, comme le bus ''Low Pin Count'', le bus PCI a été remplacé par le PCI Express, d'autres bus ont été ajoutés, etc. Mais la séparation du ''chipset'' en deux a été conservée.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Le pont PCI et le pont ISA ont été remplacés respectivement par le '''pont nord''' et le '''pont sud''', plus connus par leurs noms anglais de ''northbridge'' et de ''southbridge''. Le pont nord servait d'interface entre le processeur, la mémoire et la carte graphique et est connecté à chacun par un bus dédié. Il intégrait aussi le contrôleur mémoire. Le pont sud est le répartiteur pour les composants lents, à savoir l'USB, l'Ethernet, etc. Le bus qui relie le processeur au pont nord était appelé le '''''Front Side Bus''''', abrévié en FSB.
[[File:IMac Chipset.png|centre|vignette|upright=2|Chipset séparé en northbridge et southbridge.]]
Un point important est que le bus PCI est devenu un bus assez lent, ce qui fait qu'il a finit par être connecté au pont sud. Le pont PCI est donc devenu le pont sud, dans le courant des années 2000. Durant un moment, un équivalent du pont ISA a subsisté dans un circuit de '''''Super IO'''''. Concrètement, il s'occupait du lecteur de disquette, du port parallèle, du port série, et des ports PS/2 pour le clavier et la souris. Mais il ne gérait pas le bus ISA, mais son remplaçant, le bus ''Low Pin Count''.
[[File:Motherboard diagram fr.svg|centre|vignette|upright=1.5|Carte mère avec circuit Super IO.]]
===L'architecture des PC depuis les années 2000===
Depuis la sortie du processeur AMD Athlon 64, le pont nord a été fusionné dans le processeur. La fusion ne s'est pas faite en une fois, des fonctionnalités ont progressivement été progressivement intégrées dans le processeur. Le pont sud est resté, mais il a alors été progressivement connecté directement au processeur. La raison derrière cette intégration est que les processeurs avaient de plus en plus de transistors à leur disposition. Ils en ont profité pour intégrer le pont nord. Et cela permettait de simplifier le câblage des cartes mères, sans pour autant rendre vraiment plus complexe la fabrication du processeur. Les industriels y trouvent leur compte.
La première étape a été l'intégration du contrôleur mémoire a été intégré au processeur. Concrètement, le résultat était que la mémoire RAM n'était plus connectée au pont nord, mais était connectée directement au processeur ! Il y a donc eu un retour d'un bus mémoire, mais spécialisé pour la mémoire RAM. En théorie, une telle intégration permet diverses optimisations quant aux transferts avec la mémoire RAM. Les transferts ne passent pas par un répartiteur, ce qui réduit le temps d'accès à la mémoire RAM. Ajoutons de sombres histoires de prefetching, d'optimisation des commandes, et j'en passe. Toujours est-il que le pont nord ne servait alors d'intermédiaire que pour les ports PCI Express, et le pont sud.
[[File:X58 Block Diagram.png|centre|vignette|upright=2|Chipset X58 d'Intel.]]
Par la suite, la carte graphique fût aussi connectée directement sur le processeur. Le processeur incorpore pour cela des contrôleurs PCI-Express. Le pont nord a alors disparu complétement, son intégration dans le processeur était complète. Sur les cartes mères Intel récentes, le pont sdud subsiste, il est appelé le ''Platform Controler Hub'', ou PCH. L'organisation des bus sur la carte mère qui résulte de cette connexion du processeur à la carte graphique, est illustrée ci-dessous, avec l'exemple du PCH.
[[File:Intel 5 Series architecture.png|centre|vignette|upright=2|Intel 5 Series architecture]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'interface électrique entre circuits intégrés et bus
| prevText=L'interface électrique entre circuits intégrés et bus
| next=La hiérarchie mémoire
| nextText=La hiérarchie mémoire
}}
</noinclude>
qgrh50botvpark1o3yeu44dis4xpawj
775633
775632
2026-11-02T15:04:14Z
Mewtow
31375
/* Le décodage d'adresse sur les architectures Von Neumann */
775633
wikitext
text/x-wiki
Dans les chapitres précédents, nous avons vu comment représenter de l'information, la traiter et la mémoriser avec des circuits. Mais un ordinateur n'est pas qu'un amoncellement de circuits et est organisé d'une manière bien précise. Il est structuré autour de trois circuits principaux :
* un '''processeur''', qui manipule l'information et donne un résultat ;
* une '''mémoire''' qui mémorise les données à manipuler ;
* les '''entrées/sorties''', qui permettent à l'ordinateur de communiquer avec l'extérieur.
[[File:Architecture Von Neumann.png|centre|vignette|upright=2|Architecture d'un système à mémoire.]]
Pour faire simple, le processeur est un circuit qui s'occupe de faire des calculs. Rien d'étonnant à cela. Je rappelle que tout est codé par des nombres dans un ordinateur, ce qui fait que manipuler des nombres revient simplement à faire des calculs. Un ordinateur n'est donc qu'une grosse calculatrice améliorée, et le processeur est le composant qui fait les calculs.
La mémoire s'occupe purement de la mémorisation des données, des nombres sur lesquelles faire des calculs. Pour être plus précis, il y a deux mémoires : une pour les données proprement dites, une autre pour le programme à exécuter. La première est la '''mémoire RAM''', la seconde est la '''mémoire ROM'''. Nous détaillerons ce que sont ces deux mémoires dans la suite du chapitre, mais sachez que nous avions déjà rencontré ces deux types de mémoires dans les chapitres sur les registres et les mémoires adressables.
Les entrées-sorties permettent au processeur et à la mémoire de communiquer avec l'extérieur et d'échanger des informations avec des périphériques. Les '''périphériques''' regroupent, pour rappel, tout ce est branché sur un ordinateur, mais n'est pas à l'intérieur de celui-ci.
Le processeur, les mémoires et les entrées-sorties communiquent ensemble via un '''réseau d'interconnexions'''. Le terme est assez barbare, mais rien de compliqué sur le principe. C'est juste un ensemble de fils électriques qui relie les différents éléments d'un ordinateur. Les interconnexions sont souvent appelées le bus de communication, mais le terme est un abus de langage, comme on le verra plus bas.
Afin de simplifier les explications, on va supposer que le réseau d'interconnexion est le suivant. Tout est connecté au processeur. Il y a des interconnexions entre le processeur et la mémoire RAM, d'autres interconnexions entre processeur et mémoire ROM, et d'autres entre le processeur et les entrées-sorties. Nous verrons que d'autres réseaux d'interconnexions fusionnent certaines interconnexions, pour les partager entre la ROM et la RAM, par exemple. Mais pour le moment, gardez le schéma ci-dessous en tête.
[[File:Réseau d'interconnexion avec un processeur au centre.png|centre|vignette|upright=2|Réseau d'interconnexion avec un processeur au centre]]
==Les mémoires RAM et ROM==
La mémoire est le composant qui mémorise des informations, des données. Dans la majorité des cas, la mémoire est composée de plusieurs '''cases mémoire''', chacune mémorisant plusieurs bits, le nombre de bits étant identique pour toutes les cases mémoire. Dans le cas le plus simple, une case mémoire mémorise un '''octet''', un groupe de 8 bits. Mais les mémoires modernes mémorisent plusieurs octets par case mémoire : elles ont des cases mémoires de 16, 32 ou 64 bits, soit respectivement 2/4/8 octets. De rares mémoires assez anciennes utilisaient des cases mémoires contenant 1, 2, 3, 4, 5, 6 7, 13, 17, 23, 36 ou 48 bits. Mais ce n'était pas des mémoires électroniques, aussi nous allons les passer sous silence.
Tout ce qu'il faut savoir est que la quasi-totalité des mémoires électronique a un ou plusieurs octets par case mémoire. Pour simplifier, vous pouvez imaginer qu'une mémoire RAM est un regroupement de registre, chacun étant une case mémoire. C'est une description pas trop mauvaise pour décrire les mémoires RAM, qu'on abordera dans ce qui suit.
{|class="wikitable"
|+ Contenu d'une mémoire, case mémoire de 16 bits (deux octets)
|-
! Case mémoire N°1
| 0001 0110 1111 1110
|-
! Case mémoire N°2
| 1111 1110 0110 1111
|-
! Case mémoire N°3
| 0001 0000 0110 0001
|-
! Case mémoire N°4
| 1000 0110 0001 0000
|-
! Case mémoire N°5
| 1100 1010 0110 0001
|-
! ...
| ...
|-
! Case mémoire N°1023
| 0001 0110 0001 0110
|-
! Case mémoire N°1024
| 0001 0110 0001 0110
|}
Dans ce cours, il nous arrivera de partir du principe qu'il y a un octet par case mémoire, par souci de simplification. Mais ce ne sera pas systématique. De plus, il nous arrivera d'utiliser le terme adresse pour parler en réalité de la case mémoire associée, par métonymie.
===La capacité mémoire===
Bien évidemment, une mémoire ne peut stocker qu'une quantité finie de données. Et à ce petit jeu, certaines mémoires s'en sortent mieux que d'autres et peuvent stocker beaucoup plus de données que les autres. La '''capacité''' d'une mémoire correspond à la quantité d'informations que celle-ci peut mémoriser. Plus précisément, il s'agit du nombre maximal de bits qu'une mémoire peut contenir. Elle est le produit entre le nombre de cases mémoire, et la taille en bit d'une case mémoire.
Toutes les mémoires actuelles utilisant des cases mémoire d'un ou plusieurs octets, ce qui nous arrange pour compter la capacité d'une mémoire. Au lieu de compter cette capacité en bits, on préfère mesurer la capacité d'une mémoire avec le nombre d'octets qu'elle contient. Mais les mémoires des PC font plusieurs millions ou milliards d'octets. Pour se faciliter la tâche, on utilise des préfixes pour désigner les différentes capacités mémoires. Vous connaissez sûrement ces préfixes : kibioctets, mébioctets et gibioctets, notés respectivement Kio, Mio et Gio.
{|class="wikitable"
|-
!Préfixe!!Capacité mémoire en octets!!Puissance de deux
|-
||Kio||1024||2<sup>10</sup> octets
|-
||Mio||1 048 576||2<sup>20</sup> octets
|-
||Gio||1 073 741 824||2<sup>30</sup> octets
|}
On peut se demander pourquoi utiliser des puissances de 1024, et ne pas utiliser des puissances un peu plus communes ? Dans la majorité des situations, les électroniciens préfèrent manipuler des puissances de deux pour se faciliter la vie. Par convention, on utilise souvent des puissances de 1024, qui est la puissance de deux la plus proche de 1000. Or, dans le langage courant, kilo, méga et giga sont des multiples de 1000. Quand vous vous pesez sur votre balance et que celle-ci vous indique 58 kilogrammes, cela veut dire que vous pesez 58 000 grammes. De même, un kilomètre est égal à 1000 mètres, et non 1024 mètres.
Autrefois, on utilisait les termes kilo, méga et giga à la place de nos kibi, mebi et gibi, par abus de langage. Mais peu de personnes sont au courant de l'existence de ces nouvelles unités, et celles-ci sont rarement utilisées. Et cette confusion permet aux fabricants de disques durs de nous « arnaquer » : Ceux-ci donnent la capacité des disques durs qu'ils vendent en kilo, méga ou giga octets : l’acheteur croit implicitement avoir une capacité exprimée en kibi, mébi ou gibi octets, et se retrouve avec un disque dur qui contient moins de mémoire que prévu.
===Lecture et écriture : mémoires ROM et RWM===
Pour simplifier grandement, on peut grossièrement classer les mémoires en deux types : les ''Read Only Memory'' et les ''Read Write Memory'', aussi appelées mémoires ROM et mémoires RWM. Pour les '''mémoires ROM''', on ne peut pas modifier leur contenu. On peut y récupérer une donnée ou une instruction : on dit qu'on y accède en lecture. Mais on ne peut pas modifier les données qu'elles contiennent. Quant aux '''mémoires RWM''', on peut y accéder en lecture (récupérer une donnée stockée en mémoire), mais aussi en écriture : on peut stocker une donnée dans la mémoire, ou modifier une donnée existante.
Tout ordinateur contient au minimum une ROM et une RWM (souvent une mémoire RAM), les deux n'ont pas exactement le même rôle. Pour simplifier, la mémoire ROM mémorise le programme à exécuter, la mémoire RWM stocke des données. Il a existé des ordinateurs où la mémoire RWM était une mémoire magnétique, voire acoustique, mais ce n'est plus le cas de nos jours. Pour les ordinateurs modernes, la mémoire RWM est une mémoire électronique appelée la '''mémoire RAM'''. La mémoire RAM est utilisée pour stocker temporairement des données que le processeur doit manipuler. La mémoire RAM est donc une mémoire RWN qui a les caractéristiques suivantes :
* La mémoire RAM est une mémoire électronique, fabriquée avec des semi-conducteurs.
* La mémoire RAM s'efface complètement quand on coupe l'alimentation de l'ordinateur (on dit qu'elle est volatile).
* La mémoire RAM possède un temps d'accès approximativement constant aux données (quelle que soit l'adresse).
Et ce sont des caractéristiques que d'autres mémoires RWM n'ont pas :
* Une mémoire RWM peut être magnétique (mémoires à tore de ferrite), acoustique, basées sur des CRTs (tubes Wlliams).
* Une mémoire RWM peut ne pas être volatile et donc conserver les données écrites. L'exemple type étant les mémoires à tore de ferrite. Précisons cependant qu'il existe des prototypes de mémoires FERAM, MRAM et autres, qui sont des RAM non-volatiles.
* Une mémoire RWN n'a pas forcément un temps d'accès constant aux données, ce n'est pas garanti pour les mémoires RWM. Par exemple, les anciens tambours magnétiques des anciens ''mainframes'' étaient des mémoires RWM non-volatiles, utilisées comme stockage temporaire.
Outre le programme à exécuter, la mémoire ROM peut mémoriser des constantes, des données qui ne changent pas. Elles ne sont jamais modifiées et gardent la même valeur quoi qu'il se passe lors de l'exécution du programme. En conséquence, elles ne sont jamais accédées en écriture durant l'exécution du programme, ce qui fait que leur place est dans une mémoire ROM. La mémoire RWM est alors destinée aux données temporaires, qui changent ou sont modifiées lors de l'exécution du programme, et qui sont donc manipulées aussi bien en lecture et en écriture. La mémoire RWM mémorise alors les variables du programme à exécuter, qui sont des données que le programme va manipuler. Pour les systèmes les plus simples, la mémoire RWM ne sert à rien de plus.
[[File:Espaces d'adressage sur une archi harvard modifiée.png|centre|vignette|upright=2.5|Espaces d'adressage sur une archi harvard modifiée]]
Pour donner un exemple de données stockées en ROM, on peut prendre l'exemple des anciennes consoles de jeu 8 et 16 bits. Les jeux vidéos sur ces consoles étaient placés dans des cartouches de jeu, précisément dans une mémoire ROM à l'intérieur de la cartouche de jeu. La ROM mémorisait non seulement le code du jeu, le programme du jeu vidéo, mais aussi les niveaux et les ''sprites'' et autres données graphiques.
Une conséquence est que les consoles 8/16 bits n'avaient pas besoin de beaucoup de RAM, comparé aux ordinateurs de l'époque, vu qu'une grande partie des données utiles étaient dans une ROM directement accessible par le processeur. À l'opposé, les micro-ordinateurs devaient copier les données d'un jeu depuis une disquette dans la mémoire RAM, ce qui demandait d'avoir plus de RAM. Le passage au support CD sur les consoles 32 bits a eu la même conséquence. Le processeur ne pouvant pas lire directement le CD à sa guise, il fallait copier les données du CD en RAM. D'où l'apparition de temps de chargement assez longs, inexistants sur support cartouche.
===L'adressage mémoire===
Sur une mémoire RAM ou ROM, on ne peut lire ou écrire qu'une case mémoire, qu'un registre à la fois : une lecture ou écriture ne peut lire ou modifier qu'une seule case mémoire. Techniquement, le processeur doit préciser à quel case mémoire il veut accéder à chaque lecture/écriture. Pour cela, chaque case mémoire se voit attribuer un nombre binaire unique, l''''adresse''', qui va permettre de le sélectionner et de l'identifier celle-ci parmi toutes les autres. En fait, on peut comparer une adresse à un numéro de téléphone (ou à une adresse d'appartement) : chacun de vos correspondants a un numéro de téléphone et vous savez que pour appeler telle personne, vous devez composer tel numéro. Les adresses mémoires en sont l'équivalent pour les cases mémoire.
[[File:Adressage mémoire.png|centre|vignette|upright=2|Exemple : on demande à la mémoire de sélectionner la case mémoire d'adresse 1002 et on récupère son contenu (ici, 17).]]
L'adresse mémoire est générée par le processeur. Le processeur peut parfaitement calculer des adresses, en extraire du programme qu'il exécute, et bien d'autres choses. Nous détaillerons d'ailleurs les mécanismes pour dans les chapitres portant sur les modes d'adressage du processeur. Mais pour le moment, nous avons juste besoin de savoir que c'est le processeur qui envoie des adresses aux mémoires RAM et ROM.
Les adresses générées par le processeur sont alors envoyées à la RAM ou la ROM via une connexion dédiée, un ensemble de fils qui connecte le processeur à la mémoire : le '''bus d'adresse mémoire'''. L'adresse sélectionne une case mémoire, le processeur peut alors récupérer la donnée dedans pour une lecture, écrire une donnée pour l'écriture. Pour cela, un second ensemble de fil connecte le processeur à la RAM/ROM, mais cette fois-ci pour échanger des données. Il s'agit du '''bus de données mémoire'''. Les deux sont souvent regroupés sous le terme de '''bus mémoire'''.
Un ordinateur contient toujours une RAM et une ROM, ce qui demande aux bus mémoire de s'adapter à la présence de deux mémoires. Il y a alors deux solutions, illustrées dans les deux schémas ci-dessous. Avec la première, il y a un seul bus mémoire partagé entre la RAM et la ROM, comme illustré ci-dessous. Une autre solution utilise deux bus séparés : un pour la RAM et un autre pour la ROM. Nous verrons les différences pratiques entre les deux à la fin du chapitre. Pour le moment, nous allons partir du principe qu'il y a un bus pour la mémoire ROM, et un autre bus pour la RAM.
[[File:CPT-System-Architecture-gapfill1-ANS.svg|centre|vignette|upright=2|Architecture avec une ROM et une RAM.]]
[[File:Réseau d'interconnexion avec un processeur au centre.png|centre|vignette|upright=2|Réseau d'interconnexion avec un processeur au centre]]
===L'alignement mémoire : introduction===
Plus haut, nous avions dit qu'il y a une adresse par case mémoire, chaque case mémoire contenant un ou plusieurs octets. Mais les processeurs modernes partent du principe que la mémoire a un octet par adresse, pas plus. Et ce même si la mémoire reliée au processeur utilise des cases mémoires de 2, 3, 4 octets ou plus. D'ailleurs, la majorité des mémoires RAM actuelle a des cases mémoires de 64 bits, soit 8 octets par case mémoire. Les raisons à cela sont multiple, mais nous les verrons en détail dans le chapitre sur l'alignement mémoire. Toujours est-il qu'il faut distinguer les '''adresses mémoire''' et les '''adresses d'octet''' gérées par le processeur.
Le processeur génère des adresses d'octet, qui permettent de sélectionner un octet bien précis. L'adresse d'octet permet de sélectionner un octet parmi tous les autres. Mais la mémoire ne comprend pas directement cette adresse d'octet. Heureusement, l'octet en question est dans une case mémoire bien précise, qui a elle-même une adresse mémoire bien précise. L'adresse d'octet est alors convertie en une adresse mémoire, qui sélectionne la case mémoire adéquate, celle qui contient l'octet voulu. La case mémoire entière est lue, puis le processeur ne récupère que les données adéquates. Pour cela, des circuits d'alignement mémoire se chargent de faire la conversion entre adresses du processeur et adresse mémoire. Nous verrons cela dans le détail dans le chapitre sur l'alignement mémoire.
Il existe des mémoires qui n'utilisent pas d'adresses mémoire, mais passons : ce sera pour la suite du cours.
==Le processeur==
Dans les ordinateurs, l'unité de traitement porte le nom de '''processeur''', ou encore de '''''Central Processing Unit''''', abrévié en CPU. Le rôle principal du processeur est de faire des calculs. La plupart des processeurs actuels supportent au minimum l'addition, la soustraction et la multiplication. Quelques processeurs ne gèrent pas la division, qui est une opération très gourmande en circuit, peu utilisée, très lente. Il arrive que des processeurs très peu performants ne gèrent pas la multiplication, mais c'est assez rare.
Un processeur ne fait pas que des calculs. Tout processeur est conçu pour effectuer un nombre limité d'opérations bien précises, comme des calculs, des échanges de données avec la mémoire, etc. Ces opérations sont appelées des '''instructions'''. Les plus intuitives sont les '''instructions arithmétiques''', qui font des calculs, comme l'addition, la soustraction, la multiplication, la division. Mais il y a aussi des '''instructions d'accès mémoire''', qui échangent des données entre la mémoire RAM et le processeur. Les autres instructions ne sont pas très intuitives, aussi passons-les sous silence pour le moment, tout deviendra plus clair dans les chapitres sur le processeur.
===Le processeur exécute un programme, une suite d'instructions===
Tout processeur est conçu pour exécuter une suite d'instructions dans l'ordre demandé, cette suite s'appelant un '''programme'''. Ce que fait le processeur est défini par la suite d'instructions qu'il exécute, par le programme qu'on lui demande de faire. Les instructions sont exécutées dans un ordre bien précis, les unes après les autres. L'ordre en question est décidé par le programmeur. La totalité des logiciels présents sur un ordinateur sont des programmes comme les autres.
Le programme à exécuter est stockée dans la mémoire de l'ordinateur. C'est ainsi que l'ordinateur est rendu programmable : modifier le contenu de la mémoire permet de changer le programme exécuté. Mine de rien, cette idée de stocker le programme en mémoire est ce qui a fait que l’informatique est ce qu'elle est aujourd’hui. C'est la définition même d'ordinateur : appareil programmable qui stocke son programme dans une mémoire modifiable.
Une instruction est codée comme les données : sous la forme de suites de bits. Telle suite de bit indique qu'il faut faire une addition, telle autre demande de faire une soustraction, etc. Pour simplifier, nous allons supposer qu'il y a une instruction par adresse mémoire. Sur la grosse majorité des ordinateurs, les instructions sont placées les unes à la suite des autres dans l'ordre où elles doivent être exécutées. Un programme informatique n'est donc qu'une vulgaire suite d'instructions stockée quelque part dans la mémoire de l'ordinateur.
{|class="wikitable"
|+ Exemple de programme informatique
|-
! Adresse
! Instruction
|-
! 0
| Copier le contenu de l'adresse 0F05 dans le registre numéro 5
|-
! 1
| Charger le contenu de l'adresse 0555 dans le registre numéro 4
|-
! 2
| Additionner ces deux nombres
|-
! 3
| Charger le contenu de l'adresse 0555
|-
! 4
| Faire en XOR avec le résultat antérieur
|-
! ...
| ...
|-
! 5464
| Instruction d'arrêt
|}
Pour exécuter une suite d'instructions dans le bon ordre, le processeur détermine à chaque cycle quelle est la prochaine instruction à exécuter. Pour cela, le processeur mémorise l'adresse de l'instruction en cours dans un registre : le '''Program Counter'''. Je rappelle que des instructions consécutives sont dans des adresses consécutives. Pour passer à la prochaine instruction, il suffit donc d'incrémenter le ''program counter''.
: Si une instruction prend plusieurs octets, plusieurs adresses, il suffit de l'incrémenter du nombre d'octets/adresses.
D'autres processeurs font autrement : chaque instruction précise l'adresse de la suivante, directement dans la suite de bit représentant l'instruction en mémoire. Ces processeurs n'ont pas besoin de calculer une adresse qui leur est fournie sur un plateau d'argent. Sur des processeurs aussi bizarres, pas besoin de stocker les instructions en mémoire dans l'ordre dans lesquelles elles sont censées être exécutées. Mais ces processeurs sont très très rares et peuvent être considérés comme des exceptions à la règle.
Nous venons de voir qu'un processeur contient un registre appelé le ''program counter''. Mais il n'est pas le seul. Pour pouvoir fonctionner, tout processeur doit mémoriser un certain nombre d’informations nécessaires à son fonctionnement, qui sont mémorisées dans des '''registres de contrôle'''. La plupart ont des noms assez barbares (registre d'état, ''program counter'') et nous ne pouvons pas en parler à ce moment du cours. Nous les verrons en temps voulu, mais il est important de préciser qu'ils existent.
===L'intérieur d'un processeur===
Fort de ce que nous savons, nous pouvons expliquer ce qu'il y a à l'intérieur d'un processeur. Le premier point est qu'un processeur fait des calculs, ce qui implique qu'il contient des circuits de calcul. Ils sont regroupés dans une ou plusieurs '''unités de calcul'''. Nous avons déjà vu comment fabriquer une unité de calcul simple, dans un chapitre dédié, et c'est la même qui est présente dans un processeur. Du moins dans les grandes lignes, les circuits des processeurs modernes étant particulièrement optimisés. Il en est de même pour les autres circuits de calcul comme ceux pour les multiplications/division/autres.
Si le processeur fait des calculs, qu'en est-il des opérandes ? Et où sont mémorisés les résultats des opérations ? Pour cela, le processeur incorpore des '''registres généraux''', qui mémorisent les opérandes et résultats des instructions. Ils mémorisent des données, contrairement aux registres de contrôle mentionnés plus haut. Le nombre de registres généraux dépend grandement du processeur. Les tout premiers processeurs se débrouillaient avec un seul registre, mais les processeurs actuels utilisent plusieurs registres, pour mémoriser plusieurs opérandes/résultats. Mais la présence de registres est source de pas mal de petites complications que nous expliquerons dans les chapitres sur le processeur.
Le processeur contient aussi de quoi communiquer avec la mémoire RAM. Il y a une unité dédiée, un circuit dédié pour, appelé l'''unité mémoire'''. Elle permet d'échanger les données entre la RAM et les registres, mais aussi de simplement lire les instructions à exécuter.
Le processeur contient enfin un circuit pour interpréter les instructions, appelé l''''unité de contrôle'''. Elle lit les instructions depuis la mémoire, interprète la suite de bit associée, et commande le reste du processeur pour qu'il exécute l'instruction. Ses fonctions sont assez variées, mais nous allons simplifier en disant qu'elle configure l'unité de calcul et les registres pour faire le bon calcul. Son rôle est d'analyser la suite de bit qui constitue l'instruction, et d'en déduire quelle opération effectuer. Elle gère aussi l'accès à la mémoire RAM, et notamment ce qui est envoyé sur son bus d'adresse.
: Dans ce qui suit, on suppose que le ''program counter'' fait partie de l'unité de contrôle.
Pour résumer, un processeur contient une unité de calcul, des registres et une interface avec la mémoire RAM. Le tout est interconnecté, afin de pouvoir échanger des données. L’ensemble forme le '''chemin de données''', nom qui trahit le fait que c'est là que les données se déplacent et sont traitées. Il faut aussi ajouter l'unité de contrôle pour commander le tout. Elle lit les instructions en mémoire, puis commande le chemin de données pour que l'instruction soit exécutée correctement.
[[File:Microarchitecture d'un processeur.png|centre|vignette|upright=2|Microarchitecture d'un processeur]]
Un processeur parait donc assez simple expliqué comme ça, mais il y a de nombreuses subtilités. L'une d'entre elle est liée aux registres, et notamment à la manière dont on les utilise. Pour expliquer ces subtilités, nous allons voir comment les premiers processeurs fonctionnaient, avant de passer aux processeurs un peu plus modernes. Les tout premiers processeurs n'utilisaient qu'un seul registre, ce qui fait que l'utilisation des registres était très simple. Le passage à plusieurs registres a complexifié le tout.
===D'où viennent les adresses ?===
Il est maintenant temps de répondre à une question qui s'était posée dans la section sur l'adressage : d'où viennent les adresses envoyées à la mémoire ? Pour ce qui est des adresses des instructions, on connait déjà la réponse : c'est le ''program counter'' qui gère tout. Mais pour les données, il y a deux possibilités, qui correspondent à deux types de données : les données statiques et les données dynamiques.
Les '''données statiques''' sont les plus simples : elles existent durant toute la durée de vie du programme. Tant que celui-ci s'exécute, il aura besoin de ces données. En conséquence, il leur réserve une place en mémoire, qui est toujours la même. La donnée se situe donc à une adresse bien précise, qui ne change jamais. Attention cependant : les données peuvent être modifiées, changer de valeur. Le programme écrit dans les donnée statiques, c'est même assez fréquent. Ce sont ne sont pas forcément des données constantes !
Pour les données statiques, elles sont toujours placées à la même adresse mémoire. Pour le dire autrement, l'adresse mémoire est une constante, qui ne change pas. L'adresse est connue avant d’exécuter le programme, le programme a été codé pour utiliser cette adresse pour telle donnée, on a réservé une adresse pour la donnée voulue. Et même si vous quittez le programme et vous le relancez plusieurs jours après, l'adresse mémoire sera la même avant et après.
Et cela permet d'utiliser l''''adressage direct'''. L'idée est que les instructions précisent donc l'adresse à lire ou écrire. Pour cela, l'adresse est intégrée dans l’instruction elle-même. Pour rappel, l'instruction est codée par une suite de bit en mémoire RAM/ROM, dont certains précisent l'opération à faire, les autres servant à autre chose. L'idée est que certains bits précisent l'adresse mémoire de la donnée à lire. Les instructions sont donc du genre :
* ''LOAD 56 -> R9'' - lit l'adresse numéro 56 et copie son contenu dans le registre numéro 9 ;
* ''STORE R5 , adress 99'', copie le registre R5 dans l'adresse 99 ;
* ''ADD R5 , adress 209'' : additionne le registre R5 avec le contenu de l'adresse 209.
S'il existe des données statiques, c'est signe qu'il existe des '''données dynamiques'''. Ces dernières sont des données qui sont créées ou détruites selon les besoins. Pour comprendre d'où viennent ces données dynamiques, prenons le cas d'une personne qui écrit du texte sur un ordinateur. Le texte qu'il écrit est mémorisé dans la RAM de l’ordinateur, puis est sauvegardé sur le disque dur quand il sauvegarde son document. Au fur et à mesure qu'il écrit du texte, la RAM utilisée par ce texte augmente. Donc, ce texte est une donnée dynamique, dont la taille varie dans le temps.
Pour gérer des données dynamiques, la plupart des systèmes d'exploitation incorporent des fonctionnalités d''''allocation mémoire'''. Derrière ce nom barbare, se cache quelque chose de simple : les programmes peuvent réclamer de la mémoire au système d'exploitation, pour y placer les données qu'ils souhaitent. Les langages de programmation bas niveau supportent des fonctions comme malloc(), qui permettent de demander un bloc de mémoire de N octets à l'OS, qui doit alors accommoder la demande. De même, un programme peut libérer de la mémoire qu'il n'utilise plus avec des fonctions comme free().
Avec l'allocation mémoire, les données n'ont pas de place fixe en mémoire. Leur adresse mémoire peut varier d'une exécution du programme à l'autre. Pire que ça : les données peuvent être déplacées dans la mémoire RAM, si besoin. En clair : l'adresse est déterminée lors de l'exécution du programme. L'adresse est alors soit calculée, soit lue depuis la mémoire RAM, soit déterminée autrement. Toujours est-il qu'elle se retrouve dans un registre général. Pour gérer ces adresse variables, les processeurs utilisent l''''adressage indirect'''. L'adressage indirect permet d'utiliser une adresse qui est dans un registre de données. L'adresse en question peut être envoyée à la mémoire RAM sans problème, le processeur peut automatiquement connecter le registre adéquat sur le bus d'adresse.
Au tout début de l'informatique, les processeurs ne supportaient que l'adressage direct, pas plus. L'adressage indirect n'était tout simplement pas possible. Avec l'adressage direct, l'adresse à lire est extraite directement des instructions, par l'unité de contrôle. Le bus d'adresse de la RAM est alors relié directement à l'unité de contrôle, le bus de données est relié aux registres.
[[File:Architecture Harvard avec adressage direct uniquement.png|centre|vignette|upright=2|Architecture Harvard avec adressage direct uniquement]]
Mais dès les années 70, l'adressage indirect est apparu, rendant la programmation bien plus simple. Mais cela a demandé quelques adaptation au niveau du processeur. Avec l'adressage indirect, le bus d'adresse est connecté aux registres. Il a donc fallu rajouter un multiplexeur pour que le processeur décide de relier le bus d'adresse soit aux registres, soit à l'unité de contrôle.
===Un ordinateur peut avoir plusieurs processeurs===
La plupart des ordinateurs n'ont qu'un seul processeur, ce qui fait qu'on désigne avec le terme d''''ordinateurs mono-processeur'''. Mais il a existé (et existe encore) des '''ordinateurs multi-processeurs''', avec plusieurs processeurs sur la même carte mère. L'idée était de gagner en performance : deux processeurs permettent de faire deux fois plus de calcul qu'un seul, quatre permettent d'en faire quatre fois plus, etc. C'est très courant sur les supercalculateurs, des ordinateurs très puissants conçus pour du calcul industriel ou scientifique, mais aussi sur les serveurs ! Dans le cas le plus courant, ils utilisent plusieurs processeurs identiques : on utilise deux processeurs Core i3 de même modèle, ou quatre Pentium 3, etc.
Pour utiliser plusieurs processeurs, les programmes doivent être adaptés. Pour cela, il y a plusieurs possibilités :
* Une première possibilité, assez intuitive, est d’exécuter des programmes différents sur des processeurs différents. Par exemple, on exécute le navigateur web sur un processeur, le lecteur vidéo sur un autre, etc.
* La seconde option est de créer des programmes spéciaux, qui utilisent plusieurs processeurs. Ils répartissent les calculs à faire sur les différents processeurs. Un exemple est la lecture d'une vidéo sur le web : un processeur peut télécharger la vidéo pendant le visionnage et bufferiser celle-ci, un autre processeur peut décoder la vidéo, un autre décoder l'audio. De tels programmes restent des suites d'instructions, mais ils sont plus complexes que les programmes normaux, aussi nous les passons sous silence.
* La troisième option est d’exécuter le même programme sur les différents processeurs, mais chaque processeur traite son propre ensemble de données. Par exemple, pour un programme de rendu 3D, quatre processeurs peuvent s'occuper chacun d'une portion de l'image.
[[File:Architecture de Von Neumann Princeton multi processeurs.svg|centre|vignette|upright=2|Architecture de Von Neumann Princeton multi processeurs]]
De nos jours, les ordinateurs grand public les plus utilisés sont dans un cas intermédiaire, ils ne sont ni mono-, ni multi-processeur. Ils n'ont qu'un seul processeur, dans le sens où si on ouvre l'ordinateur et qu'on regarde la carte mère, il n'y a qu'un seul processeur. Mais ce processeur est en réalité assez similaire à un regroupement de plusieurs processeurs dans le même boitier. Il s'agit de '''processeurs multicœurs''', qui contiennent plusieurs cœurs, chaque cœur pouvant exécuter un programme tout seul.
La différence entre cœur et processeur est assez difficile à saisir, mais pour simplifier : un cœur est l'ensemble des circuits nécessaires pour exécuter un programme. Chaque cœur dispose de toute la machinerie électronique pour exécuter un programme, à savoir des circuits aux noms barbares comme : un séquenceur d'instruction, des registres, une unité de calcul. Par contre, certains circuits d'un processeur ne sont présents qu'en un seul exemplaire dans un processeur multicœur, comme les circuits de communication avec la mémoire ou les circuits d’interfaçage avec la carte mère.
Suivant le nombre de cœurs présents dans notre processeur, celui-ci sera appelé un processeur double-cœur (deux cœurs), quadruple-cœur (4 cœurs), octuple-cœur (8 cœurs), etc. Un processeur double-cœur est équivalent à avoir deux processeurs dans l'ordinateur, un processeur quadruple-cœur est équivalent à avoir quatre processeurs dans l'ordinateur, etc. Ces processeurs sont devenus la norme dans les ordinateurs grand public et les logiciels et systèmes d'exploitation se sont adaptés.
===Les coprocesseurs===
Quelques ordinateurs assez anciens disposaient de '''coprocesseurs''', des processeurs qui complémentaient un processeur principal. Les ordinateurs de ce type avaient un processeur principal, le '''CPU''', qui était secondé par un ou plusieurs coprocesseurs. Sauf exception, le CPU et le coprocesseur exécutent des programmes différents, ils travaillent en parallèle.
Les coprocesseurs les plus connus sont les '''coprocesseurs pour le rendu 2D/3D'''. Ils ont eu leur heure de gloire sur les anciennes consoles de jeux vidéo, comme Super Nintendo, la Playstation et autres consoles de cette génération ou antérieure. Ils s'occupaient respectivement de calculer les graphismes des jeux vidéos. De nos jours, ils ont été remplacés par des cartes graphiques, ou des ''Graphic Processing Units'', qui ne sont pas considérées comme des coprocesseurs.
Les '''coprocesseurs sonores''' sont une sorte d'ancêtre des cartes son, utilisés sur les anciennes consoles de jeux vidéo, comme La Nintendo 64, la Playstation et autres consoles antérieures. Ils s'occupaient respectivement de calculer tout ce qui a trait au son. Pour donner un exemple, on peut citer la console Neo-géo, qui disposait de deux processeurs travaillant en parallèle : un processeur principal, et un coprocesseur sonore. Le processeur principal était un Motorola 68000, alors que le coprocesseur sonore était un processeur Z80.
L'accès aux périphériques est quelque chose sur lequel nous passerons plusieurs chapitres dans ce cours. Mais sachez que l'accès aux périphériques peut demander pas mal de puissance de calculs. Le CPU principal peut faire ce genre de calculs par lui-même, mais il n'est pas rare qu'un '''coprocesseur d'IO''' soit dédié à l'accès aux périphériques. Un exemple assez récent est celui de la console de jeu Nintendo 3DS. Elle disposait d'un processeur principal de type ARM9, d'un coprocesseur pour les divisions qu'on abordera plus bas, et d'un second processeur ARM7. L'ARM 7 était utilisé comme coprocesseur d'I/O, ainsi que pour l'émulation de la console GBA.
[[File:Asmp 2.gif|centre|vignette|upright=2|Co-processeur pour l'accès aux entrées-sorties.]]
Les '''coprocesseurs arithmétiques''' sont un peu à part des autres. Ils sont spécialisés dans les calculs en virgule flottante. Ils étaient utilisés à une époque où les CPU ne géraient que des calculs entiers (en binaire ou en BCD). Un exemple est le coprocesseur flottant x87, complémentaire des premiers processeurs Intel x86. Il y a eu la même chose sur les processeurs Motorola 68000, avec deux coprocesseurs flottants appelés les Motorola 68881 et les Motorola 68882.
Un exemple récent de coprocesseur est celui utilisé sur la console de jeu Nintendo DS. La console utilisait deux processeurs, un ARM9 et un ARM7, qui ne pouvaient pas faire de division entière. Il s'agit pourtant d'opérations importantes dans le cas du rendu 3D, ce qui fait que les concepteurs de la console ont rajouté un coprocesseur spécialisé dans les divisions entières et les racines carrées. Le coprocesseur était adressable directement par le processeur, comme peuvent l'être la RAM ou les périphériques.
==Les entrées-sorties==
Tous les circuits vus précédemment traitent des données codées en binaire. Ceci dit, les données ne sortent pas de n'importe où : l'ordinateur contient des composants électroniques qui traduisent des informations venant de l’extérieur en nombres. Ces composants sont ce qu'on appelle des '''entrées'''. Par exemple, le clavier est une entrée : l'électronique du clavier attribue un nombre entier (''scancode'') à une touche, nombre qui sera communiqué à l’ordinateur lors de l'appui d'une touche. Pareil pour la souris : quand vous bougez la souris, celle-ci envoie des informations sur la position ou le mouvement du curseur, informations qui sont codées sous la forme de nombres. La carte son évoquée il y a quelques chapitres est bien sûr une entrée : elle est capable d'enregistrer un son, et de le restituer sous la forme de nombres.
S’il y a des entrées, on trouve aussi des '''sorties''', des composants électroniques qui transforment des nombres présents dans l'ordinateur en quelque chose d'utile. Ces sorties effectuent la traduction inverse de celle faite par les entrées : si les entrées convertissent une information en nombre, les sorties font l'inverse : là où les entrées encodent, les sorties décodent. Par exemple, un écran LCD est un circuit de sortie : il reçoit des informations, et les transforme en image affichée à l'écran. Même chose pour une imprimante : elle reçoit des documents texte encodés sous forme de nombres, et permet de les imprimer sur du papier. Et la carte son est aussi une sortie, vu qu'elle transforme les sons d'un fichier audio en tensions destinées à un haut-parleur : c'est à la fois une entrée, et une sortie.
Les '''entrées-sorties''' incluent toutes les entrées et sorties, et même certains composants qui sont les deux à la fois. Il s'agit d'un terme générique, qui regroupe des composants forts différents. Dans ce qui va suivre, nous allons parfois parler de périphériques au lieu d'entrées-sorties, mais les deux termes ne sont pas équivalents. Dans le détail, les entrées-sorties regroupent :
* Les '''périphériques''' sont les composants connectés sur l'unité centrale. Exemple : les claviers, souris, webcam, imprimantes, écrans, clés USB, disques durs externes, la Box internet, etc.
* Les '''cartes d'extension''', qui se connectent sur la carte mère via un connecteur, comme les cartes son ou les cartes graphiques.
* D'autres composants sont soudés à la carte mère mais sont techniquement des entrées-sorties : les cartes sons soudées sur les cartes mères actuelles, par exemple.
===L'interface avec le reste de l'ordinateur===
Les entrées-sorties sont très diverses, fonctionnent très différemment les unes des autres. Mais du point de vue du reste de l'ordinateur, les choses sont relativement standardisées. Du point de vue du processeur, les entrées-sorties sont juste des paquets de registres ! Tous les périphériques, toutes les entrées-sorties contiennent des '''registres d’interfaçage''', qui permettent de faire l'intermédiaire entre l'entrée/sortie et le reste de l'ordinateur. L'entrée/sortie est conçu pour réagir automatiquement quand on écrit dans ces registres.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage sont assez variés. Les plus évidents sont les '''registres de données''', qui permettent l'échange de données entre le processeur et les périphériques. Pour échanger des données avec l'entrée/sortie, le processeur a juste à lire ou écrire dans ces registres de données. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données. Si le processeur veut envoyer une donnée à une entrée/sortie, il a juste à écrire dans ces registres. Inversement, s'il veut lire une donnée, il a juste à lire le registre adéquat.
Mais le processeur ne fait pas que transmettre des données à l'entrée/sortie. Le processeur lui envoie aussi des « commandes », des valeurs numériques auxquelles l'entrée/sortie répond en effectuant un ensemble d'actions préprogrammées. En clair, ce sont l'équivalent des instructions du processeur, mais pour l'entrée/sortie. Par exemple, les commandes envoyées à une carte graphique peuvent être : affiche l'image présente à cette adresse mémoire, calcule le rendu 3D à partir des données présentes dans ta mémoire, etc. Pour recevoir les commandes, l'entrée/sortie contient des ''registres de commande'' qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande à l'entrée/sortie, il écrit la commande en question dans ce ou ces registres.
Enfin, beaucoup d'entrée/sortie ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état de l'entrée/sortie. Ils servent notamment à indiquer au processeur que l'entrée/sortie est disponible, qu'il est en train d’exécuter une commande, qu'il est occupé, qu'il y a un problème, qu'il y a une erreur de configuration, etc.
===Les adresses des registres d’interfaçage===
Les registres des périphériques sont identifiés par des adresses mémoires. Et les adresses sont conçues de façon à ce que les adresses des différentes entrées/sorties ne se marchent pas sur les pieds. Chaque entrée/sortie, chaque registre, chaque contrôleur a sa propre adresse. D'ordinaire, certains bits de l'adresse indiquent quel est le destinataire. Certains indiquent quel est l'entrée/sortie voulue, les restants indiquant le registre de destination.
Il existe deux organisations possibles pour les adresses des registres d’interfaçages. La première possibilité est de séparer les adresses pour les registres d’interfaçage et les adresses pour la mémoire. Le processeur doit avoir des instructions séparées pour gérer les périphériques et adresser la mémoire. Il a des instructions de lecture/écriture pour lire/écrire en mémoire, et d'autres pour lire/écrire les registres d’interfaçage. Sans cela, le processeur ne saurait pas si une adresse est destinée à un périphérique ou à la mémoire.
[[File:Espaces d'adressages séparés entre mémoire et périphérique.png|centre|vignette|upright=2.5|Espaces d'adressages séparés entre mémoire et périphérique]]
L'autre méthode mélange les adresses mémoire et des entrées-sorties. Si on prend par exemple un processeur de 16 bits, où les adresses font 16 bits, alors les 65536 adresses possibles seront découpées en deux portions : une partie ira adresser la RAM/ROM, l'autre les périphériques. On parle alors d''''entrées-sorties mappées en mémoire'''. L'avantage est que le processeur n'a pas besoin d'avoir des instructions séparées pour les deux.
[[File:IO mappées en mémoire.png|centre|vignette|upright=2.0|IO mappées en mémoire]]
Pour résumer, communiquer avec une entrée/sortie est similaire à ce qu'on a avec les mémoires. Il suffit de lire ou écrire dans des registres d’interfaçage, qui ont chacun une adresse mémoire. Le problème est que le système d'exploitation ne connaît pas toujours le fonctionnement d'une entrée/sortie : il faut installer un programme qui va s'exécuter quand on souhaite communiquer avec l'entrée/sortie, et qui s'occupera de tout ce qui est nécessaire pour le transfert des données, l'adressage du périphérique, etc. Ce petit programme est appelé un driver ou '''pilote de périphérique'''. La « programmation » périphérique est très simple : il suffit de savoir quoi mettre dans les registres, et c'est le pilote qui s'en charge.
==Les architectures Harvard et Von Neumann==
Après avoir vu le processeur, les mémoires et les entrées-sorties, voyons voir comment le tout est interconnecté. Tous les ordinateurs ne sont pas organisés de la même manière, pour ce qui est de leurs bus. Mais pour comprendre pourquoi, nous devons regarder qui communique avec qui, dans un ordinateur. Pour rappel, les données sont placées en mémoire RAM, alors que les instructions sont placées en mémoire ROM. Le processeur lit des instructions dans la mémoire ROM, il lit et écrit dans la mémoire RAM, et accède aux registres d’interfaçage des entrées-sorties. Il y a donc besoins de trois interconnexions : CPU-ROM, CPU-RAM et CPU-IO.
[[File:Réseau d'interconnexion avec un processeur au centre.png|centre|vignette|upright=2|Réseau d'interconnexion avec un processeur au centre]]
Il parait intéressant d'utiliser trois interconnexions, au minimum CPU-ROM, CPU-RAM et CPU-IO. Néanmoins, faire ainsi a de nombreux désavantages. Déjà, il faut pouvoir brancher tout ça sur le processeur. Et celui-ci n'a pas forcément assez de broches pour. Aussi, il est parfois préférable de mutualiser des bus, à savoir de connecter plusieurs composants sur un même bus. Par exemple, on peut mutualiser le bus pour la mémoire RAM et pour la mémoire ROM. Il faut dire que les deux bus sont des bus mémoire, avec un bus d'adresse, un bus de données, et surtout : des bus de commande similaires. Les mutualiser est alors très simple, et permet d'économiser pas mal de broches.
[[File:Réseau d'interconnexion avec un processeur au centre et une architecture Harvard.png|centre|vignette|upright=2|Réseau d'interconnexion avec un processeur au centre et une architecture Harvard]]
Cette mutualisation nous amène naturellement à parler de la distinction entre les architectures Harvard d'un côté et les architectures Von Neumann de l'autre. Elle est très liée au fait d'utiliser soit un bus mémoire unique, soit des bus séparés pour la ROM et la RAM. Voyons cela en détail.
===Les architectures Harvard et Von Neumann : des bus séparés ou unifiés===
Avec l''''architecture Harvard''', la mémoire ROM et la mémoire RAM sont reliées au processeur par deux bus séparés. Il y a un bus RAM pour la mémoire RAM, un bus ROM pour la mémoire ROM. L'avantage de cette architecture est qu'elle permet de charger une instruction et une donnée simultanément : une instruction chargée sur le bus relié à la mémoire programme, et une donnée chargée sur le bus relié à la mémoire de données. Et cela simplifie fortement la conception du processeur.
[[File:Harvard Architecture.png|centre|vignette|upright=2|Architecture Harvard, avec une ROM et une RAM séparées.]]
Avec l''''architecture Von Neumann''', mémoire ROM et mémoire RAM sont reliées au processeur par un bus unique. Le bus unique qui relie processeur, RAM et ROM, s'appelle le '''bus mémoire'''. Un défaut de ces architecture est qu'elles ne peuvent pas charger une instruction et une donnée en même temps. Et cela pose quelques problèmes pour la conception du processeur. Par contre, nous verrons dans ce qui suit qu'utiliser un bus mémoire partagé est bien plus flexible et permet des choses que les architectures Harvard ne peuvent pas faire.
[[File:Architecture Von Neumann, avec deux bus séparés.png|centre|vignette|upright=2|Architecture Von Neumann, avec deux bus séparés.]]
===Les architectures Harvard et Von Neumann : des espaces d'adressage séparés ou unifiés===
La distinction précédente se base sur les connexions entre RAM, ROM et processeur. Mais il existe une autre distinction, très liée, qui est souvent utilisée comme seconde définition des architectures Harvard/Von Neumann. Elle est liée aux adresses mémoire que le processeur peut gérer. Prenons un processeur 16 bits, par exemple, qui gère naturellement des adresses de 16 bits. Il peut gérer 2^16 adresses, soit 64 kibioctets de mémoire. L'ensemble de ces adresses est appelé un '''espace d'adressage'''. Mais comment cet espace d'adressage est utilisé pour adresser une RAM et une ROM ?
Sur les architectures Harvard, le processeur voit deux mémoires séparées avec leur lot d'adresses distinctes. Une même adresse peut donc correspondre soit à la mémoire ROM, soit à la mémoire RAM, suivant le bus utilisé. L'espace d'adressage est donc doublé, dupliqué, avec un pour la ROM, un autre pour la RAM. Rien d'étonnant à cela : il y a deux bus d'adresses, chacun correspondant à un espace d'adressage.
[[File:Vision de la mémoire par un processeur sur une architecture Harvard.png|centre|vignette|upright=2|Vision de la mémoire par un processeur sur une architecture Harvard.]]
Avec l'architecture Von Neumann, la RAM et la ROM doivent se partager les adresses mémoires disponibles. Il n'y a qu'un seul espace d'adressage qui est coupé en deux, avec une partie pour la ROM et une autre pour la RAM. Une adresse correspond soit à la mémoire RAM, soit à la mémoire ROM, mais pas aux deux. Typiquement, la mémoire ROM occupe une partie des adresses, la mémoire RAM utilise le reste. La répartition des adresses est réalisée par les circuits de décodage d'adresse mentionnés plus haut.
[[File:Vision de la mémoire par un processeur sur une architecture Von Neumann.png|centre|vignette|upright=2|Vision de la mémoire par un processeur sur une architecture Von Neumann.]]
Les '''architectures Harvard modifiées''' sont des intermédiaires entre architectures Harvard et architectures Von Neumann, bien qu'elles penchent bien plus du côté des architectures Harvard. Précisons que la terminologie n'est pas claire, beaucoup d'auteurs mettent des définitions différentes derrière ces deux termes. Mais dans ce cours, nous utiliserons une définition très stricte de ce qu'est une architecture Harvard modifiée.
Une architecture Harvard modifiée est une architecture Harvard, où le processeur peut lire des données constantes depuis la mémoire ROM. Nous avions vu plus haut que les mémoires ROM peuvent mémoriser, en plus d'un programme exécutable, des données constantes, qui ne varient pas. Les architectures Harvard pures ne permettent pas de lire des données de ce genre depuis la mémoire ROM, alors que les architectures Harvard modifiées le permettent.
Une architecture Harvard modifiée dispose d'une instruction pour lire les données en mémoire RWM, et d'une instruction pour lire des données en mémoire ROM. Il y a donc deux versions de l'instruction LOAD, qui copient la donnée dans un registre général, mais dont la source de la donnée est différente. Une autre possibilité, plus rare, est que une instruction de copie, qui copie une constante depuis la mémoire ROM vers la mémoire RAM. Le cas le plus commun est l'utilisation de deux instructions LOAD séparées.
[[File:Espaces d'adressage sur une archi harvard modifiée.png|centre|vignette|upright=2.5|Espaces d'adressage sur une archi harvard modifiée]]
Ceci étant dit, revenons à la distinction entre architecture Harvard et Von Neumann. Il faut noter que la RAM et la ROM n'ont pas forcément la même taille. Et ce que ce soit sur une architecture Harvard que sur une architecture Von Neumann, mais c'est plus facile à expliquer sur une architecture Harvard.
On peut par exemple imaginer une architecture Harvard qui utilise des adresses de 16 bits pour la ROM, et seulement 8 bits pour la RAM. Le résultat est qu'il peut adresser 64 kibioctets de ROM, mais seulement 256 octets de RAM. Les deux bus d'adresse sont alors de taille différente, l'un faisant 8 bits, l'autre 16. Quelques processeurs 8 bits étaient dans ce cas, comme on le verra dans le chapitre sur les CPU 8bits. Mais d'autres processeurs utilisent des valeurs différentes, avec par exemple des adresses de 16 bits pour la RAM, mais de 20 bits pour la ROM, ou inversement.
Sur une architecture Von Neumann, tout dépend de comment les adresses sont réparties. La solution la plus simple découpe l'espace d'adressage en deux parties égales, avec la RAM qui est dans la moitié basse (qui part de l'adresse 0 jusqu'à l'adresse au milieu), alors que la ROM est dans la moitié haute (entre l'adresse du milieu et l'adresse maximale). Mais ce n'est pas la seule possibilité, la limite entre RAM et ROM peut être mise n'importe où. Prenons par exemple un processeur 32 bits, capable de gérer 4 milliards d'adresse. Il est parfaitement possible de réserver 128 mébioctets de poids fort à la mémoire ROM, et de laisser le reste à la mémoire RAM.
===Le décodage d'adresse sur les architectures Von Neumann===
Pour résumer, les architectures Harvard et Von Neumann se distinguent sur deux points :
* L'accès à la RAM et à la ROM se font par des bus séparés sur l'architecture Harvard, sur le même bus avec l'architecture Von Neumann.
* Les adresses pour la mémoire ROM et la mémoire RAM sont séparées sur les architectures Harvard, partagées sur l’architecture Von Neumann.
Les architectures Von Neumann utilisent donc un seul bus pour connecter la RAM et la ROM au processeur. Mais cela ne parait pas intuitif : comment deux composants peuvent se connecter aux mêmes fils ? Parce que c'est ce qu'implique le fait de partager un bus. Si je prends une mémoire RAM et une mémoire ROM, toutes deux de 8 bits, elles seront connectées à un bus mémoire de 8 bits. Intuitivement, on se dit qu'il y aura des conflits, du genre : la RAM et la ROM vont accéder au bus en même temps, comment savoir si une adresse est destinée à la RAM ou la ROM, etc ?
Tous ces problèmes sont résolus avec une solution très simple : à chaque instant, seule une mémoire est connectée au bus. L'idée est que les mémoires sont connectées ou déconnectées du bus selon les besoins. Si le processeur veut envoyer lire une donnée en mémoire RAM, il déconnecte la mémoire ROM du bus. Et inversement, s'il veut lire une instruction, il déconnecte la RAM et connecte la ROM.
Pour cela, les mémoires RAM et ROM possèdent une entrée ''Chip Select'' ou ''Output Enable'', qui agit comme une sorte de bouton ON/OFF. Lorsqu'on met un 1 sur cette entrée, la mémoire se connectera au bus. Ses entrées et sorties fonctionneront normalement, elle pourra recevoir des adresses, envoyer ou recevoir des données, tout sera normal. Par contre, si on met un 0 sur cette entrée, la mémoire se "désactive", ses entrée-sorties ne répondent plus aux sollicitations extérieures. Pire que ça : elles sont électriquement déconnectées.
Au total, tout cela demande de gérer deux bit ''Chip Select''/''Output Enable'' : un pour la RAM, un pour la ROM. Et ces deux bits sont configurés pour chaque accès mémoire, pour chaque lecture ou écriture. Pour cela, un circuit de '''décodage d'adresse''' prend en entrée l'adresse mémoire à lire/écrire, et active/désactive les mémoires RAM/ROM selon les besoins. Il prend l'adresse et configure les bits ''Chip Select''/''Output Enable''.
[[File:Décodage d'adresse sur une architecture Von Neumann.png|centre|vignette|upright=2|Décodage d'adresse sur une architecture Von Neumann.]]
L'implémentation la plus simple réserve la moitié des adresses pour la RAM, l'autre moitié pour la ROM. Typiquement, la ROM prend la moitié basse, la RAM la moitié haute. Dans ce cas, activer/désactiver la RAM et la ROM se fait avec seulement le bit de poids fort de l'adresse. Si le bit de poids fort est à 1, alors on accède à la RAM et la ROM doit être désactivée. Mais si ce bit est à 0, alors on accède à la moitié basse et il faut désactiver la RAM.
Une remarque intéressante : le fait de séparer la mémoire en deux parts égales permet de simuler une architecture Harvard à partir d'une architecture Von Neumann. Par exemple, le tout premier processeur d'Intel, le 4004, était l'un de ceux là. La RAM et la ROM sont reliés au même bus, et il y a donc un unique espace d'adressage, qui est séparé en deux parties égales. Le truc est que le processeur traite les deux parties égales comme deux espaces d'adressage séparés. Le processeur se débrouille pour cacher le fait qu'il y a un espace d'adressage unique coupé en deux, ce qui fait que les programmeurs voient bien deux espaces d'adressages distincts.
[[File:Décodage d'adresse sur une architecture Von Neumann basique.png|centre|vignette|upright=2|Décodage d'adresse sur une architecture Von Neumann basique.]]
Pour résumer, quand une adresse est envoyée sur le bus, les deux mémoires vont la recevoir mais une seule va répondre et se connecter au bus. Le décodage d'adresse garantit que seule la mémoire adéquate réponde à un accès mémoire. Le décodage d'adresse est réalisé par la carte mère, par un composant dédié.
Le mécanisme peut être utilisé pour combiner plusieurs RAM en une seule, idem avec les ROM. Pour comprendre l'idée, je vais prendre l'exemple de l'IBM PC, un des tout premier PC existant. Nous étudierons ce PC dans une section dédiée, à la fin du chapitre, aussi je vais passer rapidement dessus. Tout ce que je vais faire est vous présenter la carte mère du PC, et vous demander de faire est de compter les mémoires ROM et mémoires RAM sur la carte mère :
[[File:IBM 5150 Motherboard.svg|centre|vignette|upright=3|Carte mère de l'IBM 5150, un modèle de l'IBM PC.]]
Si vous remarquerez qu'il y a 5 mémoires ROM et 8 à 32 mémoires RAM. Le fait est que le processeur voit les différentes mémoires ROM comme une seule mémoire ROM. Idem avec les mémoires RAM : elle font chacune 2 kibioctets, et l'ensemble est vu par le processeur comme une seule RAM de 16 à 64 kibioctets. Et cela grâce aux circuits de décodage d'adresse, qui sont situés en haut à droite de la carte mère.
Pour comprendre l'idée, prenons l'exemple d'un processeur 16 bits, capable de gérer 64 kibioctets de mémoire. L'espace d'adressage est découpé en quatre portions, de 16 kibioctets chacune. Une portion est réservée à une ROM de 16 kibioctet, les autres sont chacune réservée à une RAM de 16 kibioctet. Le décodage d'adresse sélectionne alors la mémoire adéquate en utilisant les deux bits de poids fort de l'adresse.
* S'ils valent 00, alors c'est la mémoire ROM qui est activée, connectée au bus.
* S'ils valent 01, alors c'est la première mémoire RAM qui est connectée au bus.
* S'ils valent 10, alors c'est la seconde mémoire RAM qui est connectée au bus.
* S'ils valent 11, alors c'est la troisième mémoire RAM qui est connectée au bus.
[[File:Décodage d'adresse sur une architecture Von Neumann, utilisant plusieurs RAM et une ROM.png|centre|vignette|upright=2|Décodage d'adresse sur une architecture Von Neumann, utilisant plusieurs RAM et une ROM]]
===L'impact sur la conception du processeur===
Plus haut, j'ai parlé d'un des avantages des architectures Harvard : elles peuvent lire une instruction en même temps qu'elles accèdent à une donnée. La donnée est lue/écrite en RAM, alors que l'instruction est lue en ROM. Et cela permet de simplifier l'intérieur du processeur. Pas de beaucoup, mais c'est déjà ça de pris. Voyons maintenant comment cela impacte l'intérieur du processeur. Tout ce dont vous avez à vous rappeler est la séparation entre chemin de données et unité de contrôle, et que les registres généraux sont dans le premier, le ''program counter'' dans la seconde.
Avec une architecture Harvard, les instructions et les données passent par des bus différent : bus ROM pour les instructions, bus RAM pour les données. L'intuition nous dit que le bus pour la mémoire ROM est connecté à l'unité de contrôle, alors que le bus pour la RAM est connecté au chemin de données. Et dans les grandes lignes, c'est vrai. La logique est imparable pour ce qui est des bus de données. Mais il y a une petite subtilité pour les bus d'adresse.
Pour comprendre comment le processeur exploite ces deux bus, voyons ce qui transite dessus. Pour la mémoire ROM, elle reçoit l'adresse de l'instruction à lire, elle renvoie l'instruction adéquate. Pour cela, le ''program counter'' est envoyé sur le bus d'adresse, l'instruction sur le bus de données. Pour la mémoire RAM, elle échange des données avec les registres généraux, les registres pour les données. Les adresses utilisées pour la RAM viennent elles soit du chemin de données, soit de l'unité de contrôle, tout dépend du mode d'adressage. Mais le ''program counter'' n'est pas impliqué.
[[File:Architecture Harvard - échanges de données.png|centre|vignette|upright=2|Architecture Harvard - échanges de données]]
Les architectures Harvard modifiées doivent cependant rajouter une connexion entre le bus ROM et les registres généraux. C'est nécessaire pour charger une donnée constante depuis la mémoire ROM. Rappelons que la donnée constante est copiée dans un registre général, donc dans le chemin de données.
[[File:Architecture Harvard modifiée - implémentation du processeur.png|centre|vignette|upright=2|Architecture Harvard modifiée - implémentation du processeur]]
Avec les architectures Von Neumann, il y a un seul bus qui est relié à la fois au chemin de données et à l'unité de contrôle. Si le processeur lit une instruction, le bus doit être relié à l'unité de contrôle. Par contre, s'il accède à une donnée, il doit être relié au chemin de données (le bus d'adresse peut éventuellement être connecté au séquenceur, si celui-ci fournit l'adresse à lire). Il faut donc utiliser un paquet de multiplexeurs et de démultiplexeurs pour faire la connexion au bon endroit.
[[File:Architecture Von Neumann - implémentation du processeur.png|centre|vignette|upright=2|Architecture Von Neumann - implémentation du processeur]]
Une instruction se fait en deux temps : on charge l'instruction depuis la mémoire ROM, puis on l'exécute. Avec une architecture Harvard, tout cela se fait en un seul cycle d'horloge, vu que charger la ROM et accéder aux données peut se faire en même temps. Pas avec les architectures Von Neumann, qui doivent libérer le bus mémoire après avoir chargé une instruction. Elles n'ont pas le choix : elles chargent l'instruction lors d'un premier cycle d'horloge, puis l'exécutent lors du second.
Pour cela, ils incorporent un registre appelé le '''registre d'instruction''', qui mémorise l'instruction chargée. L'instruction est copiée dans ce registre lors du premier cycle, puis est utilisée lors du second cycle. Le registre permet de ne pas oublier l’instruction entre les deux cycles. Le registre d'instruction est obligatoire sur les architectures Von Neumann. En comparaison, il est facultatif sur les architectures Harvard. Elles peuvent en avoir un, pour des raisons techniques, mais ce n'est pas obligatoire.
[[File:Registre d'instruction.png|centre|vignette|upright=2|Registre d'instruction.]]
===Les architectures Von Neumann sont plus flexibles===
Sur les architectures Harvard, le processeur sait faire la distinction entre programme et données. Les données sont stockées dans la mémoire RAM, le programme est stocké dans la mémoire ROM. Les deux sont séparés, accédés par le processeur sur des bus séparés, et c'est ce qui permet de faire la différence entre les deux. Il est impossible que le processeur exécute des données ou modifie le programme. Du moins, tant que la mémoire qui stocke le programme est bien une ROM.
Par contre, sur les architectures Von Neumann, il est impossible de distinguer programme et données, sauf en ajoutant des techniques de protection mémoire avancées. La raison est qu'il est impossible de faire la différence entre donnée et instruction, vu que rien ne ressemble plus à une suite de bits qu'une autre suite de bits. Et c'est à l'origine d'un des avantages majeur de l'architecture Von Neumann : il est possible que des programmes soient copiés dans la mémoire RWM et exécutés dans celle-ci.
Un cas d'utilisation familier est celui de votre ordinateur personnel. Le système d'exploitation et les autres logiciels sont copiés en mémoire RAM à chaque fois que vous les lancez. Mais cet exemple implique un disque dur, ce qui rend les choses plus compliquées que prévu. Un autre exemple serait la compilation de code à la volée, mais il ne sera pas très parlant.
Un exemple plus adapté serait celui où la ROM mémorise un programme compressée dans la mémoire ROM, qui est décompressé pour être exécuté en mémoire RAM. Le programme de décompression est stocké en mémoire ROM et est exécuté au lancement de l’ordinateur. Cette méthode permet d'utiliser une mémoire ROM très petite et très lente, tout en ayant un programme rapide (si la mémoire RWM est rapide).
Il est aussi possible de créer des programmes qui modifient leurs propres instructions : cela s'appelle du '''code auto-modifiant'''. Ce genre de choses servait autrefois sur des ordinateurs rudimentaires, au tout début de l'informatique. À l'époque, les adresses à lire/écrire devaient être écrites en dur dans le programme, dans les instructions exécutées. Pour gérer certaines fonctionnalités des langages de programmation qui ont besoin d'adresses modifiables, comme les tableaux, on devait corriger les adresses au besoin avec du code auto-modifiant. De nos jours, le code automodifiant est utilisée occasionnellement pour rendre un programme indétectable dans la mémoire (les virus informatiques utilisent beaucoup ce genre de procédés).
L'impossibilité de séparer données et instructions est à l'origine de problèmes assez fâcheux. Il est parfaitement possible que le processeur charge et exécute des données, qu'il prend par erreur pour des instructions. C'est le cas quand des pirates informatiques arrivent à exploiter des bugs. Il arrive que des pirates informatiques vous fournissent des données corrompues, qui contiennent un virus ou un programme malveillant est caché dans les données. Les bugs en question permettent d'exécuter ces données, donc virus. Pour éviter cela, le système d'exploitation peut marquer certaines zones de la mémoire comme non-exécutable, c’est-à-dire que le système d'exploitation interdit d’exécution de quoi que ce soit qui est dans cette zone. Mais ce n'est pas parfait.
Toujours est-il que tout cela est impossible sur les architectures Harvard. Et ce serait très limitant. Imaginez : pas possible de lancer un programme depuis le disque dur ou une clé USB, le programme doit impérativement être dans une mémoire ROM, pas de compilation à la volée, etc. Que des techniques très utilisées dans l'informatique moderne. Malgré ses défauts, les architectures Von Neumann ne sont pas les plus utilisées pour rien. Les architectures Harvard sont concrètement utilisées uniquement dans l'informatique embarquée, sur des microcontrôleurs très spécifiques.
==Le bus de communication avec les entrées-sorties==
Le processeur, la mémoire et les entrées-sorties sont connectées par un ou plusieurs '''bus de communication'''. Ce bus n'est rien d'autre qu'un ensemble de fils électriques sur lesquels on envoie des zéros ou des uns. Pour communiquer avec la mémoire, il y a trois prérequis qu'un bus doit respecter : pouvoir sélectionner la case mémoire (ou l'entrée-sortie) dont on a besoin, préciser à la mémoire s'il s'agit d'une lecture ou d'une écriture, et enfin pouvoir transférer la donnée. Pour cela, on doit donc avoir trois bus spécialisés, bien distincts, qu'on nommera le bus de commande, le bus d'adresse, et le bus de donnée.
* Le '''bus de données''', sur lequel s'échangent les données entre les composants.
* Le '''bus de commande''' pour configurer la mémoire et les entrées-sorties.
* Le '''bus d'adresse''', facultatif, permet de préciser quelle adresse mémoire il faut lire/écrire.
Chaque composant possède des entrées séparées pour le bus d'adresse, le bus de commande et le bus de données. Par exemple, une mémoire RAM possédera des entrées sur lesquelles brancher le bus d'adresse, d'autres sur lesquelles brancher le bus de commande, et des broches d'entrée-sortie pour le bus de données. Précisons cependant que le bus de commande n'est pas exactement le même entre des mémoires RAM/ROM et des entrées-sorties.
[[File:Bus general schematic.svg|centre|vignette|upright=2|Contenu d'un bus, généralités.]]
===Le réseau d'interconnexion : généralités===
Reprenons où nous nous étions arrêté. Avant de voir les architectures Harvard et Von Neumann, nous avions dit que le processeur, les mémoires et les entrées-sorties sont reliées entre eux par un réseau d'interconnexion. Nous venons de voir qu'il est possible de mutualiser certains bus, notamment celui de la mémoire RAM et celui de la mémoire ROM. Mais il est possible de faire la même chose pour les entrées-sorties. Là encore, il est possible de regrouper le bus mémoire avec les bus pour les entrées-sorties. Voyons ce que cela implique.
{|
|[[File:Réseau d'interconnexion avec un processeur au centre.png|centre|vignette|upright=2|Réseau d'interconnexion avec une architecture Harvard.]]
|[[File:Réseau d'interconnexion avec un processeur au centre et une architecture Harvard.png|centre|vignette|upright=2|Interconnexions d'une architecture Von Neumann.]]
|}
Avant de poursuivre, nous devons préciser quelque chose d'important. Sur les ordinateurs modernes, les entrées-sorties peuvent accéder à la mémoire RAM. Les ordinateurs modernes intègrent des techniques de '''''Direct Memory Access''''' (DMA) qui permettent aux entrées-sorties de lire ou d'écrire en mémoire RAM. Les transferts DMA se font sans intervention du processeur. Ils permettent de copier un bloc de plusieurs octets, dans deux sens : de la mémoire RAM vers une entrée-sortie, ou inversement. Le DMA demande d'ajouter un circuit dédié sur la carte mère : le contrôleur DMA. Il effectue la copie d'un paquet d'octets de la RAM vers l'entrée-sortie ou dans l'autre sens.
[[File:Réseau d'interconnexion avec un processeur au centre, et direct memory access.png|centre|vignette|upright=2|Réseau d'interconnexion avec un processeur au centre, et direct memory access]]
===Les bus systèmes===
La première solution utilise un bus unique, celui-ci est appelé le '''bus système''', aussi appelé ''backplane bus''. Le bus système est connecté à la mémoire RAM, la mémoire ROM, au processeur, et aux entrées-sorties. Tous les composants présents dans l'ordinateur sont connectés à ce bus, sans exception. De tels bus avaient pour avantage la simplicité. Le processeur n'est connecté qu'à un seul bus, ce qui utilise peu de broches et économise des fils. La mutualisation des bus est totale, le câblage est plus simple, la fabrication aussi.
[[File:Architecture minimale d'un ordinateur.png|centre|vignette|upright=2|Architecture minimale d'un ordinateur.]]
Un bus système contient un bus d'adresse, de données et de commande. Un bus système se marie bien avec des entrées-sorties mappées en mémoire. La conséquence est que le bus d'adresse ne sert pas que pour l'accès à la mémoire RAM/ROM, mais aussi pour l'accès aux entrées-sorties. Il y a moyen d'implémenter un système d'adresse séparés avec, mais c'est pas l'idéal.
[[File:Architecture Von Neumann avec les bus.png|centre|vignette|upright=2|Architecture Von Neumann avec les bus.]]
Un bus système n'a pas de limitations quant aux échanges de données. Le processeur peut communiquer directement avec les mémoires et les entrées-sorties, les entrées-sorties peuvent communiquer avec la mémoire RAM, etc. Notamment, un bus système peut implémenter le ''Direct Memory Access''. Il suffit juste de connecter un contrôleur DMA sur le bus système. Le contrôleur DMA est considéré comme une entrée-sortie, ses registres sont mappés en mémoire et sont donc accessibles directement par le processeur.
[[File:Bus système avec controleur DMA.png|centre|vignette|upright=2|Bus système avec contrôleur DMA.]]
Si on suit la définition à la lettre, un bus système est systématiquement une architecture Von Neumann, vu que la mémoire ROM et la mémoire RAM sont reliées sur le bus système. La conséquence est que les circuits de décodage d'adresse sont présents. Ils sont toujours sur la carte mère, et sont plus ou moins à côté du bus système. Cependant, le décodage d'adresse est parfois étendu pour tenir compte des entrées-sorties.
Les entrées-sorties soudées sur la carte mère ont elles aussi des entrées ''Chip Select'' ou quelque chose de similaire. Le décodage d'adresse peut alors les activer ou les désactiver suivant l'adresse envoyée sur le bus d'adresse. C'est ce qui arrive quand le processeur écrit dans un registre d’interfaçage : il envoie l'adresse de ce registre sur le bus d'adresse, le circuit de décodage d'adresse active seulement l'entrée-sortie associée. Il faut noter que ce n'est pas systématique, il existe des techniques pour se passer de décodage d'adresse. Mais nous en reparlerons dans le chapitre sur les bus de communication.
[[File:Chipselectfr.png|centre|vignette|upright=1.5|Exemple détaillé.]]
Les bus systèmes sont certes très simples, mais ils ont aussi des désavantages. Par exemple, il faut éviter que le processeur et les entrées-sorties se marchent sur les pieds, ils ne peuvent pas utiliser le bus en même temps. De tels conflits d'accès au bus système sont fréquents et ils réduisent la performance, comme on le verra dans le chapitre sur les bus. De plus, un bus système a le fâcheux désavantage de relier des composants allant à des vitesses très différentes : il arrivait fréquemment qu'un composant rapide doive attendre qu'un composant lent libère le bus. Le processeur était le composant le plus touché par ces temps d'attente.
Elle était utilisée sur les tout premiers ordinateurs, pour sa simplicité. Elle était parfaitement adaptée aux anciens composants, qui allaient tous à la même vitesse. De nos jours, les ordinateurs à haute performance ne l'utilisent plus trop, mais elle est encore utilisée sur certains systèmes embarqués, en informatique industrielle dans des systèmes très peu puissants.
===Les bus d'entrées-sorties===
Les bus systèmes ont de nombreux problèmes, ce qui fait que d'anciens ordinateurs faisaient autrement. À la place d'un bus système unique, ils utilisent un bus séparé pour les mémoires, et un autre séparé pour les entrées-sorties. Le bus spécialisé pour la mémoire est appelé le '''bus mémoire''', l'autre bus est appelé le '''bus d'entrées-sorties'''. Le bus mémoire est généralement relié à la fois à la mémoire RAM et à la mémoire ROM, les exceptions ne sont pas rares, cependant.
[[File:Bus mémoire séparé du bus pour les IO.png|centre|vignette|upright=2|Bus mémoire séparé du bus pour les IO]]
Les bus d'entrée-sorties peuvent être spécialisés et simplifiés. Par exemple, ils peuvent avoir un bus de commande différent de celui de la mémoire, qui utilise nettement moins de fils. Le bus d'adresse peut aussi être réduit, et utiliser des adresses plus courtes que celles du bus mémoire. Les bus de données peuvent aussi être de taille différentes. Il est ainsi possible d'avoir un bus mémoire capable de lire/écrire 64 bits à la fois, alors que la communication avec les entrées-sorties se fait octet par octet ! En général, les bus d'entrée-sortie sont assez petits, ils ont une taille de 8 ou 16 bits, même si le bus mémoire est plus grand. Cela permet de ne pas gaspiller trop de broches. Ajouter un bus d'entrée-sortie n'est donc pas très gourmand en broches et en fils.
: Il est en théorie possible d'avoir une fréquence différente pour les deux bus, avec un bus mémoire ultra-rapide et un bus pour les entrées-sorties est un bus moins rapide. Mais il faut que le processeur soit prévu pour, et c'est très rare.
Niveau performances, le processeur peut théoriquement accéder à la mémoire en attendant qu'une entrée/sortie réponde, mais il faut que le processeur soit prévu pour, et ce n'est pas de la tarte. Par contre, cela implique d'avoir des adresses séparées pour les registres d’interfaçage et la mémoire. En clair : pas d'entrée-sortie mappée en mémoire ! Un autre problème est que les entrées-sorties ne peuvent pas communiquer avec la mémoire directement, elles doivent passer par l'intermédiaire du processeur. En clair : pas de ''Direct Memory Access'' ! Les deux sont des défauts rédhibitoires pour les programmeurs système, notamment pour ceux qui codent les pilotes de périphériques.
Pour résumer, les défauts sont assez problématiques : pas d'entrées-sorties mappées en mémoire, pas de ''Direct Memory Access'', économie de broches limitée. Les deux premiers sont des défauts majeurs, qui font que de tels bus ne sont pas utilisés dans les ordinateurs modernes. À la place, ils utilisent une troisième solution, distincte des bus systèmes et des bus d'entrée-sorties.
===Les bus avec répartiteur===
Il existe une méthode intermédiaire, qui garde deux bus séparés pour la mémoire et les entrées-sorties, mais élimine les problèmes de brochage sur le processeur. L'idée est d'intercaler, entre le processeur et les deux bus, un '''circuit répartiteur'''. Il récupère tous les accès et distribue ceux-ci soit sur le bus mémoire, soit sur le bus des périphériques. Le ou les répartiteurs s'appellent aussi le '''''chipset''''' de la carte mère.
C'était ce qui était fait à l'époque des premiers Pentium. À l'époque, la puce de gestion du bus PCI faisait office de répartiteur. Elle mémorisait des plages mémoires entières, certaines étant attribuées à la RAM, les autres aux périphériques mappés en mémoire. Elles utilisaient ces plages pour faire la répartition.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus]]
Niveau adresses des registres d'interfacage, il est possible d'avoir soit des adresses unifiées avec les adresses mémoire, soit des adresses séparées.
L'usage d'un répartiteur ne pose pas de problèmes particuliers pour implémenter le DMA. La seule contrainte est que le contrôleur DMA soit intégré dans le répartiteur. Les échanges entre IO et mémoire passent par le répartiteur, qui fait le pont entre bus mémoire et bus des IO.
[[File:Implémentation du DMA avec un répartiteur.png|centre|vignette|upright=2|Implémentation du DMA avec un répartiteur]]
==Les microcontrôleurs et ''system on chip''==
Parfois, on décide de regrouper la mémoire, les bus, le CPU et les ports d'entrée-sortie dans un seul circuit intégré, un seul boitier. L'ensemble forme alors ce qu'on appelle un '''''System on Chip''''' (système sur une puce), abrévié en SoC. Le nom est assez explicite : un SoC comprend un système informatique complet sur une seule puce de silicium, microprocesseurs, mémoires et périphériques inclus. Ils incorporent aussi des ''timers'', des compteurs, et d'autres circuits très utiles.
[[File:ARMSoCBlockDiagram.svg|centre|vignette|upright=2|SoC basé sur un processeur ARM, avec des entrées-sorties typiques de celles d'un µ-contrôleur. Le support du bus CAN, d'Ethernet, du bus SPI, d'un circuit de PWM (génération de signaux spécifiques), de convertisseurs analogique-digital et inverse, sont typiques des µ-contrôleurs.]]
Le terme SoC regroupe des circuits imprimés assez variés, aux usages foncièrement différents et à la conception distincte. Les plus simples d’entre eux sont des microcontrôleurs, qui sont utilisés pour des applications à basse performance. Les plus complexes sont utilisés pour des applications qui demandent plus de puissance, que nous appellerons SoC haute performance.
La relation entre SoC et microcontrôleurs est assez compliquée à expliquer, la terminologie n'étant pas clairement établie. Il existe quelques cours/livres qui séparent les deux, d'autres qui pensent que les deux sont très liés. Dans ce cours, nous allons partir du principe que tous les systèmes qui regroupent processeur, mémoire et quelques périphériques/entrées-sorties sont des SoC. En suivant cette définition, les microcontrôleurs sont donc un cas particulier de SoC, .
===Les microcontrôleurs===
Les '''microcontrôleurs''' sont des composants utilisés dans l'embarqué ou d'informatique industrielle. Leur nom trahit leur rôle. Ils sont utilisés pour contrôler de l'électroménager, des chaines de fabrication dans une usine, des applications robotiques, les alarmes domestiques ou ebcore les voitures. De manière générale, on les trouve dans tous les systèmes dits embarqués et/ou temps réel. Ils ont besoin de s'interconnecter à un grand nombre de composants et intègrent pour cela un grand nombre d'entrée-sorties. Les microcontrôleurs sont généralement peu puissants et doivent consommer peu d'énergie/électricité.
Fait amusant, on en trouve dans certains périphériques informatiques. Par exemple, les anciens disques durs intégraient un microcontrôleur qui contrôlait plusieurs moteurs : les moteurs pour faire tourner les plateaux magnétiques et les moteurs pour déplacer les têtes de lecture/écriture. Autre exemple : les claviers d'ordinateurs, qui intègrent un microcontrôleur connecté aux touches. Celui-ci détecte quand les touches sont appuyées et qui communique avec l'ordinateur. Nous détaillerons ces deux exemples dans les chapitres dédiés aux périphériques et aux disques durs, tout deviendra plus clair à ce moment là. La majorité des périphériques ou des composants internes à un ordinateur contiennent des microcontrôleurs.
Un microcontrôleur tend à intégrer des entrées-sorties assez spécifiques, qu'on ne retrouve pas dans les SoC destinés au grand public. Un microcontrôleur est typiquement relié à un paquet de senseurs et son rôle est de commander des moteurs ou d'autres composants. Et les entrées-sorties intégrées sont adaptées à cette tâche. Par exemple, ils tendent à intégrer de nombreux convertisseurs numériques-analogiques pour gérer des senseurs. Ils intègrent aussi des circuits de génération de signaux PWM spécialisés pour commander des moteurs, le processeur peut gérer des calculs trigonométriques (utiles pour commander la rotation d'un moteur), etc.
[[File:C8051F340-9 A B C D.png|centre|vignette|upright=2|Exemple d'entrées-sorties intégrées à un microcontroleur.]]
En plus des entrées-sorties intégrées, les microcontrôleurs ont souvent des '''ports d'entrées-sorties''' banalisés, à savoir qu'on peut brancher n'importe quoi dessus. Il était possible de brancher un capteur de température, un moteur à commander, un port série, un port parallèle, un écran, un clavier, une souris, peu importe. Les ports font souvent un octet et ils sont généralement reliées directement ou indirectement au processeur. Le logiciel qui s'exécute sur le processeur décide quoi envoyer sur des broches et comment interprète ce qui est reçu dessus.
Les ports banalisés de ce type sont parfois appelés des '''GPIO''', abréviation de ''General Purpose Input/Output'', mais nous utiliserons le terme de ''port I/O''. Un port regroupe plusieurs broches qui peuvent être utilisés à volonté. C'est le logiciel qui s'exécute sur le processeur qui décide de la fonction de broches. Un port IO peut fonctionner soit en tant qu'entrée, soit en tant que sortie. Il est possible de changer de sens en cours de fonctionnement, pour passer d'une entrée à une sortie ou inversement.
[[File:Afficheurs7seg.png|centre|vignette|upright=2|Exemple d'utilisation d'un port I/O. le port est connecté à un afficheur LCD dit 7-segments. Les 8 entrées de cet afficheur sont connectées à un port I/O d'un octet.]]
===Les SoC haute performance===
Les SoC les plus performants sont actuellement utilisés dans les téléphones mobiles, tablettes, ''Netbook'', ''smartphones'', ou tout appareil informatique grand public qui ne doit pas prendre beaucoup de place. La petite taille de ces appareils fait qu'ils gagnent à regrouper toute leur électronique dans un circuit imprimé unique. Mais les contraintes font qu'ils doivent être assez puissants. Ils incorporent des processeurs assez puissants, surtout ceux des ''smartphones''. C'est absolument nécessaire pour faire tourner le système d'exploitation du téléphone et les applications installées dessus.
Niveau entrées-sorties, ils incorporent souvent des interfaces WIFI et cellulaires (4G/5G), des ports USB, des ports audio, et même des cartes graphiques pour les plus puissants d'entre eux. Les SoC incorporent des cartes graphiques pour gérer tout ce qui a trait à l'écran LCD/OLED, mais aussi pour gérer la caméra, voire le visionnage de vidéo (avec des décodeurs/encodeurs matériel). Par exemple, les SoC Tegra de NVIDIA incorporent une carte graphique, avec des interfaces HDMI et VGA, avec des décodeurs vidéo matériel H.264 & VC-1 gérant le 720p. Pour résumer, les périphériques sont adaptés à leur utilisation et sont donc foncièrement différents de ceux des microcontrôleurs.
[[File:SOMblk.png|centre|vignette|upright=3|Exemple de SoC.]]
Un point important est que les processeurs d'un SoC haute performance sont... performants. Ils sont le plus souvent des processeurs de marque ARM, qui sont différents de ceux utilisés dans les PC fixe/portables grand public qui sont eux de type x86. Nous verrons dans quelques chapitres en quoi consistent ces différences, quand nous parlerons des jeux d'instruction du processeur. Autrefois réservé au monde des PCs, les processeurs multicœurs deviennent de plus en plus fréquents pour les SoC de haute performance. Il n'est pas rare qu'un SoC incorpore plusieurs cœurs. Il arrive même qu'ils soient foncièrement différents, avec plusieurs cœurs d'architecture différente.
La frontière entre SoC haute performance et microcontrôleur est de plus en plus floue. De nombreux appareils du quotidien intègrent des SoC haute performance, d'autres des microcontrôleurs. Par exemple, les lecteurs CD/DVD/BR et certains trackers GPS intègrent un SoC ou des processeurs dont la performance est assez pêchue. À l'opposé, les systèmes domotiques intègrent souvent des microcontrôleurs simples. Malgré tout, les deux cas d'utilisation font que le SoC/microcontrôleur est connecté à un grand nombre d'entrées-sorties très divers, comme des capteurs, des écrans, des LEDs, etc.
[[File:GPS tracker Hardware Architecture.png|centre|vignette|upright=2|Hardware d'un tracker GPS.]]
==Étude de l'architecture de quelques consoles de jeu==
Après avoir vu la théorie, nous allons voir des exemples réels d'ordinateurs. Dans ce qui suit, nous allons voir des ordinateurs qui collent assez bien à l''''architecture de base''' vue plus haut, avec un CPU, une RAM et une ROM, quelques entrées-sorties. Tous les ordinateurs modernes, mais aussi dans les smartphones, les consoles de jeu et autres, utilisent une architecture grandement modifiée et améliorée, avec un grand nombre de périphériques, des disques durs/SSD, un grand nombre de mémoires différentes, etc.
Il pourrait sembler pertinent d’étudier des microcontrôleurs ou des ''System On Chip'', en premier lieu. Mais nous éviterons soigneusement de tels systèmes pour le moment. La raison est qu'ils ont un grand nombre d'entrées-sorties, qui sont peu familières. Attendez-vous à avoir près d'une vingtaine ou centaine d'entrée-sorties différentes pour de tels systèmes. Le tout est très complexe, bien trop pour un premier exemple.
À la place, nous allons voir précisément des exemples plus simples : les premiers PC, et des consoles de jeu 8 et 16 bits. Bien que ce soit des systèmes très simples, ils sont cependant plus complexes que l'architecture de base. Et leur avantages/désavantages sont un peu inverse l'un de l'autre. Si on devait résumer les différences, on aurait ceci :
* Les PC ont plus d'entrées-sorties que les consoles, bien que nettement moins que pour les microcontrôleurs/SoC.
* Les PC utilisent des disques durs, les consoles font avec soit des cartouches de jeu, soit des CD/DVD.
* Les PC utilisent des cartes électroniques séparées pour le son et l'écran, les consoles utilisent des circuits soudés sur la carte mère, qui sont souvent des co-processeurs.
* Les PC ont une mémoire ROM soudées sur la carte mère, les consoles 8 bits font sans.
Les PC et micro-ordinateurs ont plus d'entrées-sorties que les consoles. Même si on mets de côté les périphériques, ils ont aussi beaucoup d'entrées-sorties soudées sur la carte mère. En comparaison, les consoles de jeu 8/16 bits se débrouillent avec : une cartouche de jeu et une manette en entrée, une sortie vidéo et une sortie son.
Un autre point important est l'absence de disque dur ou de lecteur CD. La présence d'un disque dur ou d'un lecteur CD/DVD complexifie tout de suite l'architecture des PC. Il faut leur réserver un bus dédié ou les connecter à un bus système, ajouter des circuits sur la carte mère, etc. Et surtout, il faut expliquer comment l'ordinateur exécute des programmes, ce qui demande de parler de l'interaction avec le disque dur et la ROM du BIOS. Rien de tout cela sur les consoles de jeu 8 et 16 bits. Elles utilisent à la place une mémoire ROM, dans la cartouche de jeu, pour mémoriser le code du jeu. Pas besoin de parler des mémoires de stockage, on est beaucoup plus proche de l'architecture de base avec une ROM unique.
Autre différence : les PC utilisent des cartes électroniques à brancher sur la carte mère pour alimenter l'écran et les hauts-parleurs/casques, alors que les consoles de jeu utilisent des co-processeurs dédiés pour le son et les graphismes. Nous avons déjà expliqué ce que sont les co-processeurs plus haut, aussi les co-processeurs des consoles nous paraitrons familiers. On n'a pas à s’embêter à expliquer ce que sont les cartes d'extension, les bus associés et tout ce qui va avec, cela peut être retardé pour la section sur l'architecture des PC.
Par contre, n'allez pas croire que tout est rose avec les consoles 8/16 bits. Il y a quelques différences qui font qu'elles sont plus complexes qu'un PC sur certains points. La gestion de la cartouche de jeu est notamment un peu subtile à comprendre, bien que ce soit bien plus simple à comprendre qu'un système avec un disque dur. Les cartouches de jeu intègrent une mémoire ROM, pour mémoriser les données du jeu, voire son code. Et le processeur doit exécuter le code depuis cette mémoire ROM. La conséquence est que les consoles 8/16 bits utilisent une architecture Harvard, avec un bus relié à la cartouche pour lire les instructions. Mais si ce n'était que ça... Les cartouches mémorisent aussi les données pour les graphismes, ce qui fait que le co-processeur vidéo doit lui aussi lire la cartouche pour récupérer ces données...
===L'architecture de la TurboGraphX-16===
La console PC Engine, aussi appelée TurboGraphX, est une ancienne console 8 bits. Elle contient un processeur 65C02, 8 kibioctets de RAM, un port manettes, une carte son et une carte vidéo. La '''carte son''' est le composant qui s'occupe de commander les haut-parleurs et de gérer tout ce qui a rapport au son. La '''carte graphique''' est le composant qui est en charge de calculer les graphismes, tout ce qui s'affiche à l'écran. Sur cette console, les cartes son et graphique ne sont PAS des co-processeurs, ce sont des circuits électroniques dits fixes. C'est totalement différent de ce qu'on a sur les consoles modernes, aussi le préciser est important.
Bien que la carte graphique ne soit pas un processeur, elle a 64 kibioctets de RAM rien que pour elle. La RAM en question est séparée de la RAM normale, c'est un circuit intégré séparé. Et c'est un cas très fréquent, qui reviendra par la suite. La majeure partie des cartes graphiques dispose de leur propre '''mémoire vidéo''', totalement réservée à la carte graphique. La RAM vidéo est connectée à la carte graphique via un bus séparé. Le processeur est souvent connecté à ce bus, afin de pouvoir écrire des données dedans, mais ce n'est pas le cas ici.
[[File:Architecture de la PC Engine, aussi appelée TurboGrafx-16.png|centre|vignette|upright=2.5|Architecture de la PC Engine, aussi appelée TurboGrafx-16]]
L'architecture de la console était particulièrement simple. Le processeur était le centre de l'architecture, tout était connecté dessus. Il y a un bus pour la cartouche de jeu, un autre pour la RAM, un autre pour les manettes, un autre pour carte son, et un dernier pour la carte graphique. Le fait d'avoir un bus par composant est assez rare et ce n'est le cas ici que parce des conditions particulières sont remplies. Déjà, il y a peu d'entrée-sorties. Ensuite, les bus font tous 8 bits, vu que le processeur est un CPU 8 bits. Avec 5 connexions de 8 bits, le tout utilise 40 broches, ce qui est beaucoup, mais totalement gérable. Par contre, les choses changerons pour les autres consoles.
Au final, l'organisation des bus peut s'expliquer avec ce qu'on a vu dans la section sur les bus de communication. La console utilise une architecture Harvard, car la ROM et la RAM utilisent des bus différents. De plus, il y a des bus dédiés aux entrées-sorties, séparés des bus mémoire. Enfin, la carte graphique a droit à ses propres bus pour lire dans la cartouche et dans sa RAM vidéo dédiée.
===L'architecture de la console de jeu NES===
Maintenant, nous allons voir la console de Jeu Famicom, aussi appelée la NES en occident. Elle a une architecture centrée sur un processeur Ricoh 2A03, similaire au processeur 6502, un ancien processeur autrefois très utilisé et très populaire. Le processeur est associé à 2 KB de mémoire RAM.
Sur certaines cartouches, on trouve une RAM utilisée pour les sauvegardes, qui est adressée par le processeur directement. Première variation par rapport à l'architecture de la console précédente : l'ajout de la RAM pour les sauvegardes dans les cartouches.
Niveau carte graphique, une différence importante est que la carte graphique est connectée à la cartouche de jeu via un autre bus, afin de pouvoir lire les sprites et textures du jeu dans la cartouche.
[[File:Architecture de la NES.png|centre|vignette|upright=2.5|Architecture de la NES]]
La différence avec l'architecture précédente est que des bus ont été fusionnés. Comme dit plus haut, le système utilise une architecture Harvard, vu que la ROM est dans la cartouche, alors que la RAM est soudée à la carte mère. Par contre, la Famicon utilise un bus dédié aux entrées-sorties. Il est utilisé pour la carte son et la carte graphique, seules les manettes sont sur un bus à part. Ce qui fait qu'on devrait plutôt parler de bus de sorties, mais passons... L'essentiel est qu'on n'est plus tout à fait dans le cas de la console précédente, avec un bus par composant.
===L'architecture de la SNES===
L'architecture de la SNES est illustrée ci-dessous. Les changements pour le processeur et la RAM sont mineurs.La RAM a augmenté en taille et passe à 128 KB. Pareil pour la RAM de la carte vidéo, qui passe à 64 KB. Par contre, on remarque un changement complet au niveau des bus, de la carte graphique et de la carte son.
[[File:Architecture de la SNES.png|centre|vignette|upright=2|Architecture de la SNES]]
La console utilise un '''bus système unique''', sur lequel tout est connecté : ROM, RAM, entrées-sorties, etc. La seule exception est pour les manettes, qui sont encore connectées directement sur le processeur, via un bus séparé. La transition vers un bus système s'explique par le fait que la console est maintenant de 16 bits, ce qui fait que les bus doivent être plus larges. Le processeur adresse des mémoires RAM et ROM plus grandes, ce qui double la taille de leurs bus. De plus, les entrées-sorties aussi ont besoin d'un bus plus large. Le processeur n'ayant pas un nombre illimité de broches, la seule solution est de fusionner les bus en un seul bus système.
Un autre changement est que la carte graphique est maintenant composée de deux circuits séparés. Encore une fois, il ne s'agit pas de coprocesseurs, mais de circuits non-programmables. Par contre, la carte son est remplacée par deux coprocesseurs audio ! De plus, les deux processeurs sont connectés à une mémoire RAM dédiée de 64 KB, comme pour la carte graphique. L'un est un processeur 8 bits (le DSP), l'autre est un processeur 16 bits.
Un point très intéressant : certains jeux intégraient des coprocesseurs dans leurs cartouches de jeu ! Par exemple, les cartouches de Starfox et de Super Mario 2 contenait un coprocesseur Super FX, qui gérait des calculs de rendu 2D/3D. Le Cx4 faisait plus ou moins la même chose, il était spécialisé dans les calculs trigonométriques, et diverses opérations de rendu 2D/3D. En tout, il y a environ 16 coprocesseurs d'utiliser et on en trouve facilement la liste sur le net. La console était conçue pour, des pins sur les ports cartouches étaient prévues pour des fonctionnalités de cartouche annexes, dont ces coprocesseurs. Ces pins connectaient le coprocesseur au bus des entrées-sorties. Les coprocesseurs des cartouches de NES avaient souvent de la mémoire rien que pour eux, qui était intégrée dans la cartouche.
===L'architecture de la Megadrive et de la néo-géo===
Passons maintenant à la console de jeu Megadrive, une console 16 bits. Elle a une architecture similaire à celle de la néo-géo, une autre console bien plus puissante, sorti à peu près en même temps. Elles intègrent deux processeurs : un Motorola 68000 qui sert de processeur principal, un Z80 qui sert de processeur dédié à l'audio. Le Z80 et le Motorola 68000 étaient deux processeurs très populaires à l'époque. Le Z80 est une sorte de version améliorée de l'Intel 8088 utilisé sur les anciens PC et de nombreuses consoles utilisaient des Z80 comme processeur principal. Il était familier pour les programmeurs de l'époque, pour son cout réduit, sa bonne disponibilité, et bien d'autres avantages liés à sa production de masse.
Le Z80 est utilisé comme co-processeur audio. Il commande un synthétiseur sonore, et est relié à sa propre mémoire, distincte de la mémoire principale. Le MC68000 est le processeur principal et a une relation maitre-esclave avec le Z80 : le MC68000 envoie des commandes au Z80, mais la communication ne va pas dans l'autre sens.
Le Motorola 68000 était un processeur 16 bits, alors que le Z80 est un processeur 8 bits. Et cette différence fait que l'on ne peut pas connecter directement les deux sur le même bus, ou du moins pas facilement. La solution retenue est d'utiliser deux bus séparés : un bus de 16 bits connecté au 68000, un bus de 8 bits connecté au Z80. Le premier bus est un bus système sur lequel est connecté le 68000, 64 kibioctets de RAM, la cartouche de jeu, et la carte graphique. Le second bus est un bus de 8 bits, plus court, relié au Z80, à un synthétiseur sonore, et 8 kibioctets de RAM
Les deux bus sont connectés à un '''''chipset''''', un circuit répartiteur, qui fait le pont entre les deux bus. Les manettes sont connectées sur le ''chipset''. Il contient un registre de 8 bits, dans lequel le MC68000 peut écrire dedans à sa guise, le registre étant adressable par le processeur. Lorsque le MC68000 écrit une valeur dedans, cela déclenche l’exécution automatique d'un programme pré-déterminé sur le Z80.
: Pour ceux qui savent ce qu'est une interruption, les valeurs écrites dans ce registre sont des numéros d'interruption, qui indiquent quelle routine d'interruption exécuter.
[[File:Architecture de la Megadrive et de la Néogeo.png|centre|vignette|upright=2.5|Architecture de la Megadrive et de la Néogeo]]
Cet exemple nous montre que les bus systèmes sont certes très simples, mais aussi inflexibles. Ils fonctionnent bien quand les composants branchés dessus sont tous des composants 8 bits, ou sont tous de 16 bits, ou tous 32 bits. Mais dès qu'on mélange composants 8, 16, 32 ou 64 bits, les choses deviennent plus compliquées. Il est alors préférable d'utiliser des bus séparés, avec des répartiteurs pour faire le pont entre les différents bus. Et nous verrons que le problème s'est posé lui aussi sur les PC.
===L'architecture des anciennes consoles Playstation : beaucoup de co-processeurs===
Les consoles que nous venons d'aborder étaient des consoles 8 ou 16 bits. A partir des consoles 32 bits, leur architecture s'est rapprochée de celle des PC, avec un usage plus complexes de répartiteurs. La XBOX était très semblable à un PC : le processeur était un Pentium 3 modifié, la carte graphique était une Geforce 3 modifiée, les 64 mébioctets de RAM était la même mémoire DDR que celle des PC, le répartiteur secondaire était un ''chipset'' nForce de NVIDIA, etc. Mais les Playstation 1, 2 et 3 se distinguent de leur contemporains. Elles disposent de très nombreux co-processeurs, qui sont en plus très variés.
La Playstation 1 a été une des premières console à utiliser les CD-ROM comme support de stockage, en remplacement des cartouches. La conséquence est que la console contient une mémoire ROM, soudée à la carte mère, de 512 kibioctets. Elle contient aussi 2 mébioctets de RAM, une carte graphique avec 1 mébioctet de mémoire vidéo, un processeur, et de quoi gérer les périphériques. Il y a un co-processeur audio spécialisé, avec 512 kibioctets de RAM, ce qui nous est familier. Par contre, les autres co-processeurs ne le sont pas.
Déjà, le lecteur de CD-ROM est associé à des circuits sur la carte mère, il y a tout un sous-système dédié au lecteur de CD. Il y a un contrôleur qui sert d'interface avec le lecteur proprement dit, mais aussi deux co-processeurs audio et 32 kibioctets de RAM. Les co-processeurs audio servent à lire des CD sans trop utiliser le second co-processeur audio, ils lui servent de complément.
Ensuite, le processeur incorpore plusieurs cœurs, avec un cœur principal et plusieurs co-processeurs. Le premier est un co-processeur système, qui est utilisé pour gérer la mémoire cache intégrée au processeur, pour des fonctionnalités appelées interruptions et exceptions, ainsi que pour configurer le processeur. Le second est un co-processeur arithmétique spécialisé dans les calculs en virgule flottante, très importants pour le rendu 3D. Enfin, il y a un décodeur vidéo, qui n'est pas un co-processeur, mais un circuit non-programmable, spécialisé dans le décodage vidéo. De nos jours, ce circuit aurait été intégré dans la carte graphique, mais il était intégré dans le processeur sur la Playstation 2.
Pour le reste, le processeur est la figure centrale de la console. Il est connecté à 4 bus : un pour la RAM, un pour la carte graphique, un pour les manettes, un autre pour le reste. Le dernier bus est connecté au système audio et au système pour le lecteur CD. Ce serait un bus d'entrée-sortie, s'il n'était pas connecté à la mémoire ROM. Vous avez bien lu : la mémoire ROM est reliée au bus d'entrée-sortie.
[[File:Architecture de la Playstation.png|centre|vignette|upright=2.5|Architecture de la Playstation]]
La Playstation 2 est composé d'un processeur, couplé à 32 Mébioctets de RAM, et d'un paquet de co-processeurs. Plus de co-processeurs que la PS1. Le processeur principal n'est pas la même que celui de la PS1, mais il a une architecture similaire. Il intègre un décodeur vidéo sur le même circuit intégré, ainsi que deux co-processeur. Les co-processeurs ne sont cependant pas les mêmes.
Le co-processeur système disparait et est remplacé par un second co-processeur arithmétique. Les deux co-processeurs arithmétiques sont spécialisés dans les nombres flottants, avec quelques différences entre les deux. Par exemple, le second co-processeur gérait des calculs trigonométriques, des exponentielles, des logarithmes, et d'autres fonctions complexes du genre ; mais pas le premier co-processeur. Ils sont reliés à 4 kibioctets de RAM pour le premier, 16 kibioctets de RAM pour le second ; qui sont intégrées dans le processeur et non-représentés dans le diagramme ci-dessous.
La PS2 intègre aussi un co-processeur d'entrées-sorties. Pour information, il s'agit du processeur principal de la Playstation 1, qui est ici utilisé différemment, suivant que l'on place un jeu PS1 ou PS1 dans la console. Si on met un jeu PS1, il est utilisé pour émuler la Playstation 1, afin de faire tourner le jeu PS1 sur la PS2. Si on met un jeu PS2, il est utilisé comme co-processeur d'entrée-sortie et fait l'interface entre CPU et entrées-sorties. Il est relié à 2 mébioctets de RAM, soit exactement la même quantité de mémoire que la Playstation 1.
Tous les périphériques sont connectés au co-processeur d'entrées-sortie. Pour cela, le co-processeur d'entrées-sortie est relié à deux bus dédiés aux périphériques. Le premier bus est relié aux manettes, aux ports USB et aux ports pour cartes mémoires. Le second bus est relié à la carte son, la carte réseau, le lecteur DVD, et un port PCMIA. Notons que la carte son intègre un co-processeur audio, qui n'est pas représenté dans le diagramme ci-dessous.
[[File:Playstation 2 architecture.png|centre|vignette|upright=2.5|Playstation 2 architecture]]
==L'architecture des PC et son évolution==
Après avoir vu les consoles, nous allons maintenant voir les anciens PC, des années 80 ou 90. Le tout premier PC était techniquement l''''IBM PC'''. Par la suite, de nombreux ordinateurs ont tenté de reproduire l'IBM PC originel, avec parfois quelques modifications mineures. De tels ordinateurs ''IBM PC compatibles'', ont été très nombreux, pour des raisons diverses. Le fait d'utiliser des composants banalisés, facilement disponibles, ainsi qu'une bonne documentation de l'IBM PC originel, a grandement aidé. Les IBM PC compatibles ont progressivement évolué pour donner les PC actuels. L'IBM PC compatible a donné naissance à de nombreux standards divers.
===L'IBM PC originel et l'IBM PC XT===
[[File:IBM PC XT 02.jpg|vignette|IBM PC XT.]]
Nous allons commencer par voir l'IBM PC originel, et son successeur : l'IBM Personal Computer XT. Nous les appelerons tous deux l'IBM PC. L'IBM PC utilisait un processeur Intel 8088, qui était un processeur 8 bits. Ils utilisaient un bus système unique, appelé le '''bus XT'''. Le bus système allait à 4.77 MHz, soit la même fréquence que le processeur. C'était un bus de 8 bits, ce qui collait parfaitement avec les processeurs 8 bits commercialisés par Intel à l'époque.
L'IBM PC comprenait une mémoire ROM avec de quoi faire fonctionner le PC. La ROM en question contenait un programme minimal, appelé le '''BIOS''', sans lequel le PC ne fonctionnait pas du tout. Il servait de base pour le système d'exploitation et MS-DOS ne fonctionnait pas sans elle. De nos jours, son rôle est plus limité : sans elle, le PC ne démarre pas. Mais nous détaillerons cela dans le prochain chapitre.
En plus de la ROM pour le BIOS, l'IBM PC avait quatre mémoires ROM dédiée au langage de programmation BASIC. Lorsque le PC démarrait, il ne bootait pas un système d'exploitation, mais lançait l'interpréteur pour le langage BASIC. De nos jours, ce serait l'équivalent d'un ordinateur qui boote directement sur du Python, à savoir la console Python que vous avez peut-être déjà utilisé si vous avez testé Python. Ceux qui ont déjà touché à un ordinateur de l'époque savent ce que ca veut dire, mais c'est malheureusement très difficile à expliquer sans ce genre d'expérience. Toujours est-il que c'était une sorte de norme à l'époque
: les ordinateurs bootaient généralement sur un interpréteur BASIC.
[[File:XT Bus pins.svg|vignette|Connecteur du bus XT.]]
Les PC étaient conçus pour qu'on branche des '''cartes d'extension''', à savoir des cartes électroniques qu'on branchait sur la carte mère, à l'intérieur du PC. Les cartes d'extension de l'époque étaient surtout des cartes son ou des cartes graphiques, mais aussi des cartes pour brancher des péripéhriques. par exemple, on pouvait ajouter deux cartes graphiques dans l'IBM PC originel : l'''IBM Monochrome Display Adapter'' et/ou la ''IBM Color Graphics Adapter''. De nos jours, les cartes son sont intégrées à la carte mère, mais les cartes graphiques sont restées des cartes d'extension.
Les cartes d'extension étaient branchées sur un '''connecteur XT''', qui était directement relié au bus XT. Le connecteur XT est illustré ci-contre, mais ne vous en souciez pas trop pour le moment. La carte mère de l'IBM PC avait 5 connecteurs de ce type, qu'on pouvait peupler avec autant de cartes d'extension. L'IBM Personal Computer XT est passé à 8 connecteurs XT, soit trois de plus.
Pour ce qui est des périphériques, l'IBM PC avait plusieurs connecteurs : un port série, un port parallèle, un port pour le clavier, et un port pour un lecteur cassette. Le clavier et le lecteur cassette étaient connectés directement sur la carte mère, qui contenait quelques circuits pour gérer le clavier. Par contre, les deux premiers n'étaient pas connectés à la carte mère. Le port série était en réalité une carte d'extension, branchée sur un connecteur XT. Et il en est de même pour le port parallèle.
Pour ce qui est des supports de stockage, l'IBM PC originel n'avait pas de disque dur et n'avait que des lecteurs de disquette. De plus, le lecteur de disquette n'était pas connecté directement sur la carte mère, mais était connecté à une carte d'extension, branchée sur un connecteur XT. La carte d'extension avait deux connecteurs, un par lecteur de disquette, ce qui fait que les deux lecteurs de disquettes pouvaient être branchés sur une seule carte d'extension. L'IBM Personal Computer XT a ajouté un disque dur, sauf sur quelques sous-modèles spécifiques.
Le PC avait aussi un petit haut-parleur capable de faire des bips.
Pour résumer, l'IBM PC originel se reposait beaucoup sur les cartes d'extension, sa carte mère contenait peu de choses. Enfin, peu de choses... Il y avait un processeur Intel 8088, éventuellement un coprocesseur flottant 8087, de la RAM, de la ROM, et des circuits intégrés assez divers. En voici la liste, certains vous seront familiers, d'autres vous seront inconnus à ce stade du cours :
* les circuits de décodage d'adresse ;
* un contrôleur DMA intel 8273 ;
* un contrôleur d'interruption 8259 ;
* un contrôleur de bus Intel 8288 pour gérer le bus XT ;
* un générateur d'horloge Intel 8284 et un diviseur de fréquence ;
* un ''timer'' Intel 8253, le même que celui étudié dans le chapitre sur les ''timers'' ;
* un contrôleur parallèle 8255.
Les multiplexeurs, registres et portes logiques, sont des circuits de décodage d'adresse, qui permettent de combiner plusieurs RAM en une seule, idem avec la mémoire ROM. Si vous verrez qu'il y a 5 mémoires ROM : une ROM pour le BIOS, et quatre autres ROM pour le BASIC. Les 4 ROM du BASIC sont combinées en une seule mémoire ROM. Pour les RAM, il y en a 8 à 32, qui sont combinées en une seule RAM de 16 à 64 kibioctets.
[[File:IBM 5150 Motherboard.svg|centre|vignette|upright=3|Carte mère de l'IBM 5150, un modèle de l'IBM PC.]]
===L'architecture d'un IBM PC compatible 16 bits===
Les PC suivants sont passés à des processeurs 16 bits, mais c'était toujours des processeurs x86 d'Intel, à savoir des Intel 286 et 386. La RAM a grossi, quelques entrées-sorties ont été ajoutées, mais l'architecture globale est plus moins resté le même. C'est surtout au niveau du bus et des périphériques que les changements majeurs ont eu lieu.
[[File:ISA Bus pins.svg|vignette|Connecteur ISA.]]
Les PC 16 bits utilisaient un bus système unique, sur lequel tout était connecté : le processeur, la RAM, la ROM, les cartes d'extension et tout le reste. Le bus en question s'appelait le '''bus AT''', mais il a rapidement été renommé en '''bus ISA''' (''Industry Standard Architecture''). Le bus ISA était prévu pour avoir une compatibilité avec le bus 8 bits de l'IBM PC originel. D'ailleurs, cela se ressent jusque dans le connecteur utilisé : le connecteur ISA est un connecteur XT qu'on a fusionné avec un second connecteur pour l'étendre de 8 à 16 bits.
Les PC 16 bits avaient toujours un port série, un port parallèle, un clavier, un lecteur de disquette et des cartes d'extension. Des disques durs pouvaient être ajoutés, aussi. Mais pour ces périphériques, un changement majeur a eu lieu comparé à l'IBM PC originel. L'IBM PC originel utilisait des cartes d'extension pour tout, sauf le clavier. Mais maintenant, les périphériques ne sont plus connectés à une carte d'extension. À la place, les circuits de la carte d'extension sont déplacés sur la carte mère. Mais n'allez pas croire qu'ils étaient connectés directement au bus ISA, il y avait des intermédiaires.
Le clavier était relié à un '''contrôleur de clavier''', qui faisait l'interface entre le connecteur du clavier et le bus ISA. Le contrôleur de clavier était appelé le ''Keyboard Controler'', abrévié en KB. Il recevait ce qui est tapé au clavier et traduisait cela en quelque chose de compréhensible par l'ordinateur.
Les autres périphériques étaient connectés à un circuit intégré dédié : l''''Intel 82091AA'''. Il était connecté au lecteur de disquette, au port série et au port parallèle. Il servait d'intermédiaire entre ces périphériques et le bus ISA. Vous pouvez le voir comme une sorte de répartiteur, mais qui ne serait pas connecté sur le processeur et la RAM
Enfin, il ne faut pas oublier les autres composants présents sur l'IBM PC originel. Le BIOS est toujours là, de même que les ''timers'' Intel 8253 PIT, le contrôleur d'interruption Intel 8259 et le contrôleur DMA Intel 8237. Les PC 16 bits ont aussi intégré une ''Real Time Clock'' (RTC). Pour rappel, c'est un composant qui permet au PC de mémoriser la date et l'heure courante, à la seconde près. Le tout est résumé dans le schéma ci-dessous.
[[File:Architecture de l'IBM PC compatible.png|centre|vignette|upright=2.5|Architecture de l'IBM PC compatible]]
Un point important est que le bus ISA allait à la même fréquence que le processeur, vu que c'était un bus système. Les processeurs de l'époque étaient des CPU 286 d'Intel, ou le 386 d'Intel. Les Intel 286 allaient de 4 MHz minimum, à 25 MHz maximum. Le 386, quant à lui, allait de 12 à 40 MHz. Le bus ISA devait aller à cette fréquence, il était synchrone avec le processeur.
Par la suite, les processeurs ont gagné en performance, ce qui fait que le bus ISA est devenu trop lent pour le processeur. Une idée a alors été de conserver le bus ISA, pour des raisons de compatibilité, mais de le reléguer comme bus secondaire. L'ordinateur contient alors deux bus : un bus système, et un bus ISA secondaire. Le lien entre les deux est réalisé par un '''pont ISA''', ''ISA Bridge'' en anglais. Le bus ISA fonctionnait alors sa fréquence usuelle, alors que le bus système était beaucoup plus rapide. Le bus système fonctionnait à une fréquence bien plus élevée, ce qui fait que le processeur pouvait communiquer à pleine vitesse, notamment avec la RAM. Le processeur n'était alors plus forcé à aller à la même fréquence que le bus ISA
[[File:Architecture de l'IBM PC compatible avec bridge ISA.png|centre|vignette|upright=2.5|Architecture de l'IBM PC compatible avec bridge ISA]]
Les PC de l'époque intégraient donc plusieurs bus séparés. Vous avez bien lu : plusieurs bus ! Ici, il s'agit de ce que j'appelle des '''bus en cascade''', à savoir qu'un bus est connecté à un autre bus par un intermédiaire. Au passage, si j'aborde ces exemples, car c'est pareil sur les ordinateurs modernes. Le pont ISA a été remplacé par des circuits différents, mais qui ont un rôle assez similaire. Le ''chipset'' de votre carte mère n'est qu'un lointain descendant du pont ISA, qui s'interface avec des bus différents.
===L'arrivée des standards AT et IDE pour les disques durs===
Initialement, les disques durs étaient placés dans l'ordinateur et étaient connectés sur le bus ISA, via une carte d'extension ISA. En clair, il fallait connecter le disque dur sur une carte d'extension, et non sur la carte mère. Les cartes d'extension en question permettaient de connecter un ou plusieurs disques durs, parfois des lecteurs de disquette supplémentaires. Les cartes ISA de ce type faisaient juste l'interface entre le bus ISA et les disques durs, rien de plus. L'interface en question a été standardisée, ce qui a donné le standard ''AT Bus Attachment'', qui a été abrévié en ATA.
Et ce n'était pas que pour les disques durs, de nombreux composants étaient dans ce cas. Une carte d'extension servait d'intermédiaire entre eux et la carte mère. Les cartes d'extension en question étaient appelées des ''Host bus adapter''.
[[File:Acculogic sIDE-4 Controller ISA.jpg|centre|vignette|upright=2|Carte ISA d'interface disque dur, de marque Acculogic.]]
Mais les choses ont rapidement évoluées, que ce soit du côté des cartes mères que du côté des disques durs. Le '''standard IDE''' a permis de brancher un disque dur directement sur la carte mère, sans passer par une carte d'interface ISA. Pour cela, la carte mère réservait un connecteur ISA pour le disque dur, renommé '''connecteur ATA'''. Pour que cela soit possible, il a fallu rajouter des circuits sur la carte mère. Tout ce qui était sur les cartes d'interface ISA s'est retrouvé sur la carte mère.
[[File:Ajout des ports IDE sur la carte mère.png|centre|vignette|upright=2|Ajout des ports IDE sur la carte mère]]
En réalité, les connecteurs ATA étaient des connecteurs ISA simplifiés. Un connecteur ISA avait en tout 98 broches, alors qu'un connecteur ATA n'en contient que 40. Les broches qui étaient inutiles pour les disques durs ont simplement été enlevées. Et qui dit connecteur spécialisé, dit câble spécialisé. Les disques durs étaient branchés sur le connecteur AT grâce à un câble ATA, sur lequel on pouvait connecter deux disques durs.
[[File:ATA Plug.svg|centre|vignette|upright=2|Connecteur ATA.]]
[[File:ATA cables.jpg|centre|vignette|upright=2|Cable ATA.]]
Il était donc possible de connecter deux disques durs sur un seul connecteur ATA. Et cette possibilité est devenue d'autant plus utile par la suite. A partir de la version 2, ATA supportait aussi les lecteurs de disquettes, les lecteurs de CD/DVD, et bien d'autres supports de stockage. Il était alors possible de connecter un lecteur CD et un disque dur sur un seul connecteur. Les cartes mères avaient généralement deux connecteurs ATA, et n'avaient pas besoin de plus. C'était suffisant pour connecter un disque dur, un lecteur de disquette et un lecteur CD, configuration courante entre les années 90 et 2000.
Un câble est donc connecté à deux supports de stockage. Pour distinguer les deux, le standard ATA ajoute une possibilité de configuration. Sur un câble, il doit y avoir un support de stockage "maitre" et un support "esclave". C'était la terminologie de l'époque, que je reproduis ici, même si elle est fortement trompeuse. N'allez pas croire que cela implique que l'un ait des avantages sur l'autre. Le support 'maitre" n'a pas droit à plus de bande passante, il n'a pas la priorité sur l'autre, rien du tout. Il s'agit juste d'un nombre qui permet de savoir avec qui le processeur communique, qui vaut 0 pour le premier support, 1 pour l'autre. Une sorte d'adresse de 1 bit, si l'on veut.
[[File:ATA-Konfiguration02.png|centre|vignette|upright=2|Configuration ATA.]]
Pour configurer un support de stockage en mode "maitre" ou "esclave", le support de stockage avait quelques pins dédiés. Il suffisait de placer un détrompeur en plastique sur les pins adéquats. Les pins se trouvaient à l'arrière du disque dur ou du lecteur de CD/DVD/Disquette/autre.
[[File:HDD Master and Slave Description.jpg|centre|vignette|upright=2|Configuration ''Master/Slave''.]]
===L'architecture d'un PC avec un processeur Intel 486===
Maintenant, passons aux ordinateurs 32 bits, avec l'exemple d'un PC avec un processeur 486 d'Intel. A cette époque, le bus ISA était devenu trop limité et était en place d'être remplacé par le bus PCI, qui avait la même fonction. De nombreuses cartes d'extension utilisaient déjà ce standard et étaient branchées sur des connecteurs PCI dédiés, différents des connecteurs ISA. Intuitivement, on se dit que le bus PCI remplaçait le bus ISA, mais les choses étaient plus compliquées. Les disques durs gardaient leur connecteur ATA, et ne passaient pas par le bus PCI. Ils avaient un bus IDE séparé, qui était un bus ISA modifié.
Là encore, les processeurs étaient devenus beaucoup plus rapides que le bus PCI. Les deux allaient à des fréquences assez différentes, ce qui fait que le bus PCI était séparé du bus système. Il y avait alors deux implémentations possibles.
* La première utilise un répartiteur unique, relié au processeur, à la RAM, au bus PCI, et au bus IDE.
* La seconde utilise un bus système séparé du bus PCI, avec un '''pont PCI''' pour faire l'interface entre les deux.
Le '''''System Controler''''' était un circuit intégré, placé sur la carte mère, qui peut servir soit de pont PCI, soit de répartiteur. Le répartiteur PCI sert d'intermédiaire avec le bus PCI, mais aussi avec le bus IDE, utilisé pour les disques durs, aussi appelé le bus ''Parallel ATA''. Il peut aussi être connecté au processeur, à la mémoire RAM, ainsi qu'à la mémoire cache, mais cela ne sert que quand il est utilisé comme répartiteur.
[[File:Architecture d'un PC utilisant un bus PCI, implémentation avec un répartiteur.png|centre|vignette|upright=2|Architecture d'un PC utilisant un bus PCI, implémentation avec un répartiteur]]
Pour des raisons de compatibilité, le bus ISA avait été conservé, aux côtés du bus PCI. Il y avait un pont ISA en plus du pont/répartiteur PCI. Une implémentation possible aurait été de connecter les deux ponts ISA et PCI à un bus système unique. Mais cette solution n'a pas été retenue. La raison est que le bus PCI et le bus ISA ont des performances très différentes. Le bus PCI est très rapide, le bus ISA beaucoup plus lent. La différence est d'un ordre de grandeur, environ. Dans ces conditions, il est possible de faire passer les communications ISA à travers le bus PCI. Pour cela, le pont ISA est directement connecté sur le pont PCI, comme illustré ci-dessous.
Et il en est de même pour le bus dédié aux disques durs. En effet, les disques durs étaient autrefois reliés au bus ISA, mais cela a changé depuis. Ils disposent maintenant de leur propre bus dédié, le '''bus IDE''', qui est un bus ISA simplifié. Et ce bus ISA simplifié était connecté directement sur le pont PCI.
[[File:Architecture de l'IBM PC compatible avec pont PCI.png|centre|vignette|upright=2|Architecture de l'IBM PC compatible avec pont PCI]]
Dans ce qui va suivre, nous allons étudier un exemple qui utilise un bus système séparé, avec un pont PCI, sans répartiteur. Voilà pour les grandes lignes, mais le schéma ci-dessous montre que tout est plus complexe. Vous remarquerez des connexions optionnelles entre le pont PCI et la mémoire RAM et la mémoire cache. La raison est que le pont PCI peut aussi servir de répartiteur en remplacement du bus système. Concrètement, on peut alors retirer le bus système. La mémoire, le bus PCI, le bus ISA, le bus IDE, le processeur et la RAM sont alors connectés au répartiteur PCI, qui sert d'intermédiaire central entre tous ces composants. Mais ce n'est pas la solution qui a été retenue dans notre exemple.
[[File:Intel486-Typ PCI System.png|centre|vignette|upright=2|PC IBM compatible avec un 486, un bus PCI et un bus ISA. Le ''host bus'' est le bus système.]]
Le pont ISA sert ici d'intermédiaire entre le bus système et le bus ISA. De plus, il a été amélioré sur de nombreux points. Il inclut notamment des circuits qui étaient autrefois sur la carte mère, à savoir le contrôleur DMA 82C87 et le contrôleur d'interruption 82C59, ainsi que les ''timers'' Intel 82C54. Les composants restants sont eux reliés sur un quatrième bus : le bus X, l'ancêtre du bus ''Low Pin Count''. Le bus X était celui du BIOS, du contrôleur de clavier, de la ''Real Time Clock'', et du contrôleur de périphérique 82091AA d'Intel.
[[File:ISA Bridge schematic.png|centre|vignette|upright=2|ISA Bridge.]]
===L'architecture des PC des années 90-2000===
Par la suite, les ponts PCI et ISA ont évolué avec l'évolution des bus de l'ordinateur. Le bus ISA a progressivement été remplacé par d'autres bus, comme le bus ''Low Pin Count'', le bus PCI a été remplacé par le PCI Express, d'autres bus ont été ajoutés, etc. Mais la séparation du ''chipset'' en deux a été conservée.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Le pont PCI et le pont ISA ont été remplacés respectivement par le '''pont nord''' et le '''pont sud''', plus connus par leurs noms anglais de ''northbridge'' et de ''southbridge''. Le pont nord servait d'interface entre le processeur, la mémoire et la carte graphique et est connecté à chacun par un bus dédié. Il intégrait aussi le contrôleur mémoire. Le pont sud est le répartiteur pour les composants lents, à savoir l'USB, l'Ethernet, etc. Le bus qui relie le processeur au pont nord était appelé le '''''Front Side Bus''''', abrévié en FSB.
[[File:IMac Chipset.png|centre|vignette|upright=2|Chipset séparé en northbridge et southbridge.]]
Un point important est que le bus PCI est devenu un bus assez lent, ce qui fait qu'il a finit par être connecté au pont sud. Le pont PCI est donc devenu le pont sud, dans le courant des années 2000. Durant un moment, un équivalent du pont ISA a subsisté dans un circuit de '''''Super IO'''''. Concrètement, il s'occupait du lecteur de disquette, du port parallèle, du port série, et des ports PS/2 pour le clavier et la souris. Mais il ne gérait pas le bus ISA, mais son remplaçant, le bus ''Low Pin Count''.
[[File:Motherboard diagram fr.svg|centre|vignette|upright=1.5|Carte mère avec circuit Super IO.]]
===L'architecture des PC depuis les années 2000===
Depuis la sortie du processeur AMD Athlon 64, le pont nord a été fusionné dans le processeur. La fusion ne s'est pas faite en une fois, des fonctionnalités ont progressivement été progressivement intégrées dans le processeur. Le pont sud est resté, mais il a alors été progressivement connecté directement au processeur. La raison derrière cette intégration est que les processeurs avaient de plus en plus de transistors à leur disposition. Ils en ont profité pour intégrer le pont nord. Et cela permettait de simplifier le câblage des cartes mères, sans pour autant rendre vraiment plus complexe la fabrication du processeur. Les industriels y trouvent leur compte.
La première étape a été l'intégration du contrôleur mémoire a été intégré au processeur. Concrètement, le résultat était que la mémoire RAM n'était plus connectée au pont nord, mais était connectée directement au processeur ! Il y a donc eu un retour d'un bus mémoire, mais spécialisé pour la mémoire RAM. En théorie, une telle intégration permet diverses optimisations quant aux transferts avec la mémoire RAM. Les transferts ne passent pas par un répartiteur, ce qui réduit le temps d'accès à la mémoire RAM. Ajoutons de sombres histoires de prefetching, d'optimisation des commandes, et j'en passe. Toujours est-il que le pont nord ne servait alors d'intermédiaire que pour les ports PCI Express, et le pont sud.
[[File:X58 Block Diagram.png|centre|vignette|upright=2|Chipset X58 d'Intel.]]
Par la suite, la carte graphique fût aussi connectée directement sur le processeur. Le processeur incorpore pour cela des contrôleurs PCI-Express. Le pont nord a alors disparu complétement, son intégration dans le processeur était complète. Sur les cartes mères Intel récentes, le pont sdud subsiste, il est appelé le ''Platform Controler Hub'', ou PCH. L'organisation des bus sur la carte mère qui résulte de cette connexion du processeur à la carte graphique, est illustrée ci-dessous, avec l'exemple du PCH.
[[File:Intel 5 Series architecture.png|centre|vignette|upright=2|Intel 5 Series architecture]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'interface électrique entre circuits intégrés et bus
| prevText=L'interface électrique entre circuits intégrés et bus
| next=La hiérarchie mémoire
| nextText=La hiérarchie mémoire
}}
</noinclude>
fal6wpym9mzir40nmsuqtcr1g6mens5
Fonctionnement d'un ordinateur/Architectures multiprocesseurs et multicœurs
0
65962
775628
773398
2026-11-02T14:12:41Z
Mewtow
31375
/* Le multiprocesseur/multicœur asymétrique */
775628
wikitext
text/x-wiki
Pour réellement tirer parti du parallélisme de taches, rien ne vaut l'utilisation de plusieurs processeurs et/ou de plusieurs cœurs, qui exécutent chacun un ou plusieurs programmes dans leur coin. Des solutions multiprocesseurs ont alors vu le jour pour rendre l'usage de plusieurs processeurs plus adéquat. Avant de poursuivre, nous allons voir les systèmes multiprocesseur à part des processeurs multicœurs. Il faut dire qu'utiliser plusieurs processeurs et avoir plusieurs cœurs sur la même puce, ce n'est pas la même chose. Particulièrement pour ce qui est de la mémoire cache.
Les '''systèmes multiprocesseur''' placent plusieurs processeurs sur la même carte mère. Ils sont courants dans les serveurs ou les ''data centers'', mais sont beaucoup plus rares pour les ordinateurs grand public. Il y a eu quelques systèmes multiprocesseur vendus au grand public dans les années 2000, certaines cartes mères avaient deux sockets pour mettre deux processeurs. Mais les logiciels et les systèmes d'exploitation grand public n'étaient pas adaptés pour, ce qui fait que la technologie est restée confidentielle.
Puis, en 2005, les '''processeurs multicœurs''' sont arrivés. Ils peuvent être vus comme un regroupement de plusieurs processeurs dans le même circuit intégré. Pour être plus précis, ils contiennent plusieurs ''cœurs'', chaque cœur pouvant exécuter un programme tout seul. Un cœur dispose de toute la machinerie électronique pour exécuter un programme, que ce soit un séquenceur d'instruction, des registres, une unité de calcul. Par contre, certains circuits du processeur sont partagés entre les cœurs, comme les circuits d’interfaçage avec la mémoire.
Les processeurs multicœurs sont devenus la norme dans les ordinateurs grand public et les logiciels et systèmes d'exploitation se sont adaptés. Suivant le nombre de cœurs présents dans notre processeur, celui-ci sera appelé un processeur double-cœur (deux cœurs), quadruple-cœur (4 cœurs), octuple-cœur (8 cœurs), etc. Les processeurs grand public ont généralement entre 8 et 16 cœurs, à l'heure où j'écris ces lignes (2025), rarement au-delà. Par contre, les processeurs pour serveurs dépassent la vingtaine de cœurs. Les serveurs utilisent souvent des architectures dites '''''many core''''', qui ont un très grand nombre de cœurs, plus d'une cinquantaine, voire plusieurs centaines ou milliers.
: Dans ce qui suit, nous utiliserons les termes "processeurs" et "cœurs" comme s'ils étaient synonymes. Tout ce qui vaut pour les systèmes multiprocesseur vaut aussi pour les systèmes multicœurs.
==Le partage des caches==
Quand on conçoit un processeur multicœur, il ne faut pas oublier ce qui arrive à la pièce maîtresse de tout processeur actuel : le cache ! Pour le moment nous allons oublier le fait que les processeurs ont une hiérarchie de caches, avec des caches L1, L2, L3 et autres. Nous allons partir du principe qu'un processeur simple cœur a un seul cache, et voir comment adapter le cache à la présence de plusieurs cœurs. Nous allons rapidement lever cette hypothèse, pour étudier le cas où un processeur multicœur a une hiérarchie de caches, mais seulement après avoir vu le cas le plus simple à un seul cache.
===Le partage des caches sans hiérarchie de caches : caches dédiés et partagés===
Avec un seul niveau de cache, sans hiérarchie, deux solutions sont possibles. La première consiste à garder un seul cache, et de le partager entre les cœurs. L'autre solution est de dupliquer le cache et d'utiliser un cache par cœur. Les deux solutions sont appelées différemment. On parle de '''caches dédiés''' si chaque cœur possède son propre cache, et de '''cache partagé''' avec un cache partagé entre tous les cœurs. Ces deux méthodes ont des inconvénients et des avantages.
{|
|[[File:Caches dédiés.png|vignette|Caches dédiés]]
|[[File:Caches partagés.png|vignette|Cache partagé]]
|}
Le premier point sur lequel comparer caches dédiés et partagés est celui de la capacité du cache. La quantité de mémoire cache que l'on peut placer dans un processeur est limitée, car le cache prend beaucoup de place, près de la moitié des circuits du processeur. Aussi, un processeur incorpore une certaine quantité de mémoire cache, qu'il faut répartir entre un ou plusieurs caches. Les caches dédiés et partagés ne donnent pas le même résultat. D'un côté, le cache partagé fait que toute la mémoire cache est dédiée au cache partagé, qui est très gros. De l'autre, on doit répartir la capacité du cache entre plusieurs caches séparés, individuellement plus petits. En conséquence, on a le choix entre un petit cache pour chaque processeur ou un gros cache partagé.
Le choix entre les deux n'est pas simple, mais doit tenir compte du fait que les programmes exécutés sur les cœurs n'ont pas les mêmes besoins. Certains programmes sont plus gourmands et demandent beaucoup de cache, alors que d'autres utilisent peu la mémoire cache. Avec un cache dédié, tous les programmes ont accès à la même quantité de cache, car les caches des différents cœurs sont de la même taille. Les caches dédiés étant assez petits, les programmes plus gourmands devront se débrouiller avec un petit cache, alors que les autres programmes auront du cache en trop.
À l'opposé, un cache partagé répartit le cache de manière optimale : un programme gourmand peut utiliser autant de cache qu'il veut, laissant juste ce qu'il faut aux programmes moins gourmands. le cache peut être répartit plus facilement selon les besoins des différents programmes.
[[File:Cache partagé contre cache dédié.png|centre|vignette|upright=2.5|Cache partagé contre cache dédié]]
Un autre avantage des caches partagés est quand plusieurs cœurs accèdent aux même données. C'est un cas très courant, souvent lié à l'usage de mémoire partagé ou de ''threads''. Avec des caches dédiés, chaque cœur a une copie des données partagées. Mais avec un cache partagé, il n'y a qu'une seule copie de chaque donnée, ce qui utilise moins de mémoire cache. Imaginons que l'on sait 8 caches dédiés de 8 Kibioctets, soit 64 kibioctets au total, comparé à un cache partagé de même capacité totale. Les doublons dans les caches dédiés réduiront la capacité mémoire utile, effective, comparé à un cache partagé. S'il y a 1 Kibioctet de mémoire partagé, 8 kibioctets seront utilisés pour stocker ces données en doublons, seulement 1 kibioctet sur un cache partagé. Ajoutons aussi que la cohérence des caches est grandement simplifiée avec l'usage d'un cache partagé, vu que les données ne sont pas dupliquées dans plusieurs caches.
Mais le partage du cache peut se transformer en inconvénient si les programmes entrent en compétition pour le cache, que ce soit pour y placer des données ou pour les accès mémoire. Deux programmes peuvent vouloir accéder au cache en même temps, voire carrément se marcher sur les pieds. La résolution des conflits d'accès au cache est résolu soit en prenant un cache multiport, avec un port dédié par cœur, soit par des mécanismes d'arbitrages avec des circuits dédiés. Le revers de la médaille tient au temps de latence. Plus un cache est gros, plus il est lent. En conséquence, des caches dédiés seront plus rapides qu'un gros cache partagé plus lent.
===Le partage des caches adapté à une hiérarchie de caches===
Dans la réalité, un processeur multicœur ne contient pas qu'un seul cache, mais une hiérarchie de caches avec des caches L1, L2 et L3, parfois L4. Dans cette hiérarchie, certains caches sont partagés entre plusieurs cœurs, les autres sont dédiés. Le cache L1 n'est jamais partagé, car il doit avoir un temps d'accès très faible. Pour les autres caches, tout dépend du processeur.
[[File:Dual Core Generic.svg|vignette|Cache L2 partagé.]]
Les premiers processeurs multicœurs commerciaux utilisaient deux niveaux de cache : des caches L1 dédiés et un cache L2 partagé. Le cache L2 partagé était relié aux caches L1, grâce à un système assez complexe d'interconnexions. Le cache de niveau L2 était souvent simple port, car les caches L1 se chargent de filtrer les accès aux caches de niveau inférieurs.
Les processeurs multicœurs modernes ont des caches L3 et même L4, de grande capacité, ce qui a modifié le partage des caches. Le cache de dernier niveau, à savoir le cache le plus proche de la mémoire, est systématiquement partagé, car son rôle est d'être un cache lent mais gros. Il s'agit le plus souvent d'un cache de L3, plus rarement L4. Sur certains processeurs multicœurs, le cache de dernier niveau n'est techniquement pas dans le cœur, mais fait partie d'un ensemble de circuits reliés, comme le contrôleur mémoire ou l'interface mémoire. Il fonctionne à une fréquence différente du processeur, n'a pas la même tension d'alimentation, etc.
Le cas du cache L2 dépend des architectures : il est partagé sur certains processeurs, dédié sur d'autres. Mais sur les processeurs modernes, c'est un cache dédié soit par cœur, soit pour un groupe de cœurs. Dans le cas le plus courant, chaque cache L2 est partagé entre plusieurs cœurs mais pas à tous. En effet, on peut limiter le partage du cache à quelques cœurs particuliers pour des raisons de performances.
[[File:Partage des caches sur un processeur multicoeurs.png|centre|vignette|upright=2.0|Partage des caches sur un processeur multicœur.]]
D'autres processeurs ont des caches L2 dédiés. Il s'agit surtout des processeurs multicœurs anciens, parmi les premières générations de processeurs multicœurs. Un exemple est celui de la microarchitecture Nehalem d'Intel. Il avait des caches L1 et L2 dédiés, mais un cache L3 partagé.
[[File:Nehalem EP.png|centre|vignette|upright=2.0|Partage des caches sur un processeur Intel d'architecture Nehalem.]]
===Les caches partagés centralisés et distribués===
Un point important est que quand on parle de cache partagé ou de cache dédié, on ne parle que de la manière dont les cœurs peuvent accéder au cache, pas de la manière dont le caches est réellement localisé sur la puce. En théorie, qui dit plusieurs caches dédiés signifie que l'on a vraiment plusieurs caches séparés sur la puce. Et chaque cache dédié est proche du cœur qui lui est attribué. Et pour les caches partagés unique, une portion de la puce de silicium contient le cache, que cette portion est un énorme bloc de transistors. Il est généralement placé au milieu de la puce ou sur un côté, histoire de facilement le connecter à tous les cœurs.
Mais pour les caches séparés, ce n'est pas toujours le cas. Avoir un cache énorme poserait des problèmes sur les architectures avec beaucoup de cœurs. En réalité, le cache est souvent découpé en plusieurs banques, reliées à un contrôleur du cache par un système d'interconnexion assez complexe. Les banques sont physiquement séparées, et il arrive qu'elles soient placées proche d'un cœur chacune. L'organisation des banques ressemble beaucoup à l'organisation des caches dédiés, avec une banque étant l'équivalent d'un cache dédié. La différence est que les cœurs peuvent lire et écrire dans toutes les banques, grâce au système d'interconnexion et au contrôleur de cache.
Tel était le cas sur les processeurs AMD Jaguar. Ils avaient un cache L2 de 2 mébioctets, partagés entre tous les cœurs, qui était composé de 4 banques de 512 Kibioctets. Les quatre banques du cache étaient reliées aux 4 cœurs par un réseaux d'interconnexion assez complexe.
[[File:AMDJaguarModule.png|centre|vignette|upright=2|AMD Jaguar Module]]
La différence entre les deux solutions pour les caches partagés porte le nom de cache centralisés versus distribués. Un gros cache unique sur la puce est un '''cache centralisé''', et c'est généralement un cache partagé. Mais un cache composé de plusieurs banques dispersées sur la puce est un '''cache distribué''', qui peut être aussi bien dédié que partagé.
===Les caches virtualisés===
Il faut noter que quelques processeurs utilisent cette technique pour fusionnent le cache L2 et le cache L3. Par exemple, les processeurs IBM Telum utilisent des caches L3 virtualisés, dans leurs versions récentes. Le processeur Telum 2 contient 10 caches L2 de 36 mébioctets chacun, soit 360 mébioctets de cache. L'idée est que ces 360 mébioctets sont partagés à la demande entre cache L2 dédié et cache L3. On parle alors de '''cache virtualisé'''.
Un cache de 36 mébioctet est associé à un cœur, auquel il est directement relié. Les cœurs n'utilisent pas tous leur cache dédié à 100% Il arrive que des cœurs aient des caches partiellement vides, alors que d'autres on un cache qui déborde. L'idée est que si un cœur a un cache plein, les données évincées du cache L2 privé sont déplacées dans le cache L2 d'un autre cœur, qui lui est partiellement vide. Le cache L2 en question est alors partitionné en deux : une portion pour les données associée à son cœur, une portion pour les données des L2 des autres cœurs.
Pour que la technique fonctionne, le processeur mesure le remplissage de chaque cache L2. De plus, il faut gérer la politique de remplacement des lignes de cache. Une ligne de cache évincée du cache doit être déplacé dans un autre L2, pas dans les niveaux de cache inférieur, ni dans la mémoire. Du moins, tant qu'il reste de la place dans le cache L3. De plus, une lecture/écriture dans le cache L3 demande de localiser le cache L2 contenant la donnée. Pour cela, les caches L2 sont tous consultés lors d'un accès au L3, c'est la solution la plus simple, elle marche très bien si le taux de défaut du cache L2 est faible.
Une telle optimisation ressemble beaucoup à un cache L2/L3 distribué, mais il y a quelques différences qui sont décrites dans le paragraphe précédent. Avec un L2 distribué, tout accès au L2 déclencherait une consultation de toutes les banques du L2. Avec un cache L3 virtualisé, ce n'est pas le cas. Le cache L2 associé au cœur est consulté, et c'est seulement en cas de défaut de cache que les autres caches L2 sont consultés. De plus, avec un cache L2 distribué, il n'y a pas de déplacement d'une ligne de cache entre deux banques, entre deux caches L2 physiques. Alors qu'avec un cache L3 virtualisé, c'est le cas en cas de remplacement d'une ligne de cache dans le cache L2.
Sur le processeur Telum 1, le partage du cache L2 est assez simple. Un cache L2 fait 32 mébioctets et est découpé en deux banques de 16 mébioctets. En temps normal, les premiers 16 mébioctets sont toujours associé au cache L2, au cœur associé. Les 16 mébioctets restants peuvent soit être attribués au cache L3, soit fusionnés avec les 16 premiers mébioctets. Dans le cas où le cœur associé est en veille, n'est absolument pas utilisé, les 32 mébioctets sont attribués au cache L3. Un partage assez simple, donc. Le partage du cache L2/L3 sur les processeurs Telum 2 n'est pas connu, il est supposé être plus flexible.
==Le réseau d'interconnexion entre cœurs==
Les systèmes avec plusieurs processeurs incorporent un réseau d'interconnexion pour connecter les processeurs entre eux, ainsi qu'à la mémoire RAM. Il s'agit d'un '''réseau d'interconnexion inter-processeur''', placé sur la carte mère. Les CPU multicœurs ont aussi un tel réseau d'interconnexion, pour relier les cœurs entre eux. La différence est que le réseau d'interconnexion est placé dans le processeur, pas sur la carte mère.
Les systèmes multi-cœurs modernes utilisent des réseaux d'interconnexion standardisés, les standards les plus communs étant l'HyperTransport, l'Intel QuickPath Interconnect, l'IBM Elastic Interface, le Intel Ultra Path Interconnect, l'Infinity Fabric, etc. Ils sont aussi utilisés pour faire communiquer entre eux plusieurs processeurs.
[[File:Architecture multicoeurs et réseau sur puce.png|centre|vignette|upright=1.5|Architecture multicoeurs et réseau sur puce]]
===Le bus partagé entre plusieurs cœurs===
Pour un faible nombre de coeurs/processeurs, la solution utilisée relie les processeurs entre eux grâce au bus mémoire. Le bus mémoire est donc un '''bus partagé''', avec tout ce que cela implique.
[[File:Architecture multicoeurs à bus partagé.png|centre|vignette|upright=2|Architecture multiprocesseurs à bus partagé]]
Pour les systèmes multicœurs, l'usage d'un bus partagé doit être adaptée pour tenir compte des caches partagés. Voyons d'abord le cas d'un CPU avec deux niveaux de cache, dont un cache L2 est partagé entre tous les cœurs. Les caches L1 sont reliés au cache L2 partagé par un bus, qui n'a souvent pas de nom. Nous désignerons le bus entre le cache L1 et le cache L2 : '''bus partagé''', sous-entendu partagé entre tous les caches. C'est lui qui sert à connecter les cœurs entre eux.
[[File:Architecture multicoeurs à bus partagé entre caches L1 et L2.png|centre|vignette|upright=2|Architecture multicoeurs à bus partagé entre caches L1 et L2]]
Un processeur multicœur typique a une architecture avec trois niveaux de cache (L1, L2 et L3), avec un niveau L1 dédié par cœur, un niveau L2 partiellement partagé et un L3 totalement partagé. Le bus partagé est alors difficile à décrire, mais il correspond à l'ensemble des bus qui connectent les caches L1 aux caches L2, et les caches L2 au cache L3. Il s'agit alors d'un ensemble de bus, plus que d'un bus partagé unique.
L'usage d'un bus partagé a cependant de nombreux défauts. Par exemple, les processeurs doivent se répartir l'accès au bus mémoire, il faut gérer le cas où deux processeurs accèdent au bus en même temps, etc. Pour cela, un composant dédié s'occupe de l'arbitrage entre processeurs. Il est généralement placé sur la carte mère de l'ordinateur, dans le ''chipset'', dans le pont nord, ou un endroit proche. Les processeurs doivent être conçus pour communiquer avec ce circuit d'arbitrage, ou du moins avoir un support minimal pour le multiprocesseur.
[[File:Intel486 System Arbitration.png|centre|vignette|upright=2|Système avec deux Intel486 - Arbitrage du bus mémoire.]]
D'autres défauts très importants seront abordés en détail dans le chapitre sur la cohérence des caches
===Le réseau d'interconnexion entre plusieurs cœurs===
Relier plusieurs cœurs avec des bus pose de nombreux problèmes techniques qui sont d'autant plus problématiques que le nombre de cœurs augmente. Le câblage est notamment très complexe, les contraintes électriques pour la transmission des signaux sont beaucoup plus fortes, les problèmes d'arbitrages se font plus fréquents, etc. Pour régler ces problèmes, les processeurs multicoeurs n'utilisent pas de bus partagé, mais un réseau d'interconnexion plus complexe.
Un exemple est le réseau du processeur Celle, utilisé sur la PS3. Les transferts sur ce bus se font par paquets de 128 bits. Le réseau était composé de 12 intermédiaires appelés ''Ramps''. Chaque intermédiaire communiquait avec deux autres ''ramps'' ce qui formait un anneau de ''ramps''. Les paquets de 128 bits circulaient sur cet anneau en passant d'un ''ramp'' au suivant, soit dans le sens horaires, soit dans le sens anti-horaire.
Avant d'entrer dans l'anneau, les processeurs envoient une requête à un circuit d'arbitrage, qui commande les transferts de données sur l'anneau. Si celui-ci accepte la requête, la donnée est envoyée au ''ramp'' et circule dans l'anneau. Il faut noter que le contrôleur mémoire a la priorité sur tout le reste. Le sens de transfert, horaire ou anti-horaire, est choisit de manière à prendre le chemin le plus court. Les paquets passent alors de ''ramp'' en ''ramp''. Chaque ''ramp'' reçoit le paquet, regarde l'adresse de destination du paquet, et décide de la suite. Soit le paquet lui est destiné, il le traite. Sinon, le il transmet au ''ramp'' suivant.
Le bus permet de transférer 4 paquets de 128 bits par cycle. Pour cela, les ''ramps'' avaient quatre bus, chacun de 128 bits : deux bus pour le sens horaire, et deux autres bus pour le sens anti-horaire. Il peut y avoir 3 transferts simultanés, tant que les paquets ne se recouvrent pas, mais les transferts sont de taille arbitraire. Le réseau d'interconnexion avait une fréquence de 1,6 GHz. Si vous faites les calculs, vous tomberez cependant sur un débit binaire théorique d'environ 300 gibioctets par secondes, qui n'est cependant jamais atteint en pratique, pour des raisons techniques liées à la cohérence des caches. La limite théorique est en pratique plus proche des 200 gibioctets par secondes, en tenant compte de ces limitations théoriques.
[[File:Réseau d'interconnexion du processeur CELL.png|centre|vignette|upright=2|Réseau d'interconnexion du processeur CELL]]
Un autre xemple de réseau d'interconnexion est celui des architectures AMD EPYC, de microarchitecture Zen 1. Elles utilisaient des chiplets, à savoir que le processeur était composé de plusieurs puces interconnectées entre elles. Chaque puce contenait un processeur multicoeurs intégrant un cache L3, avec un réseau d'interconnexion interne au processeur sans doute basé sur un ensemble de bus. De plus, les puces étaient reliées à une puce d'interconnexion qui servait à la fois d'interface entre les processeurs, mais aussi d'interface avec la R1AM, le bus PCI-Express, etc. La puce d'interconnexion était gravée en 14 nm contre 7nm pour les chiplets des cœurs.
{|
|[[File:AMD Epyc 7702 delidded.jpg|centre|vignette|upright=2|AMD Epyc 7702.]]
|[[File:AMD Epyc Rome Aufbau.png|centre|vignette|upright=2|Schéma fonctionnel de l'AMD Epyc.]]
|}
Le réseau d'interconnexion peut être très complexe, avec des connexions réseau, des commutateurs, et des protocoles d'échanges entre processeurs assez complexes basés sur du passage de messages. De telles puces utilisent un '''réseau sur puce''' (''network on chip''). Mais d'autres simplifient le réseau d'interconnexion, qui se résume à un réseau ''crossbar'', voire à des mémoires FIFO pour faire l'interface entre les cœurs.
Le problème principal des réseaux sur puce est que les mémoires FIFOs sont difficiles à implémenter sur une puce de silicium. Elles prennent beaucoup de place, utilisent beaucoup de portes logiques, consomment beaucoup d'énergie, sont difficiles à concevoir pour diverses raisons (les accès concurrents/simultanés sont fréquents et font mauvais ménage avec les ''timings'' serrés de quelques cycles d'horloges requis). Il est donc impossible de placer beaucoup de mémoires FIFO dans un processeur, ce qui fait que les commutateur sont réduits à leur strict minimum : un réseau d'interconnexion, un système d'arbitrage simple parfois sans aucune FIFO, guère plus.
===Les architectures en ''tile''===
Un cas particulier de réseau sur puce est celui des '''architectures en ''tile''''', des architectures avec un grand nombre de cœurs, connectés les unes aux autres par un réseau d'interconnexion "rectangulaire". Chaque cœur est associé à un commutateur (''switch'') qui le connecte au réseau d'interconnexion, l'ensemble formant une ''tile''.
[[File:Tile64-Tile.svg|centre|vignette|upright=1.5|''Tile'' de base du Tile64.]]
Le réseau est souvent organisé en tableau, chaque ''tile'' étant connectée à plusieurs voisines. Dans le cas le plus fréquent, chaque ''tile'' est connectée à quatre voisines : celle du dessus, celle du dessous, celle de gauche et celle de droite. Précisons que cette architecture n'est pas une architecture distribuée dont tous les processeurs seraient placés sur la même puce de silicium. En effet, la comparaison ne marche pas pour ce qui est de la mémoire : tous les cœurs accèdent à une mémoire partagée située en dehors de la puce de silicium. Le réseau ne connecte pas plusieurs ordinateurs séparés avec chacun leur propre mémoire, mais plusieurs cœurs qui accèdent à une mémoire partagée.
Un bon exemple d'architecture en ''tile'' serait les déclinaisons de l'architecture Tilera. Les schémas du-dessous montrent l'architecture du processeur Tile 64. Outre les ''tiles'', qui sont les éléments de calcul de l'architecture, on trouve plusieurs contrôleurs mémoire DDR, divers interfaces réseau, des interfaces série et parallèles, et d'autres entrées-sorties.
[[File:Tile64.svg|centre|vignette|upright=2|Architecture Tile64 du Tilera.]]
==Les interruptions inter-processeurs==
Les '''interruptions inter-processeurs''' sont des interruptions déclenchées sur un processeur et exécutées sur un autre. Elles sont très utiles pour le système d'exploitation, pour des raisons qu'on ne peut pas expliquer ici. Disons simplement qu'elles permettent de répartir des programmes/''threads'' sur plusieurs processeurs. Un programme/''thread'' est démarré par une interruption, et le système d'exploitation détermine sur quel processeur elle doit être exécutée. L'utilité des interruptions inter-processeur est assez variée. Autrefois, elles servaient aussi pour la cohérence des caches, mais nous détaillerons cela dans un futur chapitre.
Une interruption inter-processeurs peut être envoyée soit à un cœur bien précis, soit à n'importe quel cœur, soit à tous les cœurs, voire même revenir à l'envoyeur. Tout dépend de ce que décide le système d'exploitation. Les trois situations ne sont pas identiques, sur un point : comment préciser quel est le processeur de destination ? Si on envoie une interruption à un cœur bien précis, il faut préciser quel est le cœur qui réceptionne l'interruption. Pour cela, il n'y a pas 36 solutions : on numérote les processeurs/cœurs avec un '''numéro de processeur'''. Ce numéro leur est soit attribué au démarrage par le BIOS, soit est gravé dans leur silicium pour les processeurs multicœurs.
Pour le reste, les interruptions inter-processeurs sont identiques aux interruptions normales. Elles ont un système de priorités, certaines devant passer avant les autres, là encore défini par des ''Interrupt Request Levels'' (IRQLs) ou quelque chose de similaire. Il peut y avoir des interruptions inter-processeur de type logicielles, à savoir lancées par une instruction machine. Par exemple, sur les IBM System/360 et les ''mainframes'' z/Architecture, le processeur avait une instruction SIGNAL PROCESSOR pour déclencher des interruptions logicielles inter-processeur.
Pour générer des interruptions inter-processeur, le contrôleur d'interruption doit pouvoir rediriger des interruptions déclenchées par un processeur vers un autre. Pour expliquer comment, nous allons étudier le cas des CPU x86, mais les implémentations ARM ou autres sont très similaires. L'ancien contrôleur d'interruption 8259A ne gérait pas les interruptions inter-processeurs, ce qui fait que les cartes mères multiprocesseurs devaient incorporer un contrôleur d'interruption spécial en complément. Par contre, son successeur, l'APIC, les gérait nativement.
De nos jours, chaque cœur x86 possède son propre contrôleur d’interruption, le '''''local APIC''''', qui gère les interruptions en provenance ou arrivant vers ce processeur. On trouve aussi un '''''IO-APIC''''', qui gère les interruptions en provenance des périphériques et de les redistribuer vers les APIC locaux. L'IO-APIC gère aussi les interruptions inter-processeurs en faisant passer les interruptions d'un local APIC vers un autre. Tous les APIC locaux et l'IO-APIC sont reliés ensembles par un '''bus APIC''' spécialisé, par lequel ils vont pouvoir communiquer et s'échanger des demandes d'interruptions.
[[File:Contrôleurs d'interrptions sur systèmes x86 multicoeurs.png|centre|vignette|upright=1.5|Contrôleurs d’interruptions sur systèmes x86 multicœurs.]]
Le déclenchement d'une interruption inter-processeur se fait en écrivant dans un registre appelé l''''''Interrupt Command Register'''''. Un détail important est que l'écriture se fait dans le ''local APIC'' de l'envoyer, du processeur qui veut envoyer une interruption, pas dans le registre du processeur qui doit réceptionner l'interruption ! Le registre est composé de deux registres de 32 bits, et mémorise : le numéro du processeur de destination, le mode de transfert (à tous, à un cœur, etc), le numéro du vecteur d'interruption (pour préciser quelle interruption exécuter), et quelques informations supplémentaires. À charge de l'IO-APIC de faire ce qu'il faut en fonction du contenu de ce registre.
==Le multiprocesseur/multicœur asymétrique==
Jusqu'ici, nous nous sommes concentrés sur des processeurs dont les cœurs étaient tous identiques. Mais ce n'est certainement pas une obligation. On peut très bien regrouper plusieurs cœurs très différents, par exemple un cœur principal avec des cœurs plus spécialisés autour. Il faut ainsi distinguer le '''multicœurs symétrique''', dans lequel on place des processeurs identiques sur la même puce de silicium, du '''multicœurs asymétrique''' où les cœurs ne sont pas identiques. Et il en est de même sur les systèmes avec plusieurs processeurs : on parle de '''multiprocesseur symétrique''' si les processeurs sont identiques, '''multiprocesseur asymétrique''' s'ils sont différents.
===Le processeur CELL de la PS3===
Un exemple de multicoeurs asymétrique est celui du processeur CELL de la console de jeu PS3. Il était conçu spécifiquement pour cette console. Il intègre un cœur principal POWER PC v5 et 8 cœurs qui servent de processeurs auxiliaires. Le processeur principal est appelé le PPE et les processeurs auxiliaires sont les SPE. Les SPE sont reliés à une mémoire locale (''local store'') de 256 kibioctets qui communique avec le processeur principal via un bus spécial. Cette fois-ci, les coprocesseurs sont intégrés dans le même processeur.
Les SPE communiquent avec la RAM principale via des contrôleurs DMA. Les SPE possèdent des instructions permettant de commander leur contrôleur DMA et c'est le seul moyen qu'ils ont pour récupérer des informations depuis la mémoire. Et c'est au programmeur de gérer tout ça ! C'est le processeur principal qui va envoyer aux SPE les programmes qu'ils doivent exécuter. Il délègue des calculs aux SPE en écrivant dans le local store du SPE et en lui ordonnant l’exécution du programme qu'il vient d'écrire.
[[File:Schema Cell.png|centre|vignette|upright=2|Architecture du processeur CELL de la PS3. Le PPE est le processeur principal, les SPE sont des processeurs auxiliaires qui comprennent : un ''local store'' noté LS, un processeur noté SXU, et un contrôleur DMA pour échanger des informations avec la mémoire principale.]]
===Les configurations Big.Little===
Les processeurs Intel modernes utilisent deux types de cœurs différents : des cœurs P et des cœurs E. Le P est pour ''Performance'', le E est pour "Efficiency". Les deux ont le même jeu d'instruction : ce sont des processeurs x86. Par contre, ils ont des microarchitectures différentes. Les cœurs E sont optimisés pour un budget en transistors serré : ils utilisent peu de place, mais sont moins performants et n’implémentent pas certaines extensions x86 (du moins, ils n'implémentaient pas ces extensions, les choses changent). Les coeurs P ont un budget en transistors plus conséquent, ce qui fait qu'ils prennent plus de place, chauffent plus, mais sont plus performants.
[[File:Intel-ARL-Core-Layout.png|centre|vignette|upright=2|Die des processeurs Intel de microarchitecture ''Raptor lake'' et ''Arrow lake''.]]
Et Intel n'est pas le seul à utiliser cette technique : ARM a fait pareil avec ses CPU d'architecture ''Big-little''. Il n'est pas clair si de telles organisation sont du multicœur symétrique ou asymétrique. Le jeu d'instruction est identique, sauf éventuellement pour certaines extensions comme l'AVX. Les deux coeurs n'ont pas les mêmes performances, mais est-ce suffisant ? La terminologie n'est pas claire.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les architectures parallèles
| prevText=Les architectures parallèles
| next=Architectures multithreadées et Hyperthreading
| nextText=Architectures multithreadées et Hyperthreading
}}
</noinclude>
h67lv9e9ptxywdnbjrr7klmmpzl2aiu
775629
775628
2026-11-02T14:50:46Z
Mewtow
31375
a
775629
wikitext
text/x-wiki
Pour réellement tirer parti du parallélisme de taches, rien ne vaut l'utilisation de plusieurs processeurs et/ou de plusieurs cœurs, qui exécutent chacun un ou plusieurs programmes dans leur coin. Des solutions multiprocesseurs ont alors vu le jour pour rendre l'usage de plusieurs processeurs plus adéquat. Avant de poursuivre, nous allons voir les systèmes multiprocesseur à part des processeurs multicœurs. Il faut dire qu'utiliser plusieurs processeurs et avoir plusieurs cœurs sur la même puce, ce n'est pas la même chose. Particulièrement pour ce qui est de la mémoire cache.
Les '''systèmes multiprocesseur''' placent plusieurs processeurs sur la même carte mère. Ils sont courants dans les serveurs ou les ''data centers'', mais sont beaucoup plus rares pour les ordinateurs grand public. Il y a eu quelques systèmes multiprocesseur vendus au grand public dans les années 2000, certaines cartes mères avaient deux sockets pour mettre deux processeurs. Mais les logiciels et les systèmes d'exploitation grand public n'étaient pas adaptés pour, ce qui fait que la technologie est restée confidentielle.
Puis, en 2005, les '''processeurs multicœurs''' sont arrivés. Ils peuvent être vus comme un regroupement de plusieurs processeurs dans le même circuit intégré. Pour être plus précis, ils contiennent plusieurs ''cœurs'', chaque cœur pouvant exécuter un programme tout seul. Un cœur dispose de toute la machinerie électronique pour exécuter un programme, que ce soit un séquenceur d'instruction, des registres, une unité de calcul. Par contre, certains circuits du processeur sont partagés entre les cœurs, comme les circuits d’interfaçage avec la mémoire.
Les processeurs multicœurs sont devenus la norme dans les ordinateurs grand public et les logiciels et systèmes d'exploitation se sont adaptés. Suivant le nombre de cœurs présents dans notre processeur, celui-ci sera appelé un processeur double-cœur (deux cœurs), quadruple-cœur (4 cœurs), octuple-cœur (8 cœurs), etc. Les processeurs grand public ont généralement entre 8 et 16 cœurs, à l'heure où j'écris ces lignes (2025), rarement au-delà. Par contre, les processeurs pour serveurs dépassent la vingtaine de cœurs. Les serveurs utilisent souvent des architectures dites '''''many core''''', qui ont un très grand nombre de cœurs, plus d'une cinquantaine, voire plusieurs centaines ou milliers.
: Dans ce qui suit, nous utiliserons les termes "processeurs" et "cœurs" comme s'ils étaient synonymes. Tout ce qui vaut pour les systèmes multiprocesseur vaut aussi pour les systèmes multicœurs.
==Le partage des caches==
Quand on conçoit un processeur multicœur, il ne faut pas oublier ce qui arrive à la pièce maîtresse de tout processeur actuel : le cache ! Pour le moment nous allons oublier le fait que les processeurs ont une hiérarchie de caches, avec des caches L1, L2, L3 et autres. Nous allons partir du principe qu'un processeur simple cœur a un seul cache, et voir comment adapter le cache à la présence de plusieurs cœurs. Nous allons rapidement lever cette hypothèse, pour étudier le cas où un processeur multicœur a une hiérarchie de caches, mais seulement après avoir vu le cas le plus simple à un seul cache.
===Le partage des caches sans hiérarchie de caches : caches dédiés et partagés===
Avec un seul niveau de cache, sans hiérarchie, deux solutions sont possibles. La première consiste à garder un seul cache, et de le partager entre les cœurs. L'autre solution est de dupliquer le cache et d'utiliser un cache par cœur. Les deux solutions sont appelées différemment. On parle de '''caches dédiés''' si chaque cœur possède son propre cache, et de '''cache partagé''' avec un cache partagé entre tous les cœurs. Ces deux méthodes ont des inconvénients et des avantages.
{|
|[[File:Caches dédiés.png|vignette|Caches dédiés]]
|[[File:Caches partagés.png|vignette|Cache partagé]]
|}
Le premier point sur lequel comparer caches dédiés et partagés est celui de la capacité du cache. La quantité de mémoire cache que l'on peut placer dans un processeur est limitée, car le cache prend beaucoup de place, près de la moitié des circuits du processeur. Aussi, un processeur incorpore une certaine quantité de mémoire cache, qu'il faut répartir entre un ou plusieurs caches. Les caches dédiés et partagés ne donnent pas le même résultat. D'un côté, le cache partagé fait que toute la mémoire cache est dédiée au cache partagé, qui est très gros. De l'autre, on doit répartir la capacité du cache entre plusieurs caches séparés, individuellement plus petits. En conséquence, on a le choix entre un petit cache pour chaque processeur ou un gros cache partagé.
Le choix entre les deux n'est pas simple, mais doit tenir compte du fait que les programmes exécutés sur les cœurs n'ont pas les mêmes besoins. Certains programmes sont plus gourmands et demandent beaucoup de cache, alors que d'autres utilisent peu la mémoire cache. Avec un cache dédié, tous les programmes ont accès à la même quantité de cache, car les caches des différents cœurs sont de la même taille. Les caches dédiés étant assez petits, les programmes plus gourmands devront se débrouiller avec un petit cache, alors que les autres programmes auront du cache en trop.
À l'opposé, un cache partagé répartit le cache de manière optimale : un programme gourmand peut utiliser autant de cache qu'il veut, laissant juste ce qu'il faut aux programmes moins gourmands. le cache peut être répartit plus facilement selon les besoins des différents programmes.
[[File:Cache partagé contre cache dédié.png|centre|vignette|upright=2.5|Cache partagé contre cache dédié]]
Un autre avantage des caches partagés est quand plusieurs cœurs accèdent aux même données. C'est un cas très courant, souvent lié à l'usage de mémoire partagé ou de ''threads''. Avec des caches dédiés, chaque cœur a une copie des données partagées. Mais avec un cache partagé, il n'y a qu'une seule copie de chaque donnée, ce qui utilise moins de mémoire cache. Imaginons que l'on sait 8 caches dédiés de 8 Kibioctets, soit 64 kibioctets au total, comparé à un cache partagé de même capacité totale. Les doublons dans les caches dédiés réduiront la capacité mémoire utile, effective, comparé à un cache partagé. S'il y a 1 Kibioctet de mémoire partagé, 8 kibioctets seront utilisés pour stocker ces données en doublons, seulement 1 kibioctet sur un cache partagé. Ajoutons aussi que la cohérence des caches est grandement simplifiée avec l'usage d'un cache partagé, vu que les données ne sont pas dupliquées dans plusieurs caches.
Mais le partage du cache peut se transformer en inconvénient si les programmes entrent en compétition pour le cache, que ce soit pour y placer des données ou pour les accès mémoire. Deux programmes peuvent vouloir accéder au cache en même temps, voire carrément se marcher sur les pieds. La résolution des conflits d'accès au cache est résolu soit en prenant un cache multiport, avec un port dédié par cœur, soit par des mécanismes d'arbitrages avec des circuits dédiés. Le revers de la médaille tient au temps de latence. Plus un cache est gros, plus il est lent. En conséquence, des caches dédiés seront plus rapides qu'un gros cache partagé plus lent.
===Le partage des caches adapté à une hiérarchie de caches===
Dans la réalité, un processeur multicœur ne contient pas qu'un seul cache, mais une hiérarchie de caches avec des caches L1, L2 et L3, parfois L4. Dans cette hiérarchie, certains caches sont partagés entre plusieurs cœurs, les autres sont dédiés. Le cache L1 n'est jamais partagé, car il doit avoir un temps d'accès très faible. Pour les autres caches, tout dépend du processeur.
[[File:Dual Core Generic.svg|vignette|Cache L2 partagé.]]
Les premiers processeurs multicœurs commerciaux utilisaient deux niveaux de cache : des caches L1 dédiés et un cache L2 partagé. Le cache L2 partagé était relié aux caches L1, grâce à un système assez complexe d'interconnexions. Le cache de niveau L2 était souvent simple port, car les caches L1 se chargent de filtrer les accès aux caches de niveau inférieurs.
Les processeurs multicœurs modernes ont des caches L3 et même L4, de grande capacité, ce qui a modifié le partage des caches. Le cache de dernier niveau, à savoir le cache le plus proche de la mémoire, est systématiquement partagé, car son rôle est d'être un cache lent mais gros. Il s'agit le plus souvent d'un cache de L3, plus rarement L4. Sur certains processeurs multicœurs, le cache de dernier niveau n'est techniquement pas dans le cœur, mais fait partie d'un ensemble de circuits reliés, comme le contrôleur mémoire ou l'interface mémoire. Il fonctionne à une fréquence différente du processeur, n'a pas la même tension d'alimentation, etc.
Le cas du cache L2 dépend des architectures : il est partagé sur certains processeurs, dédié sur d'autres. Mais sur les processeurs modernes, c'est un cache dédié soit par cœur, soit pour un groupe de cœurs. Dans le cas le plus courant, chaque cache L2 est partagé entre plusieurs cœurs mais pas à tous. En effet, on peut limiter le partage du cache à quelques cœurs particuliers pour des raisons de performances.
[[File:Partage des caches sur un processeur multicoeurs.png|centre|vignette|upright=2.0|Partage des caches sur un processeur multicœur.]]
D'autres processeurs ont des caches L2 dédiés. Il s'agit surtout des processeurs multicœurs anciens, parmi les premières générations de processeurs multicœurs. Un exemple est celui de la microarchitecture Nehalem d'Intel. Il avait des caches L1 et L2 dédiés, mais un cache L3 partagé.
[[File:Nehalem EP.png|centre|vignette|upright=2.0|Partage des caches sur un processeur Intel d'architecture Nehalem.]]
===Les caches partagés centralisés et distribués===
Un point important est que quand on parle de cache partagé ou de cache dédié, on ne parle que de la manière dont les cœurs peuvent accéder au cache, pas de la manière dont le caches est réellement localisé sur la puce. En théorie, qui dit plusieurs caches dédiés signifie que l'on a vraiment plusieurs caches séparés sur la puce. Et chaque cache dédié est proche du cœur qui lui est attribué. Et pour les caches partagés unique, une portion de la puce de silicium contient le cache, que cette portion est un énorme bloc de transistors. Il est généralement placé au milieu de la puce ou sur un côté, histoire de facilement le connecter à tous les cœurs.
Mais pour les caches séparés, ce n'est pas toujours le cas. Avoir un cache énorme poserait des problèmes sur les architectures avec beaucoup de cœurs. En réalité, le cache est souvent découpé en plusieurs banques, reliées à un contrôleur du cache par un système d'interconnexion assez complexe. Les banques sont physiquement séparées, et il arrive qu'elles soient placées proche d'un cœur chacune. L'organisation des banques ressemble beaucoup à l'organisation des caches dédiés, avec une banque étant l'équivalent d'un cache dédié. La différence est que les cœurs peuvent lire et écrire dans toutes les banques, grâce au système d'interconnexion et au contrôleur de cache.
Tel était le cas sur les processeurs AMD Jaguar. Ils avaient un cache L2 de 2 mébioctets, partagés entre tous les cœurs, qui était composé de 4 banques de 512 Kibioctets. Les quatre banques du cache étaient reliées aux 4 cœurs par un réseaux d'interconnexion assez complexe.
[[File:AMDJaguarModule.png|centre|vignette|upright=2|AMD Jaguar Module]]
La différence entre les deux solutions pour les caches partagés porte le nom de cache centralisés versus distribués. Un gros cache unique sur la puce est un '''cache centralisé''', et c'est généralement un cache partagé. Mais un cache composé de plusieurs banques dispersées sur la puce est un '''cache distribué''', qui peut être aussi bien dédié que partagé.
===Les caches virtualisés===
Il faut noter que quelques processeurs utilisent cette technique pour fusionnent le cache L2 et le cache L3. Par exemple, les processeurs IBM Telum utilisent des caches L3 virtualisés, dans leurs versions récentes. Le processeur Telum 2 contient 10 caches L2 de 36 mébioctets chacun, soit 360 mébioctets de cache. L'idée est que ces 360 mébioctets sont partagés à la demande entre cache L2 dédié et cache L3. On parle alors de '''cache virtualisé'''.
Un cache de 36 mébioctet est associé à un cœur, auquel il est directement relié. Les cœurs n'utilisent pas tous leur cache dédié à 100% Il arrive que des cœurs aient des caches partiellement vides, alors que d'autres on un cache qui déborde. L'idée est que si un cœur a un cache plein, les données évincées du cache L2 privé sont déplacées dans le cache L2 d'un autre cœur, qui lui est partiellement vide. Le cache L2 en question est alors partitionné en deux : une portion pour les données associée à son cœur, une portion pour les données des L2 des autres cœurs.
Pour que la technique fonctionne, le processeur mesure le remplissage de chaque cache L2. De plus, il faut gérer la politique de remplacement des lignes de cache. Une ligne de cache évincée du cache doit être déplacé dans un autre L2, pas dans les niveaux de cache inférieur, ni dans la mémoire. Du moins, tant qu'il reste de la place dans le cache L3. De plus, une lecture/écriture dans le cache L3 demande de localiser le cache L2 contenant la donnée. Pour cela, les caches L2 sont tous consultés lors d'un accès au L3, c'est la solution la plus simple, elle marche très bien si le taux de défaut du cache L2 est faible.
Une telle optimisation ressemble beaucoup à un cache L2/L3 distribué, mais il y a quelques différences qui sont décrites dans le paragraphe précédent. Avec un L2 distribué, tout accès au L2 déclencherait une consultation de toutes les banques du L2. Avec un cache L3 virtualisé, ce n'est pas le cas. Le cache L2 associé au cœur est consulté, et c'est seulement en cas de défaut de cache que les autres caches L2 sont consultés. De plus, avec un cache L2 distribué, il n'y a pas de déplacement d'une ligne de cache entre deux banques, entre deux caches L2 physiques. Alors qu'avec un cache L3 virtualisé, c'est le cas en cas de remplacement d'une ligne de cache dans le cache L2.
Sur le processeur Telum 1, le partage du cache L2 est assez simple. Un cache L2 fait 32 mébioctets et est découpé en deux banques de 16 mébioctets. En temps normal, les premiers 16 mébioctets sont toujours associé au cache L2, au cœur associé. Les 16 mébioctets restants peuvent soit être attribués au cache L3, soit fusionnés avec les 16 premiers mébioctets. Dans le cas où le cœur associé est en veille, n'est absolument pas utilisé, les 32 mébioctets sont attribués au cache L3. Un partage assez simple, donc. Le partage du cache L2/L3 sur les processeurs Telum 2 n'est pas connu, il est supposé être plus flexible.
==Le réseau d'interconnexion entre cœurs==
Les systèmes avec plusieurs processeurs incorporent un réseau d'interconnexion pour connecter les processeurs entre eux, ainsi qu'à la mémoire RAM. Il s'agit d'un '''réseau d'interconnexion inter-processeur''', placé sur la carte mère. Les CPU multicœurs ont aussi un tel réseau d'interconnexion, pour relier les cœurs entre eux. La différence est que le réseau d'interconnexion est placé dans le processeur, pas sur la carte mère.
Les systèmes multi-cœurs modernes utilisent des réseaux d'interconnexion standardisés, les standards les plus communs étant l'HyperTransport, l'Intel QuickPath Interconnect, l'IBM Elastic Interface, le Intel Ultra Path Interconnect, l'Infinity Fabric, etc. Ils sont aussi utilisés pour faire communiquer entre eux plusieurs processeurs.
[[File:Architecture multicoeurs et réseau sur puce.png|centre|vignette|upright=1.5|Architecture multicoeurs et réseau sur puce]]
===Le bus partagé entre plusieurs cœurs===
Pour un faible nombre de coeurs/processeurs, la solution utilisée relie les processeurs entre eux grâce au bus mémoire. Le bus mémoire est donc un '''bus partagé''', avec tout ce que cela implique.
[[File:Architecture multicoeurs à bus partagé.png|centre|vignette|upright=2|Architecture multiprocesseurs à bus partagé]]
Pour les systèmes multicœurs, l'usage d'un bus partagé doit être adaptée pour tenir compte des caches partagés. Voyons d'abord le cas d'un CPU avec deux niveaux de cache, dont un cache L2 est partagé entre tous les cœurs. Les caches L1 sont reliés au cache L2 partagé par un bus, qui n'a souvent pas de nom. Nous désignerons le bus entre le cache L1 et le cache L2 : '''bus partagé''', sous-entendu partagé entre tous les caches. C'est lui qui sert à connecter les cœurs entre eux.
[[File:Architecture multicoeurs à bus partagé entre caches L1 et L2.png|centre|vignette|upright=2|Architecture multicoeurs à bus partagé entre caches L1 et L2]]
Un processeur multicœur typique a une architecture avec trois niveaux de cache (L1, L2 et L3), avec un niveau L1 dédié par cœur, un niveau L2 partiellement partagé et un L3 totalement partagé. Le bus partagé est alors difficile à décrire, mais il correspond à l'ensemble des bus qui connectent les caches L1 aux caches L2, et les caches L2 au cache L3. Il s'agit alors d'un ensemble de bus, plus que d'un bus partagé unique.
L'usage d'un bus partagé a cependant de nombreux défauts. Par exemple, les processeurs doivent se répartir l'accès au bus mémoire, il faut gérer le cas où deux processeurs accèdent au bus en même temps, etc. Pour cela, un composant dédié s'occupe de l'arbitrage entre processeurs. Il est généralement placé sur la carte mère de l'ordinateur, dans le ''chipset'', dans le pont nord, ou un endroit proche. Les processeurs doivent être conçus pour communiquer avec ce circuit d'arbitrage, ou du moins avoir un support minimal pour le multiprocesseur.
[[File:Intel486 System Arbitration.png|centre|vignette|upright=2|Système avec deux Intel486 - Arbitrage du bus mémoire.]]
D'autres défauts très importants seront abordés en détail dans le chapitre sur la cohérence des caches
===Le réseau d'interconnexion entre plusieurs cœurs===
Relier plusieurs cœurs avec des bus pose de nombreux problèmes techniques qui sont d'autant plus problématiques que le nombre de cœurs augmente. Le câblage est notamment très complexe, les contraintes électriques pour la transmission des signaux sont beaucoup plus fortes, les problèmes d'arbitrages se font plus fréquents, etc. Pour régler ces problèmes, les processeurs multicoeurs n'utilisent pas de bus partagé, mais un réseau d'interconnexion plus complexe.
Un exemple est le réseau du processeur Celle, utilisé sur la PS3. Les transferts sur ce bus se font par paquets de 128 bits. Le réseau était composé de 12 intermédiaires appelés ''Ramps''. Chaque intermédiaire communiquait avec deux autres ''ramps'' ce qui formait un anneau de ''ramps''. Les paquets de 128 bits circulaient sur cet anneau en passant d'un ''ramp'' au suivant, soit dans le sens horaires, soit dans le sens anti-horaire.
Avant d'entrer dans l'anneau, les processeurs envoient une requête à un circuit d'arbitrage, qui commande les transferts de données sur l'anneau. Si celui-ci accepte la requête, la donnée est envoyée au ''ramp'' et circule dans l'anneau. Il faut noter que le contrôleur mémoire a la priorité sur tout le reste. Le sens de transfert, horaire ou anti-horaire, est choisit de manière à prendre le chemin le plus court. Les paquets passent alors de ''ramp'' en ''ramp''. Chaque ''ramp'' reçoit le paquet, regarde l'adresse de destination du paquet, et décide de la suite. Soit le paquet lui est destiné, il le traite. Sinon, le il transmet au ''ramp'' suivant.
Le bus permet de transférer 4 paquets de 128 bits par cycle. Pour cela, les ''ramps'' avaient quatre bus, chacun de 128 bits : deux bus pour le sens horaire, et deux autres bus pour le sens anti-horaire. Il peut y avoir 3 transferts simultanés, tant que les paquets ne se recouvrent pas, mais les transferts sont de taille arbitraire. Le réseau d'interconnexion avait une fréquence de 1,6 GHz. Si vous faites les calculs, vous tomberez cependant sur un débit binaire théorique d'environ 300 gibioctets par secondes, qui n'est cependant jamais atteint en pratique, pour des raisons techniques liées à la cohérence des caches. La limite théorique est en pratique plus proche des 200 gibioctets par secondes, en tenant compte de ces limitations théoriques.
[[File:Réseau d'interconnexion du processeur CELL.png|centre|vignette|upright=2|Réseau d'interconnexion du processeur CELL]]
Un autre xemple de réseau d'interconnexion est celui des architectures AMD EPYC, de microarchitecture Zen 1. Elles utilisaient des chiplets, à savoir que le processeur était composé de plusieurs puces interconnectées entre elles. Chaque puce contenait un processeur multicoeurs intégrant un cache L3, avec un réseau d'interconnexion interne au processeur sans doute basé sur un ensemble de bus. De plus, les puces étaient reliées à une puce d'interconnexion qui servait à la fois d'interface entre les processeurs, mais aussi d'interface avec la R1AM, le bus PCI-Express, etc. La puce d'interconnexion était gravée en 14 nm contre 7nm pour les chiplets des cœurs.
{|
|[[File:AMD Epyc 7702 delidded.jpg|centre|vignette|upright=2|AMD Epyc 7702.]]
|[[File:AMD Epyc Rome Aufbau.png|centre|vignette|upright=2|Schéma fonctionnel de l'AMD Epyc.]]
|}
Le réseau d'interconnexion peut être très complexe, avec des connexions réseau, des commutateurs, et des protocoles d'échanges entre processeurs assez complexes basés sur du passage de messages. De telles puces utilisent un '''réseau sur puce''' (''network on chip''). Mais d'autres simplifient le réseau d'interconnexion, qui se résume à un réseau ''crossbar'', voire à des mémoires FIFO pour faire l'interface entre les cœurs.
Le problème principal des réseaux sur puce est que les mémoires FIFOs sont difficiles à implémenter sur une puce de silicium. Elles prennent beaucoup de place, utilisent beaucoup de portes logiques, consomment beaucoup d'énergie, sont difficiles à concevoir pour diverses raisons (les accès concurrents/simultanés sont fréquents et font mauvais ménage avec les ''timings'' serrés de quelques cycles d'horloges requis). Il est donc impossible de placer beaucoup de mémoires FIFO dans un processeur, ce qui fait que les commutateur sont réduits à leur strict minimum : un réseau d'interconnexion, un système d'arbitrage simple parfois sans aucune FIFO, guère plus.
===Les architectures en ''tile''===
Un cas particulier de réseau sur puce est celui des '''architectures en ''tile''''', des architectures avec un grand nombre de cœurs, connectés les unes aux autres par un réseau d'interconnexion "rectangulaire". Chaque cœur est associé à un commutateur (''switch'') qui le connecte au réseau d'interconnexion, l'ensemble formant une ''tile''.
[[File:Tile64-Tile.svg|centre|vignette|upright=1.5|''Tile'' de base du Tile64.]]
Le réseau est souvent organisé en tableau, chaque ''tile'' étant connectée à plusieurs voisines. Dans le cas le plus fréquent, chaque ''tile'' est connectée à quatre voisines : celle du dessus, celle du dessous, celle de gauche et celle de droite. Précisons que cette architecture n'est pas une architecture distribuée dont tous les processeurs seraient placés sur la même puce de silicium. En effet, la comparaison ne marche pas pour ce qui est de la mémoire : tous les cœurs accèdent à une mémoire partagée située en dehors de la puce de silicium. Le réseau ne connecte pas plusieurs ordinateurs séparés avec chacun leur propre mémoire, mais plusieurs cœurs qui accèdent à une mémoire partagée.
Un bon exemple d'architecture en ''tile'' serait les déclinaisons de l'architecture Tilera. Les schémas du-dessous montrent l'architecture du processeur Tile 64. Outre les ''tiles'', qui sont les éléments de calcul de l'architecture, on trouve plusieurs contrôleurs mémoire DDR, divers interfaces réseau, des interfaces série et parallèles, et d'autres entrées-sorties.
[[File:Tile64.svg|centre|vignette|upright=2|Architecture Tile64 du Tilera.]]
==Les interruptions inter-processeurs==
Les '''interruptions inter-processeurs''' sont des interruptions déclenchées sur un processeur et exécutées sur un autre. Elles sont très utiles pour le système d'exploitation, pour des raisons qu'on ne peut pas expliquer ici. Disons simplement qu'elles permettent de répartir des programmes/''threads'' sur plusieurs processeurs. Un programme/''thread'' est démarré par une interruption, et le système d'exploitation détermine sur quel processeur elle doit être exécutée. L'utilité des interruptions inter-processeur est assez variée. Autrefois, elles servaient aussi pour la cohérence des caches, mais nous détaillerons cela dans un futur chapitre.
Une interruption inter-processeurs peut être envoyée soit à un cœur bien précis, soit à n'importe quel cœur, soit à tous les cœurs, voire même revenir à l'envoyeur. Tout dépend de ce que décide le système d'exploitation. Les trois situations ne sont pas identiques, sur un point : comment préciser quel est le processeur de destination ? Si on envoie une interruption à un cœur bien précis, il faut préciser quel est le cœur qui réceptionne l'interruption. Pour cela, il n'y a pas 36 solutions : on numérote les processeurs/cœurs avec un '''numéro de processeur'''. Ce numéro leur est soit attribué au démarrage par le BIOS, soit est gravé dans leur silicium pour les processeurs multicœurs.
Pour le reste, les interruptions inter-processeurs sont identiques aux interruptions normales. Elles ont un système de priorités, certaines devant passer avant les autres, là encore défini par des ''Interrupt Request Levels'' (IRQLs) ou quelque chose de similaire. Il peut y avoir des interruptions inter-processeur de type logicielles, à savoir lancées par une instruction machine. Par exemple, sur les IBM System/360 et les ''mainframes'' z/Architecture, le processeur avait une instruction SIGNAL PROCESSOR pour déclencher des interruptions logicielles inter-processeur.
Pour générer des interruptions inter-processeur, le contrôleur d'interruption doit pouvoir rediriger des interruptions déclenchées par un processeur vers un autre. Pour expliquer comment, nous allons étudier le cas des CPU x86, mais les implémentations ARM ou autres sont très similaires. L'ancien contrôleur d'interruption 8259A ne gérait pas les interruptions inter-processeurs, ce qui fait que les cartes mères multiprocesseurs devaient incorporer un contrôleur d'interruption spécial en complément. Par contre, son successeur, l'APIC, les gérait nativement.
De nos jours, chaque cœur x86 possède son propre contrôleur d’interruption, le '''''local APIC''''', qui gère les interruptions en provenance ou arrivant vers ce processeur. On trouve aussi un '''''IO-APIC''''', qui gère les interruptions en provenance des périphériques et de les redistribuer vers les APIC locaux. L'IO-APIC gère aussi les interruptions inter-processeurs en faisant passer les interruptions d'un local APIC vers un autre. Tous les APIC locaux et l'IO-APIC sont reliés ensembles par un '''bus APIC''' spécialisé, par lequel ils vont pouvoir communiquer et s'échanger des demandes d'interruptions.
[[File:Contrôleurs d'interrptions sur systèmes x86 multicoeurs.png|centre|vignette|upright=1.5|Contrôleurs d’interruptions sur systèmes x86 multicœurs.]]
Le déclenchement d'une interruption inter-processeur se fait en écrivant dans un registre appelé l''''''Interrupt Command Register'''''. Un détail important est que l'écriture se fait dans le ''local APIC'' de l'envoyer, du processeur qui veut envoyer une interruption, pas dans le registre du processeur qui doit réceptionner l'interruption ! Le registre est composé de deux registres de 32 bits, et mémorise : le numéro du processeur de destination, le mode de transfert (à tous, à un cœur, etc), le numéro du vecteur d'interruption (pour préciser quelle interruption exécuter), et quelques informations supplémentaires. À charge de l'IO-APIC de faire ce qu'il faut en fonction du contenu de ce registre.
==Le multiprocesseur/multicœur asymétrique==
Jusqu'ici, nous nous sommes concentrés sur des processeurs dont les cœurs étaient tous identiques. Mais ce n'est certainement pas une obligation. On peut très bien regrouper plusieurs cœurs très différents, par exemple un cœur principal avec des cœurs plus spécialisés autour. Il faut ainsi distinguer le '''multicœurs symétrique''', dans lequel on place des processeurs identiques sur la même puce de silicium, du '''multicœurs asymétrique''' où les cœurs ne sont pas identiques. Et il en est de même sur les systèmes avec plusieurs processeurs : on parle de '''multiprocesseur symétrique''' si les processeurs sont identiques, '''multiprocesseur asymétrique''' s'ils sont différents.
===Le processeur CELL de la PS3===
Un exemple de multicoeurs asymétrique est celui du processeur CELL de la console de jeu PS3. Il était conçu spécifiquement pour cette console. Il intègre un cœur principal POWER PC v5 et 8 cœurs qui servent de processeurs auxiliaires. Le processeur principal est appelé le PPE et les processeurs auxiliaires sont les SPE. Les SPE sont reliés à une mémoire locale (''local store'') de 256 kibioctets qui communique avec le processeur principal via un bus spécial. Cette fois-ci, les coprocesseurs sont intégrés dans le même processeur.
Les SPE communiquent avec la RAM principale via des contrôleurs DMA. Les SPE possèdent des instructions permettant de commander leur contrôleur DMA et c'est le seul moyen qu'ils ont pour récupérer des informations depuis la mémoire. Et c'est au programmeur de gérer tout ça ! C'est le processeur principal qui va envoyer aux SPE les programmes qu'ils doivent exécuter. Il délègue des calculs aux SPE en écrivant dans le local store du SPE et en lui ordonnant l’exécution du programme qu'il vient d'écrire.
[[File:Schema Cell.png|centre|vignette|upright=2|Architecture du processeur CELL de la PS3. Le PPE est le processeur principal, les SPE sont des processeurs auxiliaires qui comprennent : un ''local store'' noté LS, un processeur noté SXU, et un contrôleur DMA pour échanger des informations avec la mémoire principale.]]
===Les configurations Big.Little===
Les processeurs Intel modernes utilisent deux types de cœurs différents : des cœurs P et des cœurs E. Le P est pour ''Performance'', le E est pour "Efficiency". Les deux ont le même jeu d'instruction : ce sont des processeurs x86. Par contre, ils ont des microarchitectures différentes. Les cœurs E sont optimisés pour un budget en transistors serré : ils utilisent peu de place, mais sont moins performants et n’implémentent pas certaines extensions x86 (du moins, ils n'implémentaient pas ces extensions, les choses changent). Les coeurs P ont un budget en transistors plus conséquent, ce qui fait qu'ils prennent plus de place, chauffent plus, mais sont plus performants.
[[File:Intel-ARL-Core-Layout.png|centre|vignette|upright=2|Die des processeurs Intel de microarchitecture ''Raptor lake'' et ''Arrow lake''.]]
Et Intel n'est pas le seul à utiliser cette technique : ARM a fait pareil avec ses CPU d'architecture ''Big-little''. Il n'est pas clair si de telles organisation sont du multicœur symétrique ou asymétrique. Le jeu d'instruction est identique, sauf éventuellement pour certaines extensions comme l'AVX. Les deux coeurs n'ont pas les mêmes performances, mais est-ce suffisant ? La terminologie n'est pas claire.
La répartition du cache entre les coeurs E et P n'est pas la même. Elle a beaucoup évoluée dans le temps, et n'est pas la même selon les microarchitectures. Pour la microarchitecture ''Arrow Lake'' d'Intel, les coeurs partageaient tous le cache L3, avec des caches L2 différents pour les coeurs E et P. Les coeurs P avaient chacun leur propre cache dédié, alors que les coeurs E étaient regroupés en groupes de 4 coeurs qui partageaient un cache L2.
[[File:Repartition du cache entre coeurs E et P des CPU Intel Arrow Lake.png|centre|vignette|upright=2.5|Répartition du cache entre coeurs E et P des CPU Intel Arrow Lake]]
Les microachitectures ''Lunar Lake'' et ''Meteor Lake'', déstinées aux PC portables, utilisaient une organisation différente. La microarchitecture ''Lunar Lake'' avait un uniquae ''Cluster'' de 4 coeurs E, qui n'était pas connecté au cache L3. Les coeurs E avaient donc accès à leur cache L1, un cache L2 partagé entre tous les coeurs E, et c'est tout. Seuls les coeurs P pouvaient exploiter le cache L3.
[[File:Repartition du cache entre coeurs E et P des CPU Intel Lunar lake 01.png|centre|vignette|upright=2.5|Repartition du cache entre coeurs E et P des CPU Intel Lunar lake]]
La microarchitecture ''Meteor Lake'' avait elle un système hybride entre les deux précédents. Tous les coeurs avaient accès au cache L3, sauf deux coeurs E séparés des autres, qui avaient droit à leur propre ''cluster''. Ils étaient désignés comme des "coeurs ultra-basse consommation" par Intel, mais c'était des coeurs E classiques, seul leur connexion au cache était différente. Il faut noter que le GPU intégré était aussi dans ce cas, il n'avait pas accès au cache L3.
[[File:Repartition du cache entre coeurs E et P des CPU Meteor Lake.png|centre|vignette|upright=3|Repartition du cache entre coeurs E et P des CPU Intel Meteor Lake]]
Ne pas relier certains coeurs au cache L3 permet de désactiver le cache L3 quand celui-ci n'est pas nécessaire. Si le processeur n’exécute que des programme légers, seuls les coeurs non-reliés au cache L3 seront utilisés. Le cache L3 peut alors être éteint, désactivé, mis en veille. Le gain en consommation d'énergie est alors plus que conséquent.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les architectures parallèles
| prevText=Les architectures parallèles
| next=Architectures multithreadées et Hyperthreading
| nextText=Architectures multithreadées et Hyperthreading
}}
</noinclude>
kjdsztv5x0dxw10tt7olnirhv7yvk02
Vocabulaire anglais-français à l'intention des apprenants avancés/B
0
78007
775650
767822
2026-11-03T07:56:18Z
Elnon
41284
/* B */ Complément
775650
wikitext
text/x-wiki
{{../Index}}
=== B ===
* B : (note scolaire) bien
* baboon (n) : babouin
* baby (n) (pl. babies) : bébé - to expect a baby : attendre un bébé - unborn baby : enfant à naître, futur bébé - baby boom : boum des naissances, explosion démographique (f) - baby boomer : enfant de l’après-guerre, enfant du boum des naissances - baby carriage : landau - baby carrier : porte-bébé (le harnais) - baby pacifier : tétine de bébé - baby scale (amér.) / baby scales (brit., pl.) : pèse-bébé - baby seat : porte-bébé (le siège)
* babyish (a) : de bébé, puéril(e)
* baby-sit / babysit (v) (intr.) : 1/ garder des enfants ; 2/ [instrument de mesure automatique] veiller
* baby-sitter / babysitter (n) : gardien(ne) (d’enfants) (Q)
* baby-sitting / babysitting (n) : (la) garde d’enfants
* bachelor (n) (fa) : 1/ célibataire (en parlant d’un homme) - He was still a bachelor at 40 : Il était encore célibataire à 40 ans ; 2/ licencié - bachelor of Arts : licencié ès lettres - bachelor’s degree : licence (universitaire) (f)
* back (postp.) : to be back : être de retour
* back (a) : 1/ arrière, de derrière - back alley : allée derrière la maison - back office : a/ bureau du fond, deuxième ligne, commerciaux (pl.) ; b/ post-marché (off.), traitement administratif des opérations - back light : a/ contre-jour, b/ (Inf.) rétro-éclairage - back slang : (le) verlan ; 2/ de retour - back link : (Toile) lien retour
* back (n) : dos - to have someone‘s back (fig.) : toujours soutenir qn
* back (v) (tr.) : 1/ soutenir, appuyer - to back the right horse : miser sur le bon cheval ; 2/ financer
* backache (n) : mal de dos - to give sb backache : donner mal au dos à qn
* backbiter (n) : diffamateur/-trice, médisant(e) (n)
* backboard (n) : panneau de basket
* backbone (n) : colonne vertébrale (f)
* backbreaking (a) : éreintant(e)
* back-burner / backburner (n) : - to be pushed to the backburner : être mis en veilleuse, être relégué au second plan
* back channel / backchannel (nc/n) : 1/ canal de retour ; 2/ (Polit.) canal de communication secret, canal détourné, canal non officiel - to use backchannels : faire appel à des canaux non officiels
* backcomb (v) (tr.) : crêper - to backcomb one's hair : crêper ses cheveux
* backdrop (n) : toile de fond (f)
* back-engineer (v) (tr.) : désosser, démonter (une machine)
* backer (n) : 1/ partisan, allié ; 2/ commanditaire, bailleur de fonds
* backfire (n) : 1/ retour de flamme ; 2/ retour de manivelle
* backfire (v) (intr.) : provoquer un résultat contraire, avoir l’effet inverse, se retourner contre son auteur
* back-flow / backflow (n) : refoulement, reflux
* back-formation (n) : dérivation régressive (f) (from, de) - ‘intervene’ is a back-formation from ‘intervention’ : « intervene » est une dérivation régressive de « intervention »
* back garden (n) (brit.) : jardin (de derrière)
* background (n) : 1/ fond, arrière-plan ; 2/ milieu - social background : milieu social
* backhand (n) : revers (au tennis) - the two-handed backhand : le revers à deux mains
* backing (a) : d’accompagnement - backing vocals : chœurs (pl.)
* backing (n) : 1/ soutien, appui, support ; 2/ financement ; 3/ (Mus.) accompagnement
* back into (postp. + prép.) : - to rework a part back into tolerance : remettre une pièce à la cote
* backlight (n) : 1/ contre-jour ; 2/ (Inf.) rétro-éclairage (aussi back light)
* backlighting (n) 1/ éclairage en contrejour ; 2/ (Info.) éclairage par l’arrière, rétro-éclairage
* backlit (a) : 1/ à contre-jour ; 2/ rétro-éclairé, éclairé par l’arrière - backlit by the setting sun : éclairé depuis l’arrière par le soleil couchant - backlit clouds nuages éclairés par l’arrière
* backlog (n) : 1/ travail en retard, travaux (pl.) en souffrance, retard (de fabrication) - backlog of orders : commandes en attente, commandes en souffrance - order backlog carnet de commandes, commandes en portefeuille ; 2/ arriéré (n m)
* backpack (n) : 1/ sac à dos (de voyageur, randonneur) ; 2/ cartable (d’écolier) - Go pack your backpack for school! : Va préparer ton cartable pour l’école !
* backpack (v) (intr.) : voyager sac au dos ; 2/ faire de la randonnée
* backpacker (n) 1/ routard(e) (m/f) ; 2/ randonneur/-euse
* backpacking (n) : 1/ (les) voyages sac au dos ; 2/ (la) randonnée
* backpedal (v) (intr.) : faire marche arrière
* backroom (n) : réserve (f) (la pièce) - backroom stock : (les) réserves, (les) stocks
* backscratcher (n) : gratte-dos (l’instrument) (masc.) (aussi back scratcher)
* back seat / backseat (n) : 1/ siège arrière ; 2/ rôle secondaire - to take a backseat : s’effacer (au fig.)
* backside (n) : arrière-train (d’un animal), derrière (d’une personne), postérieur (d’une personne), fondement (hum.), fesses (f pl.)
* backspace (n) : (Inf.) (sur clavier d’ordinateur) recul d’une espace, retour arrière - destructive backspace : retour arrière destructif, retour arrière avec effacement - (the) backspace key (la) touche de déplacement arrière, (la) touche flèche gauche, (la) touche espace arrière
* backspace (v) (intr.) : reculer d'une espace
* back-stabbing / backstabbing (a) : qui poignarde dans le dos, traître
* backstage (a) : de coulisse - backstage quarrels : querelles de coulisse
* backstage (adv.) : derrière le rideau, dans les coulisses - to go backstage : aller dans les coulisses
* backstop (n) : 1/ (Baseball) receveur ; 2/ filet de sécurité - to act as a backstop to… : faire office de filet de sécurité pour… ; 3/ pare-balles (masc.) (dispositif anti-balles dans un stand de tir)
* backstop (v) (tr.) : 1/ (Fin.) servir de filet de sécurité à ; 2/ servir de renfort à, venir renforcer
* back then (loc. adv.) : à l’époque, en ce temps-là
* back to (loc. prép.) : de retour à - [car] to have been back to the garage 17 times : [voiture] être retournée chez le garagiste 17 fois - to send a pressure gauge back to the manufacturer : retourner un manomètre au fabricant
* back-to-back (ac) : consécutif/-ive
* backtrack (v) (intr.) : 1/ (au prop.) revenir sur ses pas, rebrousser chemin ; 2/ (au fig.) faire machine arrière - to backtrack on an agreement : revenir sur un accord
* back-up / backup (a) : 1/ de recul, de marche arrière - back-up lights : feux de recul, feux de marche arrière ; 2/ auxiliaire, de secours, de rechange - backup saving : (Inf.) sauvegarde (f) - backup server : serveur de sauvegarde
* back-up / backup (n) : 1/ assistance technique (f), secours, soutien ; 2/ renforts (pl.) ; 3/ doublure (f), remplaçant(e) ; 3/ (Inf.) sauvegarde (f), recopie (f) ; 4/ (Circul.) bouchon, embouteillage
* back up (v) : 1/ (tr.) faire reculer ; (intr.) faire marche arrière ; 2/ (Inf.) (tr.) sauvegarder ; (intr.) faire une sauvegarde, faire une copie de secours
* back wages (n pl.) : arriérés de salaire, salaires impayés - to demand back wages : exiger le versement des salaires impayés
* backward (a) : 1/ qui est en arrière ; 2/ qui va en arrière ; 3/ retardé, attardé ; 4/ rétrograde, arriéré
* backward-compatible (ac) : (Inf.) compatible en amont, compatible avec les versions précédentes, à compatibilité ascendante, rétrocompatible
* backwardness (n) : 1/ retard, sous-développement - technological backwardness : retard technologique ; 2/ arriération (f)
* back wash (nc) : ressac
* backwoodsman (n) : homme des bois - the laconic backwoodsman : l’homme des bois peu causant
* backyard (n) : arrière-cour
* bacon (n) : lard, bacon - rasher of bacon : tranche de lard, tranche de bacon - to bring home the bacon : faire bouillir la marmite
* bacterial (a) : bactérien(ne) - antibacterial gel : gel antibactérien
* bacterium (n) (pl. bacteria) : bactérie (f)
* bad (a) : mauvais(e) - to be bad at swordplay : ne pas être bon à l’épée - bad habits : mauvaises habitudes (f pl.) - bad movie : daube (f) (fam.) - a bad patch : un mauvais quart d’heure
* bad-ass / badass (a) (arg. amér.) : agressif/-ive, teigneux/-euse
* bad-ass / badass (n) (arg. amér.) : personne qui déchire (f)
* bade (prét. de to bid)
* badge (n) : écusson - the blazer with the badge (brit.) : la veste croisée avec son écusson
* badger (n) : blaireau - honey badger : ratel, zorille du Cap
* badly (adv.) : 1/ (adv. de manière) mal - I knew this would end badly : Je savais que cela se terminerait mal - to augur badly : augurer mal (for sb, pour qn) ; 2/ (adv. de degré) - The door badly needed a coat of paint : La porte avait grand besoin d’une couche de peinture
* baffle (v) (tr.) : déconcerter, laisser perplexe
* bag (n) : 1/ sac, sachet - barf bag : sac à vomi - carrier bag : sac de courses - designer bag : sac de style, sac de créateur - drawstring bag : sac à cordon coulissant, sac à cordonnet - mart bag : sac de courses - scruff bag : personne débraillée (f) - tote bag : sac fourre-tout (masc.), sac cabas ; 2/ housse (f) - body bag : housse mortuaire - foldable garment bag : housse à vêtements pliable ; 3/ poche (f) (sous les yeux) - to have eye bags : avoir des poches sous les yeux ; 4/ - bags of… : des masses de… , plein de… - We’ve got bags of time : Ce n’est pas le temps qui nous manque
* bag (v) (tr.) : emballer, mettre en sac
* baggage (n) : 1/ (ind. sing.) bagages (pl.) ; 2/ passif (n), casseroles (f pl.) (fig.) (d’une personne) ; 3/ (a baggage) vaurienne (f) ; friponne (f) ; putain (f)
* baggie (n) (amér.) : sac en plastique, poche
* baggy (a) : [pantalon] large, ample
* baggy-kneed (ac) : [pantalon] qui fait des poches aux genoux
* bail (n) (fa) : 1/ liberté sous caution (f) - He was granted bail : Il fut mis en liberté sous caution - on bail : sous caution ; 2/ caution (f) (contre libération)
* bailee (n) : dépositaire (d’un bien meuble)
* bailiff (n) : huissier - house calls by bailiffs : visites de huissiers
* bailor (n) : déposant(e)
* bail out (v) (intr.) : sauter d’un avion, évacuer un avion, s’éjecter d’un avion
* bait (n) : appât, leurre, amorce (f)
* bait (v) (tr.) : 1/ appâter ; 2/ (fig.) taquiner, embêter, asticoter (syn. to annoy)
* baize (n) : feutre, feutrine (f) - a piece of baize : un morceau de feutrine - green baize : (de la) feutrine verte (on card table, sur une table de jeu, etc.) ; the green baize (fig.) : le tapis vert, les tapis verts, le jeu - a green baize table : une table de jeu
* baize-covered (ac) : feutré
* bake (v) (tr.) : cuire (au four), faire cuire (au four)
* bakehouse (n) : fournil
* baker (n) : boulanger
* bakery (n) : boulangerie (f)
* baking (n) : cuisine (f) (au four) - baking soda (amér.) : bicarbonate de soude (l’agent levant)
* balance (n) (fa p) : 1/ balance (f) ; 2/ équilibre - He lost his balance : Il perdit l'équilibre - to strike a balance beween… and… : trouver le juste milieu entre… et… ; 3/ solde (d’un compte en banque) - account balance : balance comptable - balance sheet : bilan comptable ; 4/ (expressions) - on balance (loc. adv.) : tout bien considéré, tout compte fait, au fond (syn. all things considered)
* balance (v) (tr.) (fa) : (tr.) tenir en équilibre, mettre en équilibre, équilibrer ; (intr.) s'équilibrer, être en équilibre
* balanced (a) : équilibré
* balcony (n) (pl. balconies) : balcon
* bald (a) : chauve
* bald-faced (ac) : éhonté (syn. bare-faced)
* baldie (n) (fam.) : chauve (n), crâne pelé
* balding (a) : qui perd ses cheveux, qui devient chauve - to be balding : perdre ses cheveux, devenir chauve, se dégarnir, se déplumer (fam.) - balding forehead : front dégarni
* baldness (n) : calvitie (f)
* bale (n) : botte (f) (de foin), ballot (de paille) - hay bale : botte de foin (aussi bale of hay) - straw bale : ballot de paille - straw bale house : maison en ballots de paille
* baleful (a) (lit.) : funeste, maléfique
* balk (v) (intr.) : regimber (at sth, devant qch)
* ball (n) (fa) : 1/ balle (f), ballon - punching ball : a/ ballon de frappe ; b/ (fig.) tête de turc - ball game : jeu de ballon - ball girl : (Tennis) ramasseuse de balles - ball kid : (Tennis) ramasseur de balles ; 2/ boule (f) - ball of flames : boule de feu - wrecking ball : boule de démolition - ball lightning : (la) foudre en boule ; 3/ boulet ; 4/ bille (f) - steel ball : bille d’acier - ball bearing : roulement à billes - ball bushes : douilles à billes - ball pen : stylo à billes ; 5/ boulette (f) (de viande) - meat ball : boulette de viande ; 6/ couille (f) (vulg.) - ball breaker : casse-couille (vulg.), rabat-joie (syn. ball buster) ; 7/ bal - masquerade ball : bal masqué - to have a ball (amér.) : prendre son pied (fam.) - to have a real ball : s’amuser vachement bien, passer un très bon moment, bien rigoler ; 8/ (expressions) - to be on the ball : être à la hauteur ; être au courant de ce qui se fait
* ball (v) : (tr.) mettre en boule ; (intr.) se mettre en boule
* ball breaker / ballbreaker (n) (vulg.) : casse-couille (vulg.), rabat-joie (syn. ball buster / ballbuster)
* ball buster / ballbuster (n) (vulg.) : casse-couille (vulg.), rabat-joie (syn. ball breaker / ballbreaker) - I’d hate to be a ball buster : Je ne veux pas passer pour un casse-couille
* ballerina (n) : ballerine (f)
* ballistic (a) : 1/ balistique (noter la différence d’orthographe) - intercontinental ballistic missile : missile balistique intercontinental ; 2/ (fig.) dingue (fam.) - to go ballistic over sth : prendre qch très mal
* ball-like (ac) : en forme de boule - ball-like object : objet en forme de boule
* balloon (n) : ballon de baudruche - Buy some balloons for the party : Achète quelques ballons de baudruche pour la fête - to pop a balloon : faire éclater un ballon - hot air balloon : montgolfière (f), ballon à air chaud - helium balloon : ballon à l'hélium - meteorological balloon : ballon météorologique - tethered balloon : ballon captif - weather balloon : ballon météorologique
* balloon (v) (tr. et intr.) : gonfler, grossir démesurément
* ballooning (ppa) : qui gonfle, qui grossit démesurément
* ballooning (n) : - hot air ballooning : (le) vol en montgolfière - ballooning company : société de montgolfières - ballooning enthusiast : fana du vol en montgolfière
* ballot (n) (fa) : 1/ scrutin ; 2/ vote - to cast a ballot : voter - absentee ballot : vote par correspondance - in-person ballot : vote en bureau de vote
* ballpoint (n) : stylo à bille, stylo bille - ballpoint pen : stylo à bille (syn. brit. : Biro ou biro, nom de marque)
* ballsy (a) (fam.) : [musique] pour guincher - I need something loud and ballsy : Il me faut un truc qui gueule et sur lequel guincher
* ballyhoo (n) : battage (publicitaire ou médiatique)
* ballyhoo (v) (prét. et ppassé -hooed, gérond. et p. présent -hooing) (arg. amér.) (tr.) : faire tout un battage autour de…
* Baltic (a) : balte - the Baltic states : les pays baltes
* Baltic (n pr.) : (la) Baltique, (la) mer Baltique
* baluster (n) : balustre - carved baluster : balustre sculpté
* balustrade (n) : balustrade (f)
* bamboo (n) : bambou - bamboo house : maison de bambou, maison en bambou
* bamboozle (v) (tr.) : mystifier, embobeliner, duper
* ban (n) (fa) : interdiction (f), mise à l’index - topic ban : (WP) restriction thématique (f)
* ban (v) (tr.) (fa) : interdire, mettre à l'index
* banana (n) : banane (f) - banana republic : république bananière (f)
* band (n) (fa p) : 1/ bande (f) (de tissu, d’élastique, de métal) - Alice band : serre-tête (masc.) - conveyor band : tapis roulant, courroie transporteuse (f), transporteur à bande, bande de manutention - hair band : serre-tête - head band : serre-tête (aussi headband) - rubber band : élastique (n) - watch band : bracelet de montre - wide band : large bande ; 2/ orchestre - big band : grand orchestre (jazz) - brass band : fanfare (f) ; 3/ groupe vocal - touring band : groupe en tournée - boys' band : groupe vocal de garçons - band leader : chef de groupe (musical), chef d’un groupe musical (aussi bandleader) - band manager : agent artistique de groupe
* Band-Aid (n pr.) / band-aid (n com.) : (le) Tricosteril - a band-aid : a/ un pansement (adhésif); b/ (fig.) une solution de fortune
* bandleader (n) : chef de groupe (musical), chef d’un groupe (musical) (aussi band leader)
* bandmate (n) : membre de groupe (musical), chef d’un groupe musical (aussi band mate)
* bandstand (n) : kiosque (à musique)
* bandwagon (n) (amér.) : char des musiciens (en tête d’une cavalcade), char de la victoire - to jump on the bandwagon : a/ (au propre) grimper sur le char de la victoire ; b/ (au figuré) voler au secours de la gloire, suivre le mouvement, emboîter le pas, prendre le train en marche - the bandwagon effect : l’effet de mode, l’effet d’entraînement, l’effet boule de neige - a bandwagon jumper : un opportuniste, une opportuniste
* bandwidth (n) (Rés.) : 1/ largeur de bande ; 2/ bande passante (f), débit
* bandy (a) : arqué - bandy legs : jambes arquées (f pl.)
* bandy-legged (a) : 1/ aux jambes arquées ; 2/ bancal(e)
* bane (n) : 1/ poison - rat’s bane : (de la) mort aux rats ; 2/ (fig.) fléau, malheur - public bane : fléau public - He was the bane of his mother : Il a empoisonné la vie de sa mère
* baneful (a) 1/ empoisonné ; 2/ pernicieux/-euse, funeste
* bang (n) : détonation (f), claquement, fracas, boum - gang bang : partouze (f) - all-girl gang bang : partouze lesbienne (f)
* bang (v) (tr.) : 1/ cogner, heurter, frapper ; 2/ claquer, faire claquer - to bang one’s desk : faire claquer son pupitre ; 3/ (arg.) (vulg.) baiser, se faire (une fille)
* bangs (n pl.) (amér.) : frange (f) (de cheveux) - I like having bangs : J’aime avoir une frange
* banger (n) (arg.) : saucisse (f) - bangers and mash : saucisses et purée, saucisses-purée
* Bangladesh (n pr.) : (le) Bangladesh
* Bangladeshi (a) : bangladais(e) (sans maj.)
* Bangladeshi (n pr.) : Bangladais (le ressortissant)
* bangle (n) : petit bracelet
* banish (v) (tr.) : bannir - to banish waste : bannir le gaspillage
* banister(s) (n) : rampe d’escalier
* bank (n) (fa p) : 1/ talus ; 2/ rive (f) (d’une rivière) ; 3/ banque (f) - the Bank for International Settlements : la Banque des règlements internationaux - the bank of England : la banque d'Angleterre - central bank : banque centrale - the European Central Bank : la Banque centrale européenne - merchant bank : banque d’affaires - bank holiday : jour férié - bank run : panique bancaire (f), ruée bancaire (f), course aux guichets (f) ; 4/ maison de jeu (f), banque - to break the bank (fig.) : faire sauter la banque ; 5/ - image bank : banque d’images ; 6/ conteneur - bottle bank : conteneur pour le verre usagé ; 7/ banque (iconographique, alimentaire) - artwork bank : banque d’images, fonds iconographique - food bank : banque alimentaire - to visit a food bank : passer à la banque alimentaire
* bank (v) (intr.) : 1/ compter, miser (on, sur) ; 2/ (Aéro.) faire un virage sur l’aile
* bankable (a) : 1/ négociable en banque ; 2/ apte à bénéficier d’un financement, pouvant bénéficier de concours bancaires / de l’appui d’une banque - bankable project : projet susceptible de bénéficier de concours bancaires ; 3/ [acteur] qui facilite le financement d’un film, qui est susceptible de rapporter de l’argent, dont le succès est assuré, qui est une valeur sûre
* banker (n) : banquier
* banking (n) : (la) banque, (les) activités bancaires (f pl.), (les) opérations bancaires (f pl.) - home banking : (la) banque à domicile - banking corporation : compagnie bancaire (f) - banking institution : établissement bancaire - banking regulator : organisme de réglementation bancaire - the global banking sector : le secteur bancaire mondial - electronic banking services : services bancaires électroniques
* bankrupt (a) : en faillite - to go bankrupt : faire faillite, se retrouver sur la paille
* bankrupt (v) (tr.) : mettre en faillite, mener à la faillite, mettre sur la paille (fam.)
* bankruptcy (n) : faillite (f) - to declare bankruptcy : déclarer faillite, faire faillite
* bankrupted (ppa) : mis(e) en faillite, en faillite
* banner (n) : bannière (f)
* bantam (n) : coq nain, poule naine (f)
* bantering (a) : [ton, air] plaisantin
* baptistery / baptistry (n) : 1/ (le bâtiment) baptistère ; 2/ fonds baptismaux (pl.)
* bar (n) : 1/ barre (f) - anti-roll bar (Auto.) : barre antiroulis - dejamming bar : barre de décoincement - miner’s bar : barre à mine - pinch bar : pied-de-biche - tamping bar : bourroir - bar code : code à barres, code-barres - bar graph : diagramme à barres : 2/ barreau - to be behind bars : être derrière les barreaux - to find oneself behind bars : se retrouver derrière les barreaux ; 3/ tablette (f) - a bar of chocolate : une tablette de chocolat ; 4/ bar - bar tender : responsable du bar - bar tending : tenue d’un bar ; 5/ obstacle (to, à)
* barbarian (n pr) : Barbare - Conan the Barbarian : Conan le Barbare
* barbarism (n) : barbarisme
* barbed (a) : barbelé - barbed wire : fil de fer barbelé
* barber (n) : 1/ barbier ; 2/ coiffeur hommes - the barber’s chair (brit.) / the barber chair (amér.) : le fauteuil du coiffeur - barber shop : salon de coiffure hommes
* barbie (n) (Aus.) (abr. fam. de barbecue) : barbecue
* barbiturate (n) : barbiturique
* bard (n) : 1/ barde, chantre ; 2/ barde (de lard)
* bare (a) : 1/ nu(e), dénudé, dévêtu(e) - bare wire end : extrémité de fil électrique dénudée ; 2/ simple, seul
* bare (v) (tr.) : dénuder, dévêtir, découvrir
* bare-faced (ac) : éhonté - bare-faced liar : menteur éhonté (syn. bald-faced)
* barefoot (a) : (qui est) pieds nus, déchaussé
* barely (adv.) : à peine, tout juste
* barf (n) (arg. amér.) : (du) vomi, (du) dégulis (syn. vomit) - barf bag : sac à vomi
* barf (v) (arg. amér.) (intr.) : vomir, dégobiller (fam.), gerber (arg.)
* bargain (n) : affaire (f), occasion (f) - You got the worse of the bargain : Tu t’es fait rouler
* bargain (v) (tr.) : marchander, négocier
* barge (n m) : chaland, péniche (f) - coal-laden barge : chaland empli de charbon - barge pole (brit.) : perche de batelier - I would not touch it with a barge pole : Je n'y toucherais pas même avec des pincettes
* bark (n) : écorce (f)
* bark (v) : (intr.) aboyer
* barley (n) : orge - barley beer : bière d’orge
* barn (n) : grange (f) - aisled barn : grange à bas-côtés - tithe barn : grange dimière, grange à dîmes - barn stormer : acteur ambulant (aussi barnstormer)
* barnacle (n) : balane (f), bernacle (f), anatife
* barnstormer (n) : 1/ acteur ambulant ; 2/ (Pilotage) acrobate ambulant ; 3/ orateur électoral (aussi barn stormer)
* baron (n) : 1/ (brit.) baron ; 2/ (fig.) baron, magnat - press baron : baron de la presse, magnat de la presse
* baroness (n) (brit.) : baronne (f)
* baronet (n) (brit.) : baronnet
* barracks (n) (pl.) (fa) : caserne (sing.) (f)
* barrel (n) : 1/ tonneau, fût, barrique (f) - barrel maker : tonnelier ; 2/ baril ; 2/ canon (d’un fusil)
* barrel roll (n) : tonneau (la figure) - to do a barrel roll : (Av.) faire un tonneau
* barrel-shaped (ac) : en forme de baril
* barrette (n) (amér.) : barrette (f)
* barrier (n) : barrière (f) - to be a barrier : faire barrière (to, à) - to set up barriers : installer des barrières - crash barrier (brit.) : glissière de sécurité - crush barrier : barrière anti-écrasement - floating barrier : barrière flottante - insulation barrier : écran isolant - the language barrier : l’obstacle de la langue - the sound barrier : (le) mur du son
* barring (prép.) : sauf, excepté - barring accidents : sauf accident, à moins d’un accident - barring miracles : sauf miracle - barring the unforeseen : sauf imprévu
* barrister (n) (brit.) : avocat, membre du Barreau - barrister in business law : avocat en droit des affaires
* barter (v) (tr.) : troquer, échanger (for, contre)
* barter (n) : troc
* base (n) : 1/ base (f) - air base : base aérienne - Mars base : base martienne - moon base : base lunaire - rear base : (Milit.) base arrière ; 2/ socle, fondement, support ; 3/ base (de données) - term base : base terminologique
* base (a) : de base - base wages (pl.) : salaire (sing.) de base
* base (v) (tr.) : fonder
* based (ppa) : 1/ (+ in) basé à (anglicisme) (sens géographique), ayant son siège (social) à, sis(e) à ; 2/ (+ on) fondé sur, reposant sur
* -based (ppa) (en compo.) : (selon le cas) à base de, reposant sur, implanté à, à + n pl, en + n sing. - British-based : implanté en Grande-Bretagne - community-based : communautaire - computer-based : géré par ordinateur - dollar-based : a/ fondé sur le dollar ; b/ exprimé en dollars, libellé en dollars - electron-tube-based : muni(e) de tubes à vide - facts-based : reposant sur des faits, concret/-ète - gender-based : déterminé par le sexe - Moscow-based : moscovite - multiprocessor-based : à multiprocesseurs - office-based : de bureau - petroleum-based : dérivé du pétrole - school-based : en milieu scolaire - space-based : implanté dans l’espace - team-based : en équipe - web-based : présent(e) sur la Toile - work-based : en milieu de travail, en entreprise
* based on (loc. prép.) : à partir de
* baseline (n) : 1/ point de comparaison ; 2/ (Baseball) ligne des bases ; 3/ (Tennis) ligne de fond ; 4/ slogan
* baseline (a) : de base, basique - baseline data : données de base (anton. endline data : données finales) - baseline product : produit de base, produit de référence
* basement (n) : sous-sol
* bashful (a) : timide
* bash (n) : coup
* bash (v) (tr.) : cogner, frapper, tabasser
* bashing (n) : tabassage
* basic (a) (fa) : 1/ essentiel(le), fondamental(e), élémentaire, constitutif/-ive, de base, basique (anglic.) - basic English : (l’)anglais de base - basic parts : pièces élémentaires, pièces constitutives ; 2/ (Chimie) basique - basic medium : milieu basique
* basically (adv.) : fondamentalement
* basics (n pl.) : connaissances de base (f pl.), notions essentielles (f pl.), rudiments - to master the basics : maîtriser les bases
* basilica (n) : basilique (f)
* basin (n) : 1/ bassine (f), cuvette (f) ; 2/ (dans la cuisine) évier ; 3/ (dans la salle de bain(s)) lavabo - hand basin : lavabo - wash basin : lavabo ; 4/ (au sens géographique) bassin - drainage basin (Hydro.) : bassin versant ; 5/ (au sens portuaire) bassin - tidal basin : avant-port
* basis (n) : base (f) - on the basis of… : reposant sur…
* bask (v) (intr.) : se chauffer - to bask in the sun : se dorer au soleil - to bask in success : se reposer sur ses lauriers
* basket (n) : corbeille (f), panier - trolley basket : panier de chariot (de supermarché) - waste paper basket : corbeille à papier - basket case (fig.) : cas désespéré, catastrophe absolue (f) - basket weaver : vannier
* basketballese (n) : langue du basket-ball
* bass (a) : (Mus.) de basse - bass guitar : guitare basse - bass player : bassiste
* bassist (n) : (Mus.) bassiste
* bastard (n) : 1/ bâtard ; 2/ salaud
* bastardise (brit.) / bastardize (amér.) (v) (tr.) : abatardir
* bastardised (brit.) / bastardized (amér.) (ppa) : abatardi(e) - bastardised/-ized language : langue abatardie (f)
* bat (n) : 1/ batte (f) (de base-ball, cricket) ; 2/ raquette (f) (de tennis de table)
* bat (v) (tr.) : frapper
* batch (n) (pl. batches) : 1/ fournée (f) ; 2/ lot - to produce in batches : produire par lots - batch number : numéro de lot
* batch (v) (tr.) : grouper (en lots)
* batching (n) : mise en lots, groupage en lots
* bated (a) : retenu(e) - with bated breath : en retenant son souffle
* bath (n) : 1/ bain - to run a bath : faire couler un bain - to have/take a bath : prendre un bain - bath robe : peignoir de bain (aussi bathrobe) - spa bath : bain à remous, jacuzzi ; 2/ baignoire (f) (syn amér. bath tub) - slipper bath : baignoire sabot
* bathe (v) : (tr.) baigner, faire prendre un bain à ; (intr.) se baigner
* bathe away (v) (tr.) : faire disparaître par un bain
* bathing (n) : baignade (f) - bathing beach : plage de baignade
* bathroom (n) : salle de bain(s) - to visit the bathroom (amér.) : aller aux toilettes - bathroom cabinet : armoire de salle de bain(s) (f) - bathroom linen : linge de toilette - bathroom scale (amér.) / bathroom scales (brit., pl.) : pèse-personne (masc.)
* batten (n) : 1/ tasseau ; 2/ liteau (de toit)
* batten (v) (intr.) : - to batten on/upon sth : se gaver (de qch) - to batten on/upon sb : vivre aux crochets de qn
* batten down (v) : (tr.) bien fermer ; (intr.) - to batten down the hatches : a/ fermer les écoutilles ; b/ (fig.) se préparer au pire
* batter (n) : pâte (f) (à crêpes)
* batter (n) : inclinaison (f), talus
* batter (n) : (Cricket, Baseball) batteur, batteuse
* batter (v) (tr.) : battre
* battering (n) : raclée (f) - to take a battering : souffrir beaucoup
* battery (n) (pl. batteries) : 1/ accus (pl.), pile (f) - dead (amér.) / flat (brit.) battery : accus à plat - storage batteries : accumulateurs - battery life : longévité des accus - battery operation : fonctionnement sur piles ; 2/ coups et blessures (m pl.)
* battery-powered (ac) : fonctionnant sur pile - battery-powered portable computer : ordinateur portable fonctionnant sur pile
* battle (n) : bataille (f) - to lose the battle over ... to : perdre la bataille de ... face à - to march into battle : se jeter dans la bataille - to win a battle : gagner une bataille - hard-fought battle : bataille remportée de haute lutte - pitched battle : bataille rangée
* battle (v) (tr.) : lutter contre, tenir tête à - to battle cancer : lutter contre le cancer
* battlefield (n) : champ de bataille - to regress from the battlefield : quitter le champ de bataille
* battleground (n) : (prop. et fig.) champ de bataille
* battle group (n) : groupe de bataille, groupe de combat, groupement tactique - naval battle group : groupe de combat naval
* battle-hardened (ac) : aguerri(e)
* battlements (n pl.) (n) : 1/ créneaux (pl.) ; 2/ remparts (pl.)
* battle-worthy (ac) : prêt(e) pour le combat, sur le pied de guerre - battle-worthy weapons : armes prêtes pour le combat
* bauble (n) : babiole (f)
* bawbee (n) : sou (en Écosse), bawbee
* bawd (n) (arch.) : 1/ catin (f) ; 2/ tenancière de bordel
* bawdy (a) : paillard(e) - bawdy house : bordel (syn. brothel)
* bawl (out) (v) : (intr.) beugler, brailler ; (tr.) - to bawl sb out : engueuler qn
* bay (n) : 1/ renfoncement, compartiment (dans un navire), soute (f) (dans un avion) ; 2/ (Électron.) armoire (f) - control bay : armoire de commande ; 3/ (Inf.) emplacement (de lecteur) - vacant bay : emplacement inoccupé ; 4/ baie (f) - the Bay Area : la région de la baie de San Francisco
* bay (n) : aboiement
* bay (v) (intr.) : aboyer
* bazaar (n) (fa p) : 1/ bazar ; 2/ vente de charité
* BC (adv.) (init. de Before Christ, lit. « avant le Christ ») : av. J.-C.
* BCE (adv.) (init. de Before Common Era, « avant ère courante ») : avant notre ère
* be (v) (was, been) : être - to be in pain : avoir mal, souffrir
* beach (n) (pl. beaches) : 1/ plage (f) - oil on beaches : mazout sur les plages - bathing beach : plage de baignade - nudie beach : plage nudiste - shingle beach : plage de galets - beach break : a/ brisants de sable (pl.) ; b/ (Surf) plage à brisants, site sur fond sableux - a beach break : des vacances à la plage, un séjour à la plage - beach house : maison sur la plage, maison au bord de la mer - beach music : musique de plage - beach resort : a/ station balnéaire (f), plage ; b/ complexe hôtelier donnant sur une plage
* beach (v) (tr.) : (Naut.) échouer (un navire) - to be beached : s’échouer
* beaching (n) : 1/ (Naut.) échouage (d’un navire) ; 2/ - beachings (pl.) : échouements (de coquillages, etc.), objets échoués (pl.)
* beacon (n) : 1/ fanal ; 2/ phare (syn. lighthouse) ; 3/ (fig.) flambeau, phare - beacon of freedom : flambeau de la liberté ; 4/ balise (f)
* bead (n) : 1/ perle (f) ; 2/ grain (de chapelet) - anal beads (pl.) : (Sex.) chapelet anal (sing.)
* beadle (n) : bedeau (syn. verger)
* beak (n) : bec - parrot beak : tire-clou (masc.)
* beaked (a) : (Hérald.) à bec
* beaker (n) : gobelet (pour boire)
* beam (n) : 1/ rayon (lumineux), faisceau (lumineux) - to emit a beam of light : émettre un rayon lumineux - beam of electrons : faisceau d’électrons - laser beam : rayon laser, faisceau laser - narrow beam : faisceau étroit ; 2/ poutre (f) ; 4/ balancier (de puits)
* beam (v) : (intr.) rayonner
* beam down (v) (tr.) : téléporter (sur la planète) - 2 to beam down! : (Star Trek) Deux à téléporter (sur la planète) !
* beam up (v) (tr.) : téléporter (sur le vaisseau) - 2 to beam up! : (Star Trek) Deux à téléporter (sur le vaisseau) !
* bean (n) : 1/ haricot - green beans : haricots verts ; 2/ grain - coffee beans : grains de café ; 3/ (expressions) - full of beans (brit.) (fam.) : tout feu tout flamme, qui pète le feu
* beanfeast (n) : dîner annuel (offert par un patron à ses ouvriers)
* bear (n) : ours - Goldilocks and the three bears : Boucle d’or et les trois ours - brown bear : ours brun - polar bear : ours polaire - bear hug : câlin (fam.) (aussi bearhug)
* bear (a) : (Bourse) baissier/-ière - bear market : marché baissier
* bear (v) (intr.) : (Bourse) spéculer à la baisse
* bear (v) (bore, borne) (tr.) : 1/ supporter, soutenir - to bear the brunt of… : soutenir le choc de…, subir ou essuyer le plus gros d’(une attaque) ; 2/ tolérer, supporter - I can't bear + gér. : Je ne supporte pas de + inf. ; 3/ donner (des fruits) - to bear fruit : donner des fruits ; 4/ donner naissance à, accoucher de, enfanter - She bore him five children : Elle lui donna cinq enfants ; 5/ porter, arborer - to bear the imprint of… : porter l’empreinte de… ; 6/ (expressions diverses) - to bear a grudge against sb : avoir de la rancune contre qn - to bear a resemblance to… : présenter une ressemblance avec… - to bear similarities to… : présenter des similitudes avec…
* beard (n) : barbe
* bear down (v) (bore, borne) (intr.) : to bear down on/upon sb : oppresser qn - to bear down on the enemy : foncer sur l'ennemi
* bearer (n) : 1/ porteur, porteuse - cross bearer : porte-croix (la personne) (masc.) - standard bearer : porte-drapeau (prop. et fig.) (masc.) ; 2/ détenteur, détentrice
* bearing (n) : 1/ (a) bearing : (un) roulement - ball bearing : roulement à billes - roller bearing : roulement à rouleaux ; 2/ palier
* bearish (a) : baissier/-ière - bearish tendency : tendance baissière (anton. bullish)
* bearskin (n) : peau d’ours
* beast (n) : 1/ bête f), animal ; 2/ sauvage, brute (f) ; 3/ (la) bête (humaine)
* beat (n) : 1/ battement - beats per minute (bpm) : (nombre de) battements à la minute, cadence (f) ; 2/ temps fort (de la mesure), rythme, tempo - to march to a different beat (fig.) (litt. marcher sur un rythme différent) : faire les choses différemment, être non conformiste, ne pas suivre le troupeau - to skip a beat : sauter un temps - to play on the beat : jouer en rythme - danceable beats : rythmes dansants - beat drop : chute de rythme - beat juggling : jonglage des tempos (technique de platiniste) - beat matching : superposition des battements, calage tempo (technique de platiniste) - beat mixing : mixage des tempos (technique de platiniste) ; 3/ ronde (f) (d'un gardien de la paix) - beats and patrols : rondes et patrouilles (f pl.) - on the beat : en patrouille
* beat (v) (beat, beaten) (tr.) : 1/ (Mus.) battre - to beat the rhythm : battre la mesure - to beat time : battre la mesure ; 2/ rosser, battre, tabasser - to beat sb to death : rosser qn jusqu’à ce que mort s’ensuive, battre qn à mort ; 2/ (expressions diverses) - Beat it! (arg.) : Fiche / Fichez le camp ! - That beats all! : Ça c’est le bouquet !- That beats me! : Cela me dépasse !
* beat (prét. de to beat)
* beatbox (n) : (Mus.) boîte à rythmes
* beaten (pp de to beat) : - to be beaten to death : être battu à mort
* beater (n) : 1/ (Cuisine) fouet à œufs), batteur ; 2/ tapette (f) (à tapis) ; 3/ battoir - clothes beater : battoir à linge ; 4/ personne qui bat (les autres), batteur, personne qui rosse (les autres), rosseur
* beating (n) : correction (f), raclée (f), rossée (f), volée (f) (syn. blows (pl.)), défaite (f) (syn. defeat) - to take a severe beating : se faire rosser copieusement
* beaver (n) : 1/ castor - beaver fur : fourrure de castor ; 2/ (vulg.) foufoune (f), (fam.) minou
* beautiful (a) : beau/-elle - the beautiful (a. subst. sing.) : le beau
* beautifully (adv.) : merveilleusement, divinement, magnifiquement, à la perfection
* beauty (n) : 1/ (la qualité) beauté (f) - angelic beauty : beauté angélique - timeless beauty : beauté intemporelle - beauty shop (amér.) : salon de coiffure femmes ; 2/ (la personne) - bathing beauty : beauté en maillot, belle en maillot, belle baigneuse, sirène (f) - the Dunce and the Beauty : la belle et la bête
* became (prét. de to become)
* because (conj. de cause) : 1/ parce que ; 2/ (Because, t d p) comme, du fait que, c'est parce que ... que ; 3/ (marquant la raison) car
* because of (loc. prép.) : à cause de, du fait de, en raison de
* Because of this / that, ... (m d l) : C'est la raison pour laquelle ..., Pour cette raison, …
* beck (n) : dans l’expression at s.o.’s beck and call : aux ordres de qn
* beckon (v) : faire signe (to sb, à qn) (to do, de faire)
* become (v) (became, become) : 1/ (tr.) devenir ; 2/ (+ adj.) devenir + adj. - to become clear : devenir évident - to become operational : devenir opérationnel - to become unprofitable : a/ devenir peu rentable ; b/ devenir peu profitable
* become (pp de to become)
* becoming (a) : 1/ convenable, bienséant(e) ; 2/ [vêtement] seyant(e)
* bed (n) : 1/ lit - to make one's bed : faire son lit - double bed : grand lit - four-post bed : lit à baldaquin (syn. canopy bed) - twin beds : lits jumeaux - unmade bed : lit défait - bed sheet : drap de lit ; 2/ banc (au sens technique) - test bed : banc d'essai ; 3/ parterre - flower bed : parterre de fleurs
* bed (v) (tr.) : 1/ loger, coucher ; 2/ mettre au lit, coucher ; 3/ coucher avec - to bed a woman : coucher avec une femme ; 4/ poser à plat, coucher
* bedbug (n) : punaise (f) (de matelas)
* bedding (n) : literie (f)
* bedeck (v) (tr.) : orner (with, de)
* bedraggled (a) : [vêtements, personne] débraillé (et tout trempé)
* bedrock (n) : (Géol.) soubassement, socle rocheux - bedrock ledge : rebord rocheux
* bedroom (n) : chambre à coucher - double bedroom : grande chambre - spare bedroom : chambre d'amis (syn. guest bedroom) - bedroom community : ville dortoir
* bedside (n) : chevet - bedside lamp : lampe de chevet - bedside table : table de chevet, table de nuit (syn. amér. nightstand)
* bedspread (n) : dessus de lit, couvre-lit (syn. arch. counterpane : courte-pointe)
* bed-sitting / bedsitting (ac) : - bed-sitting room : chambre d’étudiant
* bedsitter (n) : chambre d’étudiant
* bedtime (n) : heure du coucher, heure d’aller se coucher / de se mettre au lit - bedtime story : histoire (avant de s’endormir)
* bedwetter (n) : personne qui fait pipi au lit, personne qui mouille son lit, pisse-au-lit (vulg.) - music for bedwetters : musique pour les énurétiques, musique pour les incontinents
* bed-worthiness (n) (brit.) : baisabilité (f) (d’une fille)
* bed-worthy (ac) (brit.) : (en parlant d’une fille) à mettre dans son lit, baisable
* bee (n) : abeille (f) - (the) queen bee : (la) reine des abeilles - worker bee : ouvrière (n) - bee bole : buc - bee enclosure : enclos à ruches - bee hole : niche à ruche (syn. bee niche) - bee skep : ruche à abeilles
* (the) Beeb (n pr.) (fam.) : la BBC
* beech (n) : hêtre - beech grove : hétraie (f) - beech tree : hêtre
* beef (n) : 1/ (vieil.) (pl. beeves) bœuf (l’animal) ; 2/ bœuf (la viande) ; 3/ muscles (pl.) - There is too much beef on him : Il a trop de muscles ; 4/ force (f), énergie (f) - Put some beef in it : Mets-y un peu de nerf ; 5/ - to have a beef with sb : avoir une dent contre qn
* beefier (a) (compar. de beefy) : plus costaud(e), plus baraqué
* bee-friendly (ac) : bon(ne) pour les abeilles - bee-friendly flowers : fleurs bonnes pour les abeilles
* beef up (v) (tr.) : gonfler
* beehive (n) : ruche (f) - beehive well : puits couvert en forme de ruche
* beehive-like (ac) : en forme de ruche
* beehive-shaped (ac) : en forme de ruche
* beefy (a) : 1/ de bœuf - beefy flavour : goût de bœuf ; 2/ baraqué, costaud(e)
* been (pp de to be, être) : été
* beep (n) : bip
* beep (v) (intr.) : émettre un signal sonore, émettre un bip
* beeper (n) : dispositif sonore
* beer (n) : bière (f) - pitcher of beer : pichet de bière - chilled beer : (de la) bière fraîche - beer mat : dessous de bock - beer enthusiast : grand amateur de bière
* beet (n) : betterave (f) - sugar beet : betterave à sucre
* befall (v) (-fell, -fallen) (tr.) : arriver à - the trouble that have befallen the country in recents years : les problèmes qui ont assailli le pays ces derniers temps
* befallen (pp de to befall)
* befell (prét. de to befall)
* before (prép.) : 1/ devant ; 2/ avant ; 3/ + gér. : avant de + inf. : 4/ face à - the task before our engineers : la tâche qui attend nos ingénieurs
* before (conj. de temps) : avant que, avant de + inf.
* before (adv.) : (en fin de proposition) auparavant - a few years before : quelques années auparavant
* before and after (prép. coordonnées) : avant et après + subst.
* beforehand (adv.) : à l’avance
* befouled (a) : souillé
* befuddled (a) : ivre (syn. drunk)
* beg (v) (tr.) : 1/ supplier, prier - I beg you : Je vous en conjure ; 2/ mendier
* began (prét. de to begin)
* beget (v) (begot, begotten) (tr.) : engendrer
* beggar (n) : 1/ mendiant (n) - beggar monk : moine mendiant - beggar woman : mendiante (f) ; 2/ - little beggar : petit chenapan, petit vaurien - lucky beggar : sacré veinard
* beggar (v) (tr.) : 1/ réduire à la mendicité ; 2/ défier (dans diverses expressions) - to beggar belief : défier la raison - to beggar description : défier toute description
* begging (n) : mendicité - to go begging : pratiquer la mendicité
* begin (v) (began, begun) (tr.) : commencer - to begin commercial flights : (Av.) [compagnie aérienne] inaugurer des vols commerciaux - to begin to + inf. : commencer à + inf.
* beginner (n) : débutant, novice (syn. starter) - a beginner’s guide : un guide pour débutants
* beginning (n) : 1/ commencement, début - at the beginning : au début (of, de) ; 2/ (beginnings, n pl.) : prémices
* beginning on (loc. prép.) (+ numéro de page, de revue, etc.) : à partir de, à compter de - Beginning on June 28, 2011, ... : À partir du 28 juin 2011, ... - beginning on page 40 : à partir de la page 40
* beginning with (loc. prép.) : (+ date, etc.) à commencer par, à partir de, à compter de
* begot (prét. de to beget)
* begotten (pp de to beget)
* begrudge (v) (tr.) : to begrudge sb sth : reprocher qch à qn
* beguile (v) (tr.) : séduire
* begun (pp de to begin)
* behalf (n) : représentant (n) - on behalf of : au nom de (syn. in the name of)
* behave (v) (intr.) : 1/ se conduire, se comporter, se tenir ; 2/ to behave oneself : bien se tenir - Behave yourself! : Tiens-toi / Tenez-vous bien !
* behavior (amér.) / behaviour (brit.) (n) : comportement, conduite (f), tenue (f) - human behaviour : comportement humain - playful behaviour : taquineries (f pl.) - self-serving behaviour : comportement intéressé
* behead (v) (tr.) : décapiter
* beheading (n) : décapitation (f)
* beheld (prét. de to behold)
* beheld (pp de to behold)
* behind (prép.) : 1/ derrière ; 2/ à l’origine de - behind the move : à l’origine de la mesure ; 3/ qui se cache derrière - the truth behind… : la vérité qui se cache derrière… ; 4/ dans la locution to be behind : être à la traîne, être en retard - to be technologically behind : être en retard sur le plan technologique
* behind (adv.) : 1/ à l’arrière, en arrière - to lead from behind : diriger en coulisse ; 2/ derrière - (not to) be far behind : n'être pas loin derrière
* behind (n) : derrière (n), postérieur, fesses (f pl.)
* behind-the-scene (ac) : (Théätre) qui se passe en coulisse - behind-the-scene joke : blague de coulisses
* behold (v) (beheld, beheld) (tr.) : regarder, contempler - Behold! (interj.) (littéraire) : Regardez ! - lo and behold : a/ ô surprise, surprise , b/ et voilà que…
* beholden (a) : redevable - to be beholden to sb : être redevable à qn
* beholder (n) : spectateur, observateur - Beauty is in the eyes of the beholder : Il n’y a pas de laides amours
* being (p. prés.) : 1/ étant - This being so : Cela étant ; 2/ en train d'être - the product being made : le produit en cours de fabrication
* being (n) : 1/ existence (f) - to come into being : voir le jour, naître, apparaître (syn. to come into existence) ; 2/ être, créature (f) - beings from other worlds : créatures venant d'autres mondes - alien being : créature extraterrestre - human being être humain
* be it ... or ... (loc. conj. alternative) : que ce soit ... ou ..., qu'il sagisse de ... ou de ...
* Belarus (n pr.) : (la) Biélorussie, (le) Bélarus
* Belarussian (a) : biélorusse (sans maj.)
* Belarussian (n pr.) : Biélorusse (le ressortissant)
* belated (a) : 1/ qui s’est laissé surprendre par la nuit ; 2/ attardé ; 3/ en retard, retardé ; 4/ tardif/-ive
* belatedly (adv.) : tardivement, avec du retard, en retard - to admit sth belatedly : admettre qch tardivement
* beleaguer (v) (tr.) : 1/ assiéger (un château, une ville) ; 2/ harceler
* beleaguered (a) : assiégé (syn. besieged)
* belfry (n) : beffroi
* Belgium (n pr.) : (la) Belgique
* Belgian (a) : belge (sans maj.)
* Belgian (n pr.) : Belge (le ressortissant, la ressortissante)
* belief (n) : croyance (f) - to beggar belief : défier la raison - firm belief : conviction (f) - contrary to popular belief : contrairement à ce que croient les gens
* believe (v) (tr.) : (tr.) croire - Believe me : Crois-moi, Croyez-moi - Believe you me (fam.) : Tu peux me croire, Vous pouvez me croire - to believe that… : croire que…, être d’avis que… ; (intr.) croire (in sth, en qch)
* believer (n) : croyant, croyante (n) - staunch believer : dévot - UFO believer : croyant en matière d’ovnis (anton. non-believer, skeptic)
* belittle (v) (tr.) : rabaisser, dénigrer, déprécier
* bell (n) : cloche (f) - the opening bell (Bourse) : la cloche à l'ouverture - bell curve : courbe en cloche - bell ringer : sonneur de cloches - bell tower : clocher
* bellboy (n) : portier, garçon d’étage
* bellicose (a) : belliqueux/-euse - bellicose trade policies : politiques commerciales belliqueuses
* bellicosity (n) : caractère belliqueux, bellicisme
* belligerence (n) : 1/ belligérance (f) - an act of belligerence : une manifestation d’hostilité ; 2/ pugnacité (f)
* belligerent (a) : 1/ belliqueux/-euse, agressif/-ive, hostile ; 2/ pugnace
* bellow (n) : beuglement
* bellow (v) (intr.) : beugler
* bell-shaped (ac) : en forme de cloche, campaniforme
* bellwether (n) : 1/ tête de troupeau (de moutons), chef de troupeau (de moutons) ; 2/ meneur, chef ; 3/ (fig.) indicateur de tendance, baromètre
* belly (n) : ventre - distended belly : ventre distendu, ventre gonflé - belly button : nombril (syn. navel)
* belly-up (n) : ventre à l’air (comme un poisson crevé) - maximum time to belly-up : (Maint.) temps maximum jusqu’à la faillite - [someone, company] to go belly up (expr.) : [personne] crever, [société] faire faillite, capoter
* belong (v) (intr.) : appartenir (to, à)
* belongings (n pl.) : biens (pl.), possessions (f pl.)
* beloved (a) : bien aimé
* below (prép.) : 1/ au-dessous de, en dessous de, sous ; 2/ en deçà de, inférieur(e) à + indication chiffrée
* below (adv.) : 1/ en dessous ; 2/ ci-dessous
* below-average (ac) : en dessous de la moyenne, inférieur à la moyenne - people with below-average intelligence : gens dotés d’une intelligence inférieure à la moyenne
* belt (n) : 1/ ceinture (f) - to tighten one’s belt : se serrer la ceinture - garter belt (amér.) : porte-jarretelle (masc.) - suspender belt (brit.) : porte-jarretelle (masc.) - belt tightening : serrage de ceinture, politique de rigueur (f) ; 2/ bande (f) - conveyor belt : tapis roulant, transporteur à bande, bande de manutention - tool belt : porte-outil (masc.) ; 3/ région (f) - rust belt : région industrielle sinistrée
* belt-driven (ac) : entraîné par courroie - belt-driven pulley : poulie entraînée par courroie
* belt up (v) (intr.) (arg.) : fermer sa gueule, la fermer
* bemoan (v) (tr.) : déplorer
* bench (n) : 1/ banc (de jardin) ; 2/ banquette (f) ; 3/ (Indust.) établi, paillasse (f), banc - presetting bench : banc de préréglage - bench drill : perceuse d'établi (f) - bench operation : fonctionnement sur paillasse ; 4/ banc (d’essais) - test bench : banc d’essais, banc de tests, banc-test - testing bench : banc d'essais, banc de tests, banc-test
* benchmark (n) : 1/ point géodésique, repère géodésique ; 2/ (fig.) référence (f), niveau ; 3/ banc d’essais, batterie de tests d’évaluation (f), essai comparatif
* benchmarking (n) : évaluation des performances (f), évaluation comparative (d'une entreprise)
* benchtop (n) : établi (n) - benchtop magnifier : loupe d’établi
* benchwarmer (n) : (Sport) réchauffeur de banc, remplaçant
* bend (n) : virage - sharp bend : virage serré
* bend (v) (bent, bent) : (tr.) courber, cintrer ; (intr.) se pencher, plier
* bender (n) : 1/ plieuse (f), machine à plier (f) ; 2/ - spoon bender : plieur de petites cuillères (Uri Geller) ; 3/ (arg.) beuverie (f) - to go on an all-night bender : se biturer toute la soirée ; 4/ cuite (f) - to go on a bender : prendre une cuite ; 5/ (arg. brit.) pédé ; 6/ travelo (péj.)
* bending (n) : cintrage - bending machine : machine à cintrer (f), cintreuse (f)
* beneath (prép.) : au-dessous de, en dessous de, sous (syn. under)
* benefactor (n) : bienfaiteur
* benefactress (n) : bienfaitrice (f)
* benefit (n) (fa p) : 1/ bénéfice - to give sb the benefit of the doubt : donner à qn le bénéfice du doute ; 2/ avantage, atout - the benefits of living in the city : les avantages de la vie citadine ; 3/ bienfait ; 4/ allocation (f) (de chômage, etc.), indemnité (f) - daily benefit : indemnité journalière - employee benefits : avantages sociaux- social security benefits (pl.) (brit.) : aide sociale (f) - unemployment benefit : allocation de chômage - He draws unemployment benefits : Il touche une allocation de chômage
* benefit (v) (tr.) (fa) : 1/ être utile à ; 2/ profiter à, être bénéfique à
* benevolence (n) : 1/ bienveillance (f) ; 2/ acte de bienfaisance, bienfait
* benevolent (a) : bienveillant(e) - benevolent society : société de secours mutuel (f)
* benign (a) : 1/ bénin/-igne ; 2/ inoffensif/-ve, qui n’est pas méchant(e), bienveillant
* benignly (adv.) : 1/ bénignement, de façon bénigne, sans faire de mal ; 2/ avec bienveillance, avec bonté et douceur
* benighted (a) : plongé dans les ténèbres de l’ignorance, ignare, attardé
* bent (prét. de bend)
* bent (pp de bend)
* bent (a) : 1/ courbé, plié ; 2/ (brit.) homosexuel (syn. queer) - a bent character : un homo, un pédé
* bequeath (v) (tr.) : léguer (to, à)
* bequest (n) : legs (faux pluriel) - charitable bequest : don testamentaire
* berate (v) (tr.) : réprimander, tancer - to berate sb for doing sth : réprimander qn pour avoir fait qch
* bereaved (a) : en deuil - the bereaved (a. subst. pl.) : les proches (du disparu)
* bereft (a) : dénué (of, de)
* berk (n) (arg. brit.) : idiot, imbécile, crétin - Don’t make a berk of yourself! : Ne fais pas l’idiot !
* Berliner (n pr.) : Berlinois(e) (l’habitant)
* berry (n) (pl. berries) : baie (des bois)
* berserk (a) (tjrs attribut) : fou furieux, folle furieuse - to go berserk : a/ [individu] devenir fou/folle, pêter les plombs, disjoncter ; b/ [machine] s’emballer
* berth (n) : poste de mouillage - to give a wide berth to sth : a/ passer au large de qch ; b/ (fig.) éviter qch, se tenir à l’écart de qch
* beseech (v) (besought, besought ou régulier) (tr.) (lit.) : implorer - I beseech you : Je vous en conjure
* beseem (v) (tr.) : convenir à, seoir à
* beset (v) (-set, -set) (tr.) : assaillir, assiéger
* beset (prét. de to beset)
* beset (p passé) : assailli(e) - to be beset with obstacles : être semé d'embûches, être hérissé d’obstacles
* beside (prép.) : 1/ à côté de, auprès de ; 2/ en plus de - beside the point : en dehors de la question
* besides (prép.) : 1/ en plus de, outre - besides the fact that… : sans compter que… 2/ besides + gér. : outre que, outre le fait de + inf., en plus de + inf.
* besides (adv.) : en outre, de plus, par ailleurs
* besiege (v) (tr.) : assiéger
* besmirch (v) (tr.) : salir - wastes that besmirch the land : des déchets qui souillent le territoire
* besought (prét. de to beseech)
* bespeak (v) (bespoke, bespoke/bespoken) (tr.) : indiquer, montrer, témoigner de
* bespectacled (a) : portant des lunettes
* bespoke (prét. de to bespeak)
* bespoke (a) : sur mesure
* bespoken (pp de to bespeak)
* best (a) (superl. de good) : meilleur(e) - in the best case : dans le meilleur des cas (anton. in the worst case : dans le pire des cas)
* best (n) : (le) meilleur, (la) meilleure ; (le) mieux
* best (v) (tr.) : avoir le dessus sur, l’emporter sur, filer une raclée à (fam.)
* bestir (v) (intr.) : se remuer, s’activer
* bestow (v) (tr.) : conférer (on, à), accorder (on, à)
* best-seller / bestseller (nc/n) : succès de librairie, livre à succès, livre à grand succès
* bet (n) : pari - to hedge one’s bets : limiter les risques, protéger ses arrières (fam.)
* bet (v) (bet, bet ou régulier) (tr.) : parier (une somme) (on, sur), miser (on, sur) - I bet : je parie, pour sûr, à tout coup
* bet (prét. de to bet)
* bet (pp de to bet)
* betcha (expr.) (pour I bet you) (arg.) : je te parie que, je vous parie que
* bethink (v) (tr.) : considérer, songer à
* betoken (v) (tr.) : annoncer, présager, être le signe de, augurer de
* betray (v) (tr.) : trahir - to betray one’s country : vendre son pays (to, à)
* betrayal (n) : trahison (f)
* better (a) (compar. de supériorité de good) : meilleur(e)
* better (n) : (le) meilleur - for better or for worse : pour le meilleur ou pour le pire
* betterment (n) : 1/ amélioration (f) ; 2/ plus-value (f)
* better off (tjrs attribut) : en meilleure posture - You could be better off : Tu pourrais mener une vie plus aisée (anton. worse off)
* better-quality (ac) : de meilleure qualité
* better still (loc. adv.) : encore mieux, mieux encore
* betting (n) : paris (pl.)
* between (prép.) : entre - the gulf between rich and poor : l’écart entre riches et pauvres
* bevel-edged (ac) : aux angles chanfreinés / abattus / biseautés - bevel-edged hammer : marteau à arêtes biseautées
* beverage (n) : breuvage, boisson (f) - alcoholic beverage : boisson alcoolique - effervescent beverage : boisson gazeuse - beverage can : canette/cannette (f)
* beware (v) (tr.) : - May the Gods beware you of… : puissent les dieux vous garder de…
* bewilder (v) (tr.) : abasourdir, ahurir, dérouter
* bewildered (a) : abasourdi(e), ahuri(e), époustouflé, dérouté, éberlué
* bewildering (a) : ahurissant(e), époustouflant(e), déroutant(e)
* bewitch (v) (tr.) : ensorceler, enchanter
* bewitched (ppa) : ensorcelé, enchanté
* beyond (prép.) : au-delà de - far beyond… : dépassant de loin…
* beyond (adv.) : au-delà
* bias (n) : préjugé, parti pris - confirmation bias : préjugé de confirmation - gender bias : sexisme
* biased (a) : 1/ partial(e) - to be biased : être de parti pris ; 2/ biaisé, tendancieux/-ieuse
* bicameral (a) : [parlement] bicaméral(e), qui a deux chambres, à deux chambres
* bicarb (n) (tronc. fam. de bicarbonate (of sodium)) : bicarbonate (de soude)
* bicarbonate (n) : bicarbonate - bicarbonate of soda (brit.) : bicarbonate de soude (l’agent levant) (amér. baking soda)
* biceps (n) (pl. biceps) : biceps - bulging biceps : gros biceps
* bicker (n) : chamailleries (f pl.)
* bicker (v) (intr.) : se chamailler
* bicycle (n) : bicyclette (f) - lifestyle-oriented bicycle : bicyclette de loisir - bicycle friendliness : attitude favorable à la bicyclette - bicycle rack : porte-vélo (var. bike rack)
* bid (n) : 1/ offre (f) (aux enchères), offre d’achat - takeover bid : offre de rachat - bid price : cours acheteur ; 2/ tentative (f) - to make a bid for sth : tenter d’obtenir qch, s’efforcer de gagner qch - murder bid : tentative de meurtre
* bid (v) (bade, bidden ou bid, bid) : 1/ (tr. et intr.) commander, ordonner ; 2/ faire une offre (d’achat) (intr.) to bid for sth : faire une offre d’achat pour qch ; 3/ (tr.) souhaiter - to bid goodbye to sb / to bid sb goodbye : dire au revoir à qn
* bid (prét. de to bid)
* bid (pp de to bid)
* bidet (n) : bidet
* bidden (pp de to bid)
* bidder (n) : enchérisseur, enchérisseuse - the highest bidder : le plus offrant
* bidding (n) : 1/ enchères (f pl.) ; 2/ ordres (pl.) - to do sb’s bidding : être aux ordres de qn
* bide (v) (tr.) : attendre - to bide one’s time : attendre le bon moment, chercher à gagner du temps
* bier (n) : 1/ bière (f) ; 2/ catafalque
* biff (v) (tr.) (fam.) : donner un coup de poing à, filer un gnon à (fam.), coller un pain à (fam.)
* bifilar (a) : bifilaire - bifilar winding : enroulement bifilaire
* big (a) : grand(e), gros(se), important(e) - big data (sing.) : (les) gros corpus de données - big size : grand format - big stocks : réserves importantes (f pl.)
* biggest (a) (superl. de supériorité de big) : le plus grand, le plus gros - the biggest benefits : les plus grands avantages
* big-headed (ac) : qui a la grosse tête (fam.), infatué de soi-même, vaniteux/-euse
* big-name (ac) : connu(e), célèbre, important(e)
* bigot (n) : fanatique (n) (m/f), sectaire (n) (m/f)
* bigoted (a) : sectaire, intolérant(e)
* bigotry (n) : sectarisme, intolérance (f)
* big-titted (ac) : qui a de gros seins, à grosses loches (vulg.)- big-titted slut : salope à grosses loches
* bike (n) (fam.) : 1/ (abr. de bicycle) vélo, biclou (fam.) - bike with training wheels : vélo avec une paire de roulettes - bike lane : piste cyclable (f) - bike mugger : voleur de vélo à l’arraché - bike rack : porte-vélo (masc.) ; 2/ (abr. de motorbike) moto (f) - chopper bike : (amér.) moto modifiée (f), moto réduite (abr. chopper)
* bikepacker (n) : voyageur à vélo sans porte-bagages (avec de simples sacoches accrochées au guidon, au cadre et à la tige de la selle)
* bikepacking (n) : voyager sur un vélo sans porte-bagages (avec de simples sacoches accrochées au guidon, au cadre et à la tige de la selle)
* bikini-clad (ac) : vêtu(e) d’un bikini - bikini-clad creature : belle fille en bikini
* bilge (n) : 1/ eau de cale ; 2/ propos insignifiants (pl.)
* bilingual (a) : bilingue - bilingual concordancer : concordancier bilingue - bilingual corpus : corpus bilingue
* bilingual (n) : bilingue (n) - bilinguals : (les) bilingues
* bilingualism (n) : bilinguisme - symbolic bilingualism : bilinguisme de façade
* bilk (n) : escroquerie (f)
* bilk (v) (tr.) : 1/ escroquer (qn) ; 2/ contrecarrer ; 3/ se dérober à
* bill (v) : (tr.) donner des coup de bec à ; (intr.) se becoter
* bill (n) : 1/ bec (d’oiseau) ; 2/ billet ; 3/ facture (f) - phone bill : facture téléphonique - the bill (brit .) : l’addition (f) (au restaurant) - Shall I make up your bill? : Je vous fais votre compte ?
* bill and coo (v coord.) (intr.) : se bécoter et roucouler, se faire des mamours
* billet (n) (fa) : cantonnement
* billiards (n) : (le) billard
* billion (n) (amér. = 10 puissance 9) : mille millions, milliard (en français, un billion représente aujourd'hui un million de millions)
* billionfold (a) : multiplié par un milliard
* billow (n) : volute
* billy-goat (nc) : bouc
* bi-metal / bimetal (a) : bimétallique - bimetal strip : élément bimétallique
* bimetallic (a) : bimétallique - bimetallic strip : élément bimétallique
* bin (n) : casier, réceptacle - bin liner (brit.) : sac poubelle
* binarisation (brit.) / binarization (amér.) (n) : binarisation (f)
* binarise (brit.) / binarize (amér.) (v) : transformer en données binaires, binariser
* binary (a) : binaire
* bind (v) (bound, bound) (tr.) : 1/ attacher, lier ; 2/ unir, cimenter (fig.)
* binder (n) : moissonneuse-lieuse (f)
* binge (n) : 1/ gueuleton ; 2/ beuverie (f), soûlerie (f) - binge drinking : beuverie, beuveries (pl.) ; 3/ frénésie (f) - binge viewing : visionnage boulimique (syn. binge watching)
* bio (n) pl. bios (tronc. fam. de biography biographie) bio (f)
* bio-car (n) : voiture écolo (f)
* biochemist (n) : biochimiste (m/f)
* biochemistry (n) : biochimie (f)
* biodegradable (a) : biodégradable
* biodegrade (v) (tr.) : biodégrader, décomposer - to be able to biodegrade plastic : être capable de décomposer le plastic
* biodiesel (n) : biodiesel - biodiesel car : voiture roulant au biodiesel
* bio-diverse / biodiverse (a) : biodivers(e)
* bio-diversity / biodiversity (n) : diversité biologique (f), biodiversité (f) - to increase biodiversity : accroître la biodiversité - to maintain biodiversity : préserver la biodiversité - biodiversity hotspot : point chaud de biodiversité, zone critique de biodiversité
* biofuel (n) : biocarburant - agriculturally produced biofuels : biocarburants d'origine agricole
* biography (n) (pl. biographies) : biographie
* biological (a) (fa p) : biologique (au sens de relatif à la biologie), de biologie - biological clock : horloge biologique - biological lab : labo de biologie
* biologically (adv.) : biologiquement
* biology (n) : (la) biologie ; 2/ (Éduc.) (les) sciences de la vie et de la terre
* biomass (n) : biomasse (f) - plant biomass : biomasse végétale (f)
* biopesticide (n) : biopesticide
* biopic (n) (pl. biopics) (abr. de biographical picture : film biographique) biographie filmée (f)
* biosphere (n) : (la) biosphère - our planet's biosphere : la biosphère terrestre
* biotech (n) (tronc. de biotechnology) : biotech (n f) - biotech company : société de biotech - the biotech industry : l’industrie de la biotech - biotech scientist : biotechnicien
* biotechnology (n) : biotechnologie (f) - biotechnology scientist : biotechnicien
* bioterrorism (n) : bioterrorisme
* bioweapon (n) : arme biologique (f), arme bactériologique (f) - to develop a bioweapon : créer une arme biologique - bioweapon research facility : centre de recherches sur les armes biologiques
* bipartisan (a) : constitué de deux partis politiques, bipartite, biparti
* biped (n) : bipède
* bipedal (a) : bipède
* bipolar (a) : bipolaire
* birch (n) (pl. birches) : bouleau (l’arbre et aussi le bois) - silver birch : bouleau argenté, bouleau blanc
* birchwood (n) : bois de bouleau (aussi birch)
* bird (n) : 1/ oiseau - carrion bird : charognard - migratory bird : oiseau migratoire - bird spotting : observation des oiseaux (aussi bird watching) ; 2/ (fig.) fille (f), nana (f) (fam.)
* birdbath (n) : vasque à oiseaux, abreuvoir à oiseaux
* bird-brained (ac) : à la cervelle d’oiseau
* birdie (n) : oiselet
* birdstrike : (Aéro.) impact d’oiseau (sur un avion)
* Biro / biro (n de marque) : stylo à bille (syn. ballpoint, ballpoint pen, ball pen)
* birth (n) : naissance (f) - to give birth : donner naissance (to, à), accoucher (to, de) - birth mother : mère biologique (f) - birth rate : taux de natalité
* birthday (n) : anniversaire - My birthday is not for another month : Mon anniversaire est dans un mois - birthday party : fête d’anniversaire
* birthstone (n) : pierre porte-bonheur (f)
* biscuit (n) : biscuit - biscuit tin : boîte à biscuits
* bishop (n) : 1/ évêque ; 2/ (aux échecs) fou
* bison (n) : bison - steppe bison : bison des steppes
* bit (prét . de to bite)
* bit (n) : 1/ morceau, bout, brin, soupçon (de qch) - Not one bit! : Pas un brin !, Pas un iota ! - He hasn’t changed one bit : Il n’a pas changé d’un iota - I don’t like it one bit : Je n’aime pas ça du tout ; 2/ mèche (f) (de vilebrequin) - tool bit : mèche d’outil
* bitch (n) (pl. bitches) : 1/ chienne (f) ; 2/ garce (f), salope (f) - Karma is a bitch! : On récolte ce qu’on a semé !
* bitch (v) (intr.) : râler, rouspéter, se plaindre - to bitch about sb : dire du mal de qn
* bitchslap (n) : baffe (f), torgnole (f) (vulg.)
* bitchslap (v) (tr.) : filer une baffe à, baffer, foutre une torgnole à (vulg.) - to get bitchslapped : prendre une baffe, se faire torgnoler (vulg.)
* bitchy (a) : méchant(e), vache (fam.)
* bite (v) (bit, bitten) (tr.) : 1/ mordre ; 2/ ronger - to bite one’s nails : ronger ses ongles, se ronger les ongles
* biter (n) : 1/ personne ou animal qui mord ; 2/ personne qui ronge - nail biter : a/ personne qui se ronge les ongles ; b/ situation tendue (f) ; c/ (Sport) (selon le cas) match serré, course serrée (f), championnat très disputé, chaude lutte (f)
* bitten (pp de to bite)
* bitter (a) : amer/-ère, plein(e) d’amertume (anton. sweet)
* bitterly (adv.) : avec amertume, amèrement
* bitterness (n) : amertume (f)
* bittersweet (a) : 1/ [goût] aigre-doux, aigre-douce, amer et doux, amère et douce ; 2/ [émotion] doux-amer, douce-amère - bittersweet memory : souvenir mêlant douceur et amertume ; 3/ [bilan] mitigé
* bizarre (a) : 1/ bizarre ; 2/ singulier/-ière, scabreux/-euse
* black (a) : noir(e) - black gold : or noir (le pétrole) - black spot : tache noire (f)
* black (n) : noir(e) (n)
* blackamoor (n) 1/ (Hérald.) tête de Maure ; 2/ (péj.) nègre / négresse (m/f)
* black-and-white (adj. doubles coord. par and) : noir et blanc (inv.), en noir et blanc - black-and-white film : pellicule noir et blanc
* blacken (v) (tr.) : noircir ; (intr.) noircir, se noircir
* blackberry (n) : mûre (f)
* blackboard (n) : tableau noir (syn. chalkboard)
* blackface (n) : grimage en Noir(e), maquillage en Noir(e) - to be in blackface : être grimé en Noir - white actors in blackface : acteurs blancs grimés en Noirs - blackface makeup : maquillage en Noir(e)
* blackguard (n) : gueux
* blackjack (n) (amér.) : matraque (f)
* blacklist (n) : liste noire (f) - the McCarthyite blacklist : la liste noire mccarthyste
* blacklist (v) (tr.) : mettre sur la liste noire
* blacklisting (n) : mise sur la liste noire
* blackmail (v) (tr.) : faire chanter (fig.)
* blackmailer (n) : maître chanteur
* blackmailing (n) : chantage
* black or white (adj. doubles coord. par or) : 1/ [opinion] manichéen(ne) ; 2/ [opinion] tranché, arrêté
* black out (v) (tr.) : 1/ obscurcir ; 2/ couvrir (une fenêtre) ; 3/ plonger dans le noir (une ville)
* blackout (n) : panne totale de courant
* blacksmith (n) : forgeron
* black tops (n pl.) : (litt. « têtières noires ») journaux sérieux (pl.) par opposition aux red tops (litt. « têtières rouges ») journaux populaires – existe aussi au singulier : a black top
* bladder (n) : vessie (f) - bladder leakage : fuites urinaires (f pl.)
* blade (n) : 1/ lame (f) (de couteau, etc.) - the edge of the blade : le fil de la lame - slotter blade : lame à rainurer - serrated blade : lame dentelée ; 2/ - turbine blade : ailette (f) ; 3/ - wiper blade : caoutchouc d’essuie-glace ; 4/ aube (f)
* blame (n) : responsabilité (f), culpabilité (f) - to place blame on sb : attribuer la responsabilité (de qch) à qn - to put the main blame on… : incriminer principalement… - the blame game : les accusations mutuelles (f pl.), les reproches mutuels - to play the blame game : jouer à qui la faute, se renvoyer la balle
* blame (v) (tr.) (fa) : rendre responsable, tenir pour responsable, attribuer la responsabilité à, rejeter la faute sur, mettre en cause - to blame the manufacturer : s’en prendre au fabricant - to have only oneself to blame : ne s'en prendre qu'à soi-même - The accident was blamed on human error : On imputa l'accident à une erreur humaine - to be to blame : en être responsable
* blameworthy (a) : blamable, répréhensible - blameworthy conduct : conduite répréhensible
* bland (a) : 1/ fade, insipide ; 2/ [individu] doux/-ouce, poli(e), affable, débonnaire ; 3/ [ton] suave
* blandly (adv.) : 1/ platement ; 2/ avec douceur, avec affabilité ; 3/ avec suavité
* blandness (n) : 1/ douceur (f), affabilité (f); 2/ suavité (f)
* blank (a) : 1/ à l’état d’ébauche - blank collars : bagues à l’état d’ébauches - blank forgings : ébauches forgées(f) ; 2/ vide, occulté - blank screen : écran vide, écran occulté - blank space : blanc (n) ; 3/ inexpressif/-ive
* blank (n) : bloc de métal brut, pièce brute (f), brut (n), flan, ébauche (f) (Usin.)
* blank (v) (tr.) : occulter
* blanket (a) : global(e), général(e) - blanket rate : taux uniforme
* blanket (n) (fa) : 1/ couverture (f) - electric blanket : couverture chauffante ; 2/ (fig.) chape (f), manteau - blanket of snow : manteau neigeux
* blankly (adv.) : sans comprendre
* blare (n) : beuglement, braillement
* blare (v) (intr.) : beugler, brailler
* blast (n) : 1/ souffle (d’explosion) ; 2/ rafale (f), coup (de vent) ; 3/ - air blast : jet d’air, air soufflé
* blast (v) (tr.) : 1/ faire exploser, faire sauter, souffler (un immeuble) ; 2/ bombarder
* blaster (n) : - sand blaster : sableuse (f) - water blaster : nettoyeur haute pression
* blast-resistant (ac) : résistant aux explosions - blast-resistant building : [bâtiment] résistant aux explosions
* blatancy (n) : caractère criant, caractère flagrant (syn. obviousness)
* blatant (a) : criant(e), flagrant(e) (syn. obvious)
* blatantly (adv.) : de façon flagrante, de façon éhontée
* blaze (n) : 1/ incendie ; 2/ brasier ; 3/ flamboiement, éclat
* blaze (v) (intr.) : 1/ flamber, brûler ; 2/ flamboyer
* blazer (n) : veste croisée (f) - the blazer with the badge (brit.) : la veste croisée avec son écusson
* bleach (n) : 1/ eau de Javel ; 2/ décolorant
* bleach (v) (tr.) : 1/ blanchir ; 2/ décolorer
* bleachers (n pl.) (amér.) : gradins
* bleak (a) : sombre, morne, désolé
* bleary (a) : [yeux] trouble(s), voilé(s)
* bleat (n) : 1/ bêlement ; 2/ plainte (f), jérémiade (f)
* bleat (v) (intr.) : 1/ [mouton] bêler : 2/ (fig.) se plaindre (about sth, de qch)
* bleating (a) : bêlant(e) - a bleating sheep : un mouton bêlant
* bled (prét. de to bleed)
* bled (p passé de to bleed)
* bleed (n) : saignement, hémmoragie (f) - brain bleed : hémmoragie cérébrale
* bleed (v) (bled, bled) : 1/ (tr. et intr.) saigner - My heart bleeds for you : J’ai de la peine pour toi ; 2/ (tr. et intr.) purger
* bleeding (a) : 1/ [individu] qui saigne, en sang, [blessure] qui saigne ; 2/ - bleeding edge : (fig.) pointe (f), avant-garde (f)
* bleeding (n) : saignement - menstrual bleeding : saignements menstruels (pl.)
* bleeding-edge (ac) : de pointe, d’avant-garde
* bleep (n) : bip
* bleep (v) : (tr.) (Radio, Télé) censurer, biper - The radio station always bleeped the word crap : Cette station de radio ne manquait jamais de censurer le mot merde ; (intr.) émettre un bip, biper
* blemish (n) : souillure (f)
* blend (n) : mélange - oil blend : a/ mélange d’huiles ; b/ mélange de pétroles
* blend (v) : (tr.) mélanger - to blend sth with sth : mélanger qch à qch ; (intr.) se mélanger - to blend with sth : se mélanger à
* blender (n) : mélangeur - electric blender : mélangeur électrique
* bless (v) (tr.) (fa) : bénir - God bless you! : Que Dieu vous bénisse ! - Bless you! : 1/ (après un éternuement) À vos souhaits ! ; 2/ (en remerciement) Merci mille fois !
* blessed (ppa) : béni(e), saint(e)
* blessedness (n) : béatitude (f), félicité (f)
* blessing (n) : 1/ bénédiction (f) ; 2/ bienfait - the blessings of civilization : les bienfaits de la civilisation
* blew (prét. de to blow)
* blight (n) : dégradation (f) - urban blight : quartier délabré
* blight (v) (tr.) : 1/ flétrir, brûler ; 2/ nuire à
* blighted (ppa) : malade, mal en point
* blind (a) : 1/ aveugle - to turn a blind eye : fermer les yeux (to, sur) ; 2/ - blind date : a/ rendez-vous avec un inconnu ; b/ inconnu avec qui on a rendez-vous (syn. blind dater) ; 3/ (Av.) sans visibilité - blind landing : atterrissage sans visibilité ; 4/ (au fig.) aveugle, qui ne se pose pas de questions - blind obedience : obéissance aveugle (f)
* blind (n) : 1/ store ; 2/ auvent - sun blind : auvent (aussi sunblind)
* blinder (n) : œillère (f)
* blinding (a) : 1/ aveuglant(e), éblouissant(e) ; 2/ fou/folle - blinding speed : vitesse folle (f)
* blindly (adv.) : aveuglément
* blindness (n) : 1/ cécité (f) ; 2/ (fig.) aveuglement, refus de voir - ideological blindness : cécité idéologique
* blindside (n) : (Rugby) côté fermé, petit côté
* blindside (v) (tr.) : 1/ (Sport) attaquer de côté ; 2/ (fig.) prendre par surprise, prendre en traître
* blindsided (ppa) : pris(e) de court, pris(e) au dépourvu - to get blindsided : se trouver pris de court
* blink (n) : 1/ battement (des paupières) ; 2/ clignotement - [TV] to be on the blink (amér.) : être détraqué, être en panne, être en rade, yoyoter (fam.)
* blink (v) : (intr.) cligner des yeux
* blinking (a) : clignotant(e) - blinking lights : lumières clignotantes (f)
* blink out (v) (intr.) : [ampoule électrique] clignoter et s’éteindre
* blip (n) : parasite d’écran
* bliss (n) : bonheur (f), félicité (f) - wedded bliss : bonheur conjugal
* blissful (a) : bienheureux/-euse
* blissfully (a) : 1/ merveilleusement ; 2/ d’un air béat ; 3/ complètement
* blisteringly fast (ac) : qui tourne à un train d'enfer, qui carbure
* blithe (a) : gai(e), joyeux/-euse
* blithely (adv.) : gaiement, avec insouciance
* blitz (n) : 1/ bombardement aérien, blitz ; 2/ attaque éclair (f), attaque soudaine - blitz attack : attaque éclair ; 3/ campagne éclair (f)
* blitz (v) (tr.) : 1/ bombarder (par avion) ; 2/ attaquer impétueusement
* blitz-attack (vc) : (tr.) se livrer à une attaque éclair sur
* blizzard (n) : tempête de neige - black blizzard (amér.) : blizzard noir, tempête de poussière (du Dust Bowl – « bassin de poussière » – dans les années 1930)
* bloated (a) : 1/ boursouflé, enflé, gonflé, ballonné - bloated corpse : cadavre boursouflé - bloated face : visage enflé ; 2/ (fig.) exagéré
* block (n) (fa p) : 1/ bloc (de pierre, de glace) - stumbling block : pierre d’achoppement, écueil ; 2/ billot ; 3/ cube (de construction) - building blocks : cubes de construction, éléments constructifs ; 4/ - dans block (of flats) (brit.) : immeuble d’habitation ; 5/ (amér.) pâté (de maisons, d'immeubles) ; 6/ - pulley block : palan
* block (v) (tr.) : 1/ bloquer, barrer - to block sb’s path : se mettre en travers du chemin de qn - to block the view of… : gêner la vue de… ; 2/ obturer
* blockade (n) : blocus - to subject a country to a blockade : soumettre un pays à un blocus - air blockade : blocus aérien, embargo aérien - land blockade : blocus terrestre - naval blockade : blocus naval - sea blockade : blocus maritime
* blockade (v) (tr.) : faire le blocus de
* blockbuster (n) : 1/ bombe de gros calibre (capable de faire sauter un immeuble, a block of flats) ; 2/ (fig.) superproduction (américaine) (f), grosse machine hollywoodienne (f)
* blockhead (n) (fam.) : imbécile (m/f)
* blocking (n) : blocage - site blocking : (Toile) blocage de sites
* blockline (n) : légende (d'une photo de presse)
* blog (n) : blogue, blog - blog reader : lecteur/-trice de blogues - content aggregating blog : blogue agrégateur de contenus
* blogging (n) : tenue d’un blogue - blogging platform : plate-forme pour blogues
* bloke (n) (brit.) : type, bonhomme, mec
* blonde (a) : blond(e) - blonde bombshell : belle blonde (f), blonde de toute beauté, blonde incendiaire (f)
* blonde (n) : (en parlant d’une femme) blonde (n f)
* blood (n) : sang - My blood curdled (fig.) : Mon sang s’est figé - tainted blood : sang contaminé - blood clot : caillot de sang, caillot sanguin - blood drive : collecte de sang - blood libel : accusation de meurtre rituel - blood pressure : pression sanguine (f) - blood stream : circulation du sang, circulation sanguine (f)
* blood-curdling (ac) : à (te / vous) figer le sang, à (te / vous) glacer le sang - a blood-curdling scenario : un scénario à glacer le sang
* bloodmobile (n) : camion pour le don du sang
* bloodshed (n) : effusion de sang
* blood-stained (ac) : ensanglanté, taché de sang
* bloody (a) : 1/ sanglant(e) ; 2/ (juron brit.) sacré, fichu(e), foutu(e) + subst.
* bloody (v) (tr.) : 1/ couvrir de sang, ensanglanter ; 2/ faire saigner - to bloody sb’s nose : mettre le nez de qn en sang
* bloom (n) : velouté (n) (de la peau d’un fruit, d’une personne)
* bloom (v) (intr.) : 1/ fleurir ; 2/ s’épanouir
* blooper (n) : (Cin.) bêtisier - bloopers and outtakes : bêtisier et prises inexploitées
* blossom (v) (intr.) : s’épanouir
* blot (n) : tache (f)
* blotchy (a) : boutonneux/-euse, pustuleux/-euse
* blot out (v) (tr.) : effacer (syn. to obliterate)
* blouse (n) (fa) : 1/ corsage, chemisier ; 2/ vareuse (f)
* bloviating (a) : pompeux/-euse
* blow (n) : coup - to deal a serious blow : porter un rude coup (to, à) - to come to blows : en venir aux coups - to elude a blow : esquiver un coup - foul blow : coup en traître
* blow (v) (blew, blown) : 1/ (tr.) souffler - to blow kisses : envoyer des baisers ; (intr.) souffler ; 2/ (intr.) [ampoule] griller, sauter - The bulb blew : L'ampoule a sauté / grillé ; 3/ (tr.) faire sauter, faire exploser ; (intr.) exploser - to blow one’s top (fig.) : piquer une crise (fam.), péter les plombs (fam.) ; 4/ (amér.) faire foirer (vulg.) - I blew it, completely and utterly : J’ai tout fait foirer
* blow away (v) (blew, blown) (tr.) : 1/ faire s’envoler ; 2/ impressionner - It blew me away : Ça m’a soufflé
* blower (n) : - leaf blower : souffleur de feuilles, souffleuse de feuilles
* blowhole (n) : évent (de baleine)
* blowing (a) : - blowing cave : caverne respirante (syn. breathing cave)
* blowjob (n) : (Sex.) fellation (f), pompier (fam.)
* blown (pp de to blow)
* blow off (v) (blew, blown) : 1/ (intr.) être emporté par le vent ; 2/ (tr.) - to blow sb off (amér.) : faire comme si qn n’existait pas - to blow sth off (amér.) : laisser tomber qch
* blowout (n) : 1/ explosion (f) (on an oil rig, sur une plateforme pétrolière) - to experience a blowout : être victime d'une explosion ; 2/ éclatement de pneu, creuvaison (f)
* blow out (v) (blew, blown) (tr.) : éteindre en soufflant - He blew out the candle : Il éteignit la bougie
* to blow over (v) : (tr.) [vent] renverser (un pot de fleur, un arbre) ; (intr.) [nuages] se dissiper, [tempête] se calmer, [situation] se tasser - Don’t worry, it will all blow over : Ne t’en fais pas, ça va se tasser / Ne vous en faites pas, ça va se tasser
* blowtorch (n) : chalumeau
* blow up (v) (blew, blown) : 1/ (tr.) faire exploser, faire sauter (fam.) ; (intr.) exploser, sauter (fam.) ; 2/ (tr.) (Photog.) faire un agrandissement de, agrandir
* blown-up (ppa) : - blown-up photograph : photographie agrandie (f), agrandissement photographique (syn. enlarged photograph)
* blub (v) (intr.) (brit.) : pleurnicher, gémir (about sth, sur qch)
* blue (a) : 1/ bleu(e) - blue chips : (Fin.) valeurs refuge (f pl.), valeurs vedette (f pl.) - blue collar : litt. « col bleu », ouvier d’usine - blue movie : film porno (syn. porn movie) ; 2/ déprimé - to feel blue : avoir le cafard ; 3/ (expressions) - once in a blue moon : tous les 36 du mois
* blue (n) : bleu (n)
* blueberry (n) : myrtille (f)
* bluebottle (n) : mouche à viande
* blueness (n) : bleuté
* blueprint (n) : 1/ photocalque, calque, bleu (n) ; 2/ épure (f) ; 3/ plan (de travail) ; 4/ schéma directeur
* blues (n pl.) : - the blues : le cafard, le bourdon
* bluff (n) : falaise (f), promontoire
* blunder (n) : 1/ gaffe (f), bourde (f), boulette (f) (fig.) ; 2/ énormité (f) (que l’on dit)
* blunder (v) (intr.) : 1/ faire une gaffe, faire une bourde, faire une boulette ; 2/ dire une énormité
* blundering (a) : maladroit(e), gaffeur/-euse
* blunt (a) : 1/ émoussé - blunt edge : bord émoussé ; 2/ [choc] brusque, brutal(e) ; 3/ [individu] direct(e) - and, to be blunt, it could… : et, pour être franc(he), cela pourrait…
* blunt (v) (tr.) : 1/ émousser (une lame) ; 2/ (fig.) atténuer
* bluntly (adv.) : d’un ton brusque
* blur (n) : flou (n)
* blur (v) : 1/ (tr.) obscurcir ; (intr.) s’obscurcir ; 2/ se brouiller, devenir indistinct(e)
* blurred (ppa) : flou(e), brouillé
* blurring (n) : aspect flou
* blurt (v) (tr.) : bredouiller (des excuses)
* blush (n) : fard
* blush (v) (intr.) : [individu] rougir
* bluster (v) (intr.) : [individu] tempêter
* B.O. (n) (init. de body odour (brit.) / body odor (amér.) : mauvaise odeur corporelle)
* boar (n) : verrat - wild boar : sanglier
* board (n) : 1/ planche (f) - chopping board : planche à découper, planche à hacher - draining board : égouttoir - ironing board : planche à repasser - Ouija board : planche Ouija - pastry board : planche à pâtisserie ; 2/ planche de glisse - to make boards : fabriquer des planches ; 3/ (Inf.) carte (f) - expansion board : carte d’extension, carte additionnelle - system board : carte principale, carte mère ; 4/ conseil d’administration - censorship board : commission de censure (f)
* board (v) (tr.) : appareiller à bord de
* board-and-batten (a) : en planches - board-and-batten house : maison en planches
* boarder (n) : (Éduc.) interne (n)
* boardroom (n) : salle de réunion du conseil d'administration (f), salle de conseil d’administration
* board up (v) (tr.) : obturer avec des planches, condamner (une baie) - to board up a window : obturer une fenêtre avec des planches
* boardwalk (n) (amér.) : passage en bois, passerelle en bois (f), promenade (f) (le long du front de mer)
* boast (n) : motif d’orgueil
* boast (v) (tr.) : 1/ être fier/fière de posséder, avoir à son actif ; 2/ prétendre
* boastful (a) : vantard(e)
* boat (n) : 1/ bateau - boat ramp : rampe de mise à l'eau, rampe de bateau - boat trip : excursion en mer (f) ; 2/ traîneau - stone boat : traîneau à pierres
* boating (n) : navigation de plaisance (f) - boating enthusiast : adepte de la navigation de plaisance
* boat-shaped (ac) : en forme de bateau
* bob (n) : 1/ hochement de tête ; 2/ petite révérence (f) ; 3/ petit tour (sur un manège) - I want a bob on the bobby horses : Je veux faire un petit tour sur les chevaux de bois ; 4/ balancier (d’horloge) ; 5/ cheveux courts (pl.), coupe au carré (f)
* bob (n) (inv.) (brit.) : shilling
* bob (n) (abr. de bobsleigh)
* bob (v) : 1/ (intr.) : danser, s’agiter - to bob up and down : danser sur l’eau ; 2/ (tr.) couper (la queue d’un chien), couper au carré (les cheveux)
* bobber (n) (amér.) : (= bobbed bike, moto écourtée)
* bobblehead (n) : jouet qui hoche la tête, figurine (f) qui hoche la tête
* bobby (a) : - the bobby horses : les chevaux de bois (le manège)
* bobsledder (n) : adepte du bobsleigh, bobeur
* bobsleigh (n) : 1/ traîneau court ; 2/ (Sport) (le) bobsleigh
* bode (v) (intr.) : augurer, présager - to bode well/ill : être de bon/mauvais augure
* body (n) (pl. bodies) : 1/ corps - body double : doublure physique (f) (dans les scènes chaudes) - body clock : horloge interne (f) - body language : langage du corps, langage corporel - body odour (brit.) / boy odor (amér.) : mauvaise odeur corporelle (f) ; 2/ cadavre - alien bodies : cadavres d’extraterrestres - body bag : housse mortuaire (f) ; 3/ caisse (f) (de véhicule), carrosserie (f) - car body : carrosserie de voiture - partially assembled body : caisse assemblée partiellement - body panels : panneaux de carrosserie - body shop : atelier de carrosserie
* body-friendly (ac) : 1/ non agressif/-ive pour l’organisme, supportable par le corps, bon(ne) pour la santé - body-friendly vinegar jam : confiture au vinaigre bonne pour la santé ; 2/ agréable à porter - body-friendly synthetic fibre material : matière fibreuse synthétique agréable à porter
* body man (n) (amér.) : 1/ garde du corps ; 2/ aide personnel (m)
* bodywork (n) : 1/ carrosserie (f) (d’une voiture) ; 2/ (activité de réparation) (la) carrosserie - to do bodywork : travailler comme carrossier, travailler comme tôlier
* boffin (n) : technicien radar
* bog (n) : 1/ fondrière (f) ; 2/ (brit.) the bog ou the bogs (arg.) : les chiottes (f pl.) (vulg.) - bog roll : (du) papier toilette, (du) papier cul (vulg.), (du) PQ
* bog down (v) (tr.) : embourber - to get bogged down : s’enliser (in mud, dans de la boue, in one’s work, dans son travail, etc.)
* bogey / bogy (n) (pl. bogeys / bogies) : 1/ démon, fantôme ; 2/ crotte de nez (var. ortho. amér. boogeyman)
* bogeyman / bogyman (n) : 1/ - the bogeyman : le croquemitaine, le Père Fouettard ; 2/ épouvantail (au fig.) (var. ortho. amér. boogeyman)
* bogus (a) (amér.) : faux/fausse, factice, simulé, bidon (inv.) - bogus company : société fantôme (f)
* boil (n) : furoncle
* boil (n) : point d’ébullition - to bring to the boil : amener à ébullition
* boil (v) : (tr.) faire bouillir ; 2/ (intr.) bouillir
* boil down (v) : 1/ (Cuis.) (tr.) faire réduire, laisser réduire (une sauce) ; (intr.) [liquide] réduire, se réduire par ébullition ; 2/ (fig.) (intr.) to boil down to sth : se résumer à qch - It all boils down to this : Tout se ramène à ceci.
* boiler (n) : chaudière - gas-fired boiler : chaudière à gaz - oil-fired boiler : chaudière à mazout - boiler suit (brit.) : bleu de travail, bleu de chauffe - boiler works : chaudronnerie
* boiling (a) : bouillant(e)
* boisterous (a) : 1/ bruyant(e), tapageur/-euse (syn. noisy) ; 2/ tumultueux/-euse
* boisterously (adv.) : bruyamment ; 2/ tumultueusement
* bold (a) : hardi(e), intrépide, audacieux/-euse - bold decision : décision hardie
* boldly (adv.) : hardiment, intrépidement, audacieusement - to boldly go where no man has gone before (amér.) : aller audacieusement là où aucun homme n'est allé auparavant.
* boldness (n) : hardiesse (f), intrépidité (f), audace (f)
* bole (n) : 1/ renfoncement (dans n mur) ; 2/ tronc creux (servant de ruche), ruche (f) dans un tronc creux - bee bole : buc
* Bolivia (n pr.) : (la) Bolivie
* Bolivian (a) : bolivien(ne) (sans maj.)
* Bolivian (n pr. ) : Bolivien (le ressortissant), Bolivienne (la ressortissante)
* bollock up (v) (brit.) (arg.) (tr.) : foutre (qch) en l’air, merder sur (qch) - We can’t risk you bollocking this up : On peut pas te laisser foutre ça en l’air
* bollocks (a) (brit.) (en position d’attribut du sujet) : merdique, pourri(e)
* bollocks (n pl) (brit.) (arg.) (vulg.) : 1/ couilles (f pl.) (vulg.), rouflaquettes (f pl.) ; 2/ (fig.) conneries (f pl.), cagades (f pl.) - to talk bollocks : dire des conneries - a load of old bollocks : un tas de vieilles cagades (syn. bull)
* bolster (n) : traversin
* bolster (v) (tr.) : 1/ renforcer ; 2/ soutenir, appuyer (syn. to support)
* bolt (n) : 1/ boulon - to drive a bolt : insérer un boulon - to tighten a bolt : serrer un boulon - clamping bolt : boulon de serrage - fixing bolt : boulon de fixation - screw bolt : boulon fileté - through bolt : boulon traversant ; 2/ targette (f) ; 3/ verrou ; 4/ rouleau (de tissu) - bolt of silk : rouleau de soie
* bolt (v) : 1/ (tr.) fixer par des boulons (to, sur), boulonner ; 2/ (intr.) s’emballer, déguerpir - to bolt like a rabbit : détaler comme un lièvre
* bolus (n) : bol (alimentaire) - food bolus : bol alimentaire
* bomb (n) : 1/ bombe (f) - letter bomb : lettre piégée (f) - stink bomb : bombe puante - thermonuclear bomb : bombe thermonucléaire - time bomb : bombe à retardement - bomb crater : cratère de bombe ; 2/ (fig.) (arg. brit.) a/ grand succès, belle réussite (f), b/ argent fou, fric monstre - to make a bomb : gagner un fric fou - It costs a bomb : Ça coûte un argent fou ; 3/ (fig.) (amér.) désastre
* bomb (v) (tr.) : 1/ bombarder ; 2/ (arg. amér.) a/ rater, foirer (vulg.) (un examen, etc.) ; b/ faire foirer - How to bomb a photo : Comment faire foirer une photo
* bombard (v) (tr.) : bombarder
* bombast (n) : emphase (f)
* bombastic (a) : grandiloquent(e), ampoulé, pompeux/-euse
* bomber (n) : bombardier - strategic bomber : bombardier stratégique - water bomber : bombardier à eau, canadair
* bombing (n) : bombardement - carpet bombing : bombardement intensif, pratique du tapis de bombes - bombing campaign : campagne de bombardement - bombing raid : raid aérien
* bombshell (n) : 1/ obus ; 2/ (fig.) bombe (f), nouvelle faisant l’effet d’une bombe, nouvelle fracassante (f) - to let fall a bombshell : faire éclater une bombe, annoncer une nouvelle fracassante (aussi to drop a bombshell) - bombshell report : article faisant l’effet d’une bombe, article explosif ; 3/ (fig.) bombe (sexuelle) - She’s a bombshell : Elle est canon
* bonanza (n) (amér.) : 1/ (fig.) filon, mine d’or ; 2/ manne (f)
* bonce (n) (arg. brit.) : caboche (f)
* bond (n) (fa) : 1/ lien ; 2/ (Fin.) obligation (f), titre - government bond : obligation d'État, bon du Trésor - catastrophe bonds : obligations catastrophe - high-yield bond : obligation à fort rendement - junk bond : obligation pourrie, titre douteux, action sans valeur - short-dated bonds : obligations à court terme - bond trading : commerce de titres
* bond (v) : 1/ (tr.) souder, coller ; 2/ (intr.) nouer des liens (with sb, avec qn)
* bondage (n) : servitude (f)
* bonder (n) : 1/ soudeur, colleur ; 2/ dispositif de liaison
* bondholder : obligataire (n)
* bond out (v) : (tr.) libérer sous caution ; (intr.) être libéré sous caution
* bone (n) : 1/ os - bone marrow : moelle osseuse (f) ; 2/ (expression) bone of contention : pomme de discorde (f) ; 3/ (brit.) courge (f)
* bone lazy (a) (amér.) : fainéant(e), flemmard(e) - He’s bone lazy : C’est un fainéant (syn. brit. bone idle)
* boner (n) (amér.) gaffe (f), boulette (b), bourde (bourde) - (to) make a boner faire une gaffe
* bone setting (n) : reboutage, reboutement
* boneshaker (n) : vieille bagnole (f), vieux tacot, vieille guimbarde (f) (syn. jalopy)
* boneyard (n) (arg.) : cimetière - aircraft boneyard (amér.) : cimetière d’avions
* bonnet (n) (fa p) : 1/ bonnet ; 2/ (brit.) capot (d'automobile) - Look under the bonnet for leaks : Soulevez le capot pour voir s'il n'y a pas de fuites
* bonus (n) (pl. bonuses) : prime (f)
* boo (interj.) : 1/ (pour saluer) hou !, houhou !, coucou !; 2/ (pour surprendre) bouh !
* boo (n) (svt au pl.) : huée (f)
* boo (v) : (tr.) huer, conspuer ; (intr.) pousser des huées
* boob (n) (arg.) : 1/ sein, nichon (fam.), loche (f) (fam.), nibard (vulg.) - (Cin.) boob shot : plan nichons ; 2/ bourde (f), boulette (f) ; 3/ idiot, crétin, âne, nullité (f)
* booby (n) (pl. boobies) : 1/ sein, nichon, nibard ; 2/ idiot, crétin, âne, nullité (f) - booby prize : lot de consolation - booby trap : (Milit.) a/ objet piégé ; b/ piège
* book (n) : 1/ livre - to skim through a book : parcourir un livre - to thumb through a book : feuilleter un livre - book of hours : livre d’heures - book of spells : grimoire, livre de sorts (aussi spell book) - hand-signed book : livre dédicacé - You’re an open book (fig.) : On lit en toi comme dans un livre ouvert, Tu es transparent(e) - pocket book : (brit.) livre de poche - school book : manuel scolaire, livre de classe (syn. text book) - book chapter : chapitre de livre - book end : serre-livre (masc.) (aussi bookend) - book enthusiast : bibliophile (m/f) - book mark : signet, liseuse (f) (aussi bookmark) ; 2/ cahier - copy book : cahier d’élève - grade book : cahier de notes (du professeur)
* book (v) (tr.) : réserver
* bookcase (n) : bibliothèque (f) (le meuble)
* bookend (n) : serre-livre (aussi ortho. book end)
* booking (n) : réservation (f) - advance booking required : sur réservation
* bookkeeper (n) : comptable (n m/f)
* bookkeeping (n) : comptabilité (f)
* bookmark (n) : signet, liseuse (f) (aussi book mark)
* booklet (n) : livret, fascicule
* bookshelf (n) : étagère à livres (f)
* Boolean (a) : booléen(ne) (sans maj.)
* boom (n) : 1/ fracas, grondement ; 2/ boum, essor, expansion (f) - baby boom : boum des naissances, explosion démographique (f)
* boom (v) (intr.) : 1/ tonner, gronder ; 2/ être en plein essor
* booming (a) : en plein essor
* boon (n) : aubaine (f)
* boost (n) : 1/ poussée verticale (f), impulsion (f) - to be given a boost : recevoir une impulsion - to deliver a boost to… : donner une impulsion à… ; 2/ (fig.) coup de fouet ; 3/ (fig.) coup de pouce, encouragement
* boost (v) (tr.) : 1/ impulser, propulser ; 2/ accroître, augmenter ; 3/ stimuler, renforcer, donner un coup de fouet à - to boost productivity : stimuler la productivité
* booster (n) : 1/ stimulant - productivity booster : stimulant de la productivité ; 2/ accélérateur, lanceur ; 3/ fusée d’appoint (f), propulseur auxiliaire, pousseur - solid rocket booster : propulseur à poudre - strap-on booster : pousseur latéral
* boot (n) : 1/ botte (f) - rain boots : bottes de pluie - thigh-high boots : cuissardes (f pl.) - boot licker : lèche-botte (m/f) ; 2/ (brit.) coffre (de voiture) - boot sale : vente depuis l’arrière de sa voiture (syn. boot fair)
* boot (n) (tronc. de bootstrap) : (Inf.) initialisation, amorce (f), démarrage, redémarrage - cold boot : redémarrage à froid, redémarrage après mise hors tension - warm boot : redémarrage à chaud, redémarrage sans mise hors tension
* boot (v) (tronc. de bootstrap) (tr.) : (Inf.) amorcer, mettre en route, lancer (le système d’exploitation)
* bootable (a) : (Inf.) initialisable, amorçable
* boot camp / bootcamp (n) : 1/ camp d’entraînement (militaire) ; 2/ centre de redressement (pour délinquants) ; 3/ formation intensive (f), classes (f pl.) (à l’armée)
* booth (n) : 1/ cabine (f) - photo booth : cabine de photomaton ; 2/ alcôve (f) ; 3/ baraque (f)
* booties (n pl.) : 1/ chaussons (pl.) d’enfant ; 2/ bottines (f pl.) de femme
* bootlace (n) : lacet
* bootleg (n) (amér.) : 1/ alcool fabriqué clandestinement, alcool de contrebande ; 2/ copie piratée (f) (d’un progiciel)
* bootleg (a) : piraté - bootleg duplicate : double piraté, copie piratée (f) (d’un logiciel)
* bootleg (v) (amér.) (intr.) : faire de la contrebande d’alcool (à l’époque de la Prohibition)
* bootlegger (n) (amér.) : contrebandier d’alcool
* bootlegging (n) : contrebande de l’alcool, contrebande d’alcool
* bootlicker (n) : lèche-botte (m/f)
* bootmaker (n) : bottier, bottière
* boot out (v) (tr.) : mettre à la porte à coups de bottes, flanquer dehors, virer
* booty (n) : 1/ butin ; 2/ magot
* booze (n) (arg. brit.) : alcool, boisson (f)
* booze (v) (intr.) : picoler
* booze away (v) (tr.) (arg. brit.) : dépenser en boisson (one’s salary, son salaire)
* boozer (n) (arg. brit.) : 1/ bistrot, pub, bar ; 2/ soulard, soûlaud, soûlot
* booze-up (n) (arg. brit.) : beuverie (f), cuite (f) - to have a booze-up : se payer une cuite
* border (n) : 1/ bordure (f) (de fenêtre, de page, etc.) ; 2/ frontière (f) - the Iraq-Syria border : la frontière entre l’Irak et la Syrie
* border (v) (tr.) : être à la limite de
* bore (prét. de to bear)
* bore (n) : 1/ calibre (d’un tuyau) ; 2/ (Usin.) alésage ; 3/ (fig.) raseur
* bore (v) (tr.) : 1/ forer, creuser - bore well : puits de forage; 2/ (Usin.) aléser ; 3/ (fig.) ennuyer, raser
* bored (ppa) : las, lasse - to be bored : être las (with, de)
* boredom (n) : ennui
* boring (a) : ennuyeux/-euse
* boring (n) : alésage - boring machine : machine à aléser (f)
* boringly (adv.) : de façon ennuyeuse, de façon assommante
* born-again (a) : (Rel.) né de nouveau - to have been born again : avoir connu une nouvelle naissance - born-again believer : croyant né de nouveau, croyante née de nouveau
* borne (pp de to bear)
* borough (n) : quartier, circonscription (f)
* borrow (v) (tr.) : emprunter (from, à) - [language] to borrow a foreign word : [langue] emprunter un terme étranger
* bosky (a) : boisé, broussailleux/-euse
* Bosnia (n pr.) : (la) Bosnie
* Bosnian (a) : bosniaque, bosnien(ne) (sans maj.)
* Bosnian (n pr.) : Bosniaque, Bosnien (le ressortissant), Bosnienne (la ressortissante)
* bosom (n) : poitrine (f) (d’une femme), sein (fig.)
* bosomy (a) : qui a une forte poitrine, à la poitine forte
* boss (n) : bossage
* boss (n) (pl. bosses) : patron, patronne, chef, cheffe - to show who is boss : montrer qui est le patron
* boss about (v) (tr.) : mener par le bout du nez
* bossy (a) : autoritaire, qui aime donner des ordres
* botanical (a) : botanique - botanical garden : jardin botanique
* botch (n) : 1/ travail bâclé ; 2/ ratage, foirage (vulg.)
* botch (v) (tr.) : bousiller, louper
* botched (ppa) : bousillé, loupé - botched machined parts : loupés d'usinage
* botfly (n) : gastérophile (masc.), gastrophile (masc.) - a botfly infestation : une prolifération de gastrophiles - botfly larva : larve de gastérophile (f)
* both + n. and + n. (loc. conj.) : à la fois ... et, aussi bien… que…, tant ... que… - Blake was both a painter and a poet : Blake était à la fois peintre et poète
* bother (n) : ennui, tracas - It’s no bother for them : Ce n’est pas un problème pour eux, Ça ne les tracasse pas
* bother (v) : 1/ (tr.) ennuyer, déranger - I was fearful of bothering them : J’avais peur de les déranger ; 2/ (tr.) chagriner - What's bothering you? : Qu'est-ce qui te chagrine ? ; 3/ (intr.) faire un effort, prendre la peine, se donner la peine ; se soucier (about, de ; with, de)
* bottle (n) : 1/ bouteille (f) - non-returnable bottle : bouteille non consignée - onion bottle : bouteille en forme d’oignon - bottle bank : conteneur pour le verre usagé - bottle carrier : porte-bouteille (masc.) (le casier mobile) - bottle rack : porte-bouteille (l’armoire) (masc.) - bottle rinsing : rinçage de bouteilles - bottle stopper : bouchon mécanique ; 2/ biberon
* bottleneck (n) : goulet d’étranglement
* bottle feed (vc) (-fed, -fed) (tr.) : nourrir au biberon
* bottle feeding (n) : le biberon (le fait de nourrir au biberon)
* bottling (n) : embouteillage - bottling equipment : machines d’embouteillage
* bottom (a) : le plus bas, du bas, inférieur(e) - bottom line : a/ dernière ligne (du bilan) ; b/ bas de gamme
* bottom (n) : 1/ bas, partie inférieure (f), fond - at the bottom of… : a/ en bas de …(fig.) ; b/ à la base de…, à l’origine de…
* bottom-of-the-line (ac) : de bas de gamme
* bought (prét. de to buy)
* bought (pp de to buy)
* bought and paid for (paire adj.) (ppa de to buy and pay for) : acheté, rémunéré - to be bought and paid for by the large corporations : être à la solde des grands groupes - The witness was bought and paid for by the culprit : Le témoin était à la solde du prévenu
* bought-and-paid-for (a) (tjrs épithète) : 1/ [bien, marchandise] acheté et payé - bought-and-paid-for goods : biens achetés et payés ; 2/ [personne] acheté, rémunéré - bought-and-paid-for street activist : provocateur urbain rémunéré
* boulder (n) : bloc erratique
* bouldering (n) : (l’)escalade de blocs, (le) bloc (jarg.) - urban bouldering : (l’)escalade de blocs en milieu urbain
* bounce (v) : 1/ (tr.) faire rebondir ; (intr.) : rebondir ; 2/ (tr.) refuser (un chèque) ; (intr.) [chèque sans provision] être refusé, être rejeté - The check bounced : la chèque a été rejeté ; 3/ virer (qn), flanquer à la porte (qn)
* bouncer (n) : videur (de boîte de nuit)
* bouncing (a) : rebondissant(e) - bouncing boobs : loches qui rebondissent
* bound (prét. de to bind)
* bound (pp de to bind)
* bound (ppa) : 1/ - homeward bound : [navire] retournant au port / pays, sur le retour ; 2/ forcé - to feel bound to + inf. : se sentir obligé de + inf.
* bound (n) : borne (f), limite (f) - to know no bounds : ne pas connaître de limites
* bound (v) (tr.) : border, limiter
* boundary (n) : limite (f), frontière (f) - boundary marker : borne (f)
* bounder (n) (brit.) : goujat, malotru
* boundless (a) : sans bornes, sans limites, illimité, infini(e)
* bountiful (a) : abondant(e)
* bounty (n) : 1/ prime (f) (on, sur la tête de) ; 2/ abondance (f) ; 3/ (lit.) générosité (f)
* bout (n) : 1/ accès (de fièvre), quinte (f) (de toux) - bout of sectarian violence : accès de violence confessionnelle ; 2/ période (f) - drinking bout : période de beuverie ; 3/ (Boxe) combat
* bootblack (n) : cireur de chaussures, cireur de rue, petit cireur (syn. shoe shiner - shoeshine boy (amér.))
* bovine (a) : bovin(e) - bovine excrement : excréments de bovins, bouse de vache
* bow (n) : avant (d'un navire), proue (f)
* bow (n) : 1/ courbe (f), coude ; 2/ arc (l’arme) - archery bow : arc de tir à l’arc - the bow handle : la poignée de l’arc - bow string : corde d’arc - to add another string to one’s bow (fig.) : avoir plusieurs cordes à son arc ; 3/ (Mus.) archet - violin bow : archet de violon - bow string : corde d’archet ; 4/ nœud (décoratif)
* bow (n) : révérence (f), salut de la tête - to give a bow : faire la révérence - to take a bow : saluer (l’auditoire, le public) - neck bow : inclination de la tête
* bow (v) : 1/ (tr.) incliner - to bow one’s head : incliner la tête ; (intr.) faire une révérence, s’incliner, saluer d'un signe de tête - to bow to sb : faire une révérence à qn, s’incliner devant qn - to bow low : s’incliner très bas ; 2/ (tr.) faire ployer, faire plier, soumette ; (intr.) se courber, ployer, fléchir
* bow down (v) (tr.) : accabler, abattre
* bowed down (pp) : abattu(e), accablé
* bowels (n pl.) : intestins (pl.)
* bowie-knife (n) : couteau-poignard
* bowl (n) : bol - finger bowl : rince-doigts (masc.) - trinket bowl : vide-poche (masc.)
* bowler (n) : 1/ (brit.) lanceur/-euse de cricket - bowler hat (brit.) : chapeau melon ; 2/ joueur/-euse de bowling
* bowser (n) : camion-citerne - water bowser : camion-citerne d’eau
* bowstring (n) : 1/ corde d'archet ; 2/ corde d'arc - bowstring girder : poutre en arc sous-tendu, poutre en arc à tirant
* bow tie (n) : nœud-papillon
* box (n) (pl. boxes) 1/ boîte (f) - Pandora’s box : la boîte de Pandore - shoe box : boîte à chaussures - suggestion box : boîte à idées ; 2/ emballage - cardboard box (brit.) : carton - box cutter : couteau à lame rétractable, cutter (anglic.) ; 3/ coffret ; 4/ boîtier, machine (f), boîte (f) (fam.) - control box : boîtier de commande - gear box : boîte de vitesses (aussi gearbox) - glove box : boîte à gants (d’une auto) ; 5/ loge (f) (de théâtre) ; 6/ guérite (f) - sentry box : guérite de sentinelle - 7/ (Inf.) case, cadre (d'interface graphique) - to check off a box : cocher une case - dialogue box : boîte de dialogue, case à dialogue ; 8/ encadré ; 9/ rectangle marqué au sol (à une intersection) - to block the box : bloquer l’intersection (en restant coincé au milieu de celle-ci lorsque le feu passe au rouge) ; 10/ the box (brit.) : la télé, la téloche (fam.) ; 11/ (expressions diverses) - to think outside the box : sortir des sentiers battus, faire preuve de créativité, faire montre d’originalité
* box (v) : 1/ (tr.) boxer contre (un autre boxeur) ; (intr.) faire de la boxe, boxer ; 2/ (tr.) frapper, boxer (qn)
* box (v) (tr.) : mettre dans une boîte, mettre en boîte - to box oneself into a corner : se retrouver acculé
* boxcar (n) (amér.) : wagon de marchandises
* boxed-in (ppa) : [auto] coincé (entre trois camions ou autos)
* boxers (n pl.) : caleçon (sing.) (syn. boxer shorts, pl.)
* box in (v) (tr.) : [trois camions ou autos] coincer [une auto] - [car] to get boxed in : [voiture] se retrouver coincée devant, derrière et sur le côté
* Boxing Day (n pr.) (brit.) : (le) 26 décembre (jour férié) (= la Saint-Étienne en Moselle et Alsace)
* box office (nc) : bureau de location, guichet, caisse (f), billetterie (f) - [film] to take millions at the box office : [film] faire des millions de recettes dans les salles - box-office success : film qui fait recette (syn. box office hit)
* box room / boxroom (n) : débarras
* boxy (a) : carré, cubique
* boy (n) : garçon - errand boy : garçon de courses - old boy (G.-B.) : ancien élève - school boy : écolier - boys' band : groupe vocal de garçons - a boy cyclist : un jeune cycliste - a girl cyclist : une jeune cycliste
* boy ! (exclam.) : bon sang !
* boy-friend / boyfriend (n) : petit ami, amoureux
* bpm (n) (init. de beats per minute) : (nombre de) battements à la minute, cadence (f)
* bra (n) (tronc. de brassiere) (fa) : soutien-gorge
* brace (n) : 1/ appareil dentaire ; 2/ attelle (f) ; 3/ couple, paire (f) ; 4/ vilebrequin ; 5/ accolade (f) ; 6/ braces (n pl.) (brit.) : bretelles (f pl.)
* brace (v) : 1/ (tr.) arc-bouter - to brace one's feet on sth : appuyer ses pieds contre qch ; 2/ (tr.) renforcer, fortifier - to brace oneself : rassembler ses forces ; 3/ (tr.) tonifier ; 4/ (intr.) se préparer (for, à)
* bracing (a) : tonique
* bracket (n) : 1/ équerre (f) (for a shelf, etc., d'une étagère, etc.) ; 2/ patte de fixation ; 3/ (Typo.) parenthèse (f), crochet - (Math.) angle brackets : chevrons - curly brackets : accolades (f) (syn. braces) - round brackets : parenthèses ; 4/ tranche (f) (d’âge, de revenu) - income bracket : tranche de revenu
* bracket (v) (tr.) : mettre entre parenthèses - to bracket together : mettre entre parenthèses / crochets
* braid (n) : tresse (f)
* brain (n) : 1/ cerveau - brain bleed : hémmoragie cérébrale - brain cancer : cancer au cerveau - brain child : idée personnelle (f), invention personnelle (f) - a brain drain of experts : une fuite de cerveaux - brain teaser : casse-tête (le jeu) (masc.) (aussi brainteaser) - brains trust : état-major ; 2/ (brains, pl.) cervelle (f), tête (f) - to pick sb’s brains : faire appel aux lumières de qn - to rack one’s brains : se creuser la tête
* brain-damaged (ac) : atteint(e) de lésions cérébrales
* brain-dead (ac) : en état de mort cérébrale
* brainpower (n) : ressources intellectuelles (f) pl.), ressources en intelligence
* brainstorm (v) (intr.) : se remuer les méninges, cogiter - to brainstorm about new high-tech ideas : faire assaut d’idées pour trouver de nouvelles techniques
* brainstormer (n) : participant à une séance de remue-méninges
* brainstorming (n) : assaut d'idées, recherche d'idées, libre expression d'idées, remue-méninges (masc., inv.) (l'activité) - brainstorming session : remue-méninges (la séance)
* brainteaser (n) : casse-tête (le jeu) (masc.) (aussi brain teaser)
* brainwash (v) : (tr.) lessiver le cerveau de, faire un lavage de cerveau à, bourrer le crâne de - They brainwashed us into believing that we are fighting for our rights : Ils nous ont mis dans la tête que nous luttions pour nos droits
* brainwave (n) : idée lumineuse (f), inspiration (f)
* brainy (a) : intelligent(e), futé
* braise (v) (tr.) : faire mijoter, braiser (du bœuf, etc.)
* braising (n) : cuisson (f)
* brake (n) : frein - to apply the brakes fully : actionner les freins à fond - brake adhesion : adhérence au freinage
* brake (v) (tr. et intr.) : freiner
* braking (n) : freinage - braking system : système de freinage
* bramble (n) : ronce (f)
* branch (n) : 1/ branche (f) (d’arbre, d’une famille) - a branch of futurology : une branche de la futurologie ; 2/ agence (f), succursale (f), filiale (f) - branch office : succursale ; 3/ (Inf.) branchement, rupture de séquence
* branch (v) : (intr.) se ramifier - to branch away from : se séparer de
* brand (n) : marque (f) - global brand : marque mondiale - luxury brand : marque de luxe - brand name : nom de marque, marque de commerce - brand partner : associé de la marque
* brand (v) (tr.) : 1/ marquer (les bêtes d’un troupeau) ; 2/ flétrir, stigmatiser (syn. to stigmatise/ize)
* branded (a) : de marque - branded products : produits de marque
* branding (n) : 1/ marquage au fer, ferrage (des bêtes d’un troupeau) - cattle branding : marquage du bétail, ferrage du bétail ; 3/ création de l’image de marque ; 4/ valorisation de la marque
* brand new (a) : tout neuf, toute neuve, flambant neuf, flambant neuve
* brash (a) : 1/ impudent(e), effronté, impertinent(e) ; 2/ impétueux/-euse, irréfléchi(e), téméraire, imprudent(e)
* brass (a) : en cuivre (jaune), en laiton
* brass (n) : 1/ cuivre (jaune), laiton ; 2/ (Mus.) (les) cuivres ; 3/ (Milit.) (les) gradés - the top brass : les haut gradés ; 4/ (les) huiles (f pl.) (fam.), (les) gros bonnets (fam.), (les) grosses légumes (fam.) - the top brass : les grosses légumes (fig.) (f pl.)
* brassed off (ppa) (brit., arg.) : - to be / to get brassed off : en avoir ras le bol, en avoir ras la casquette
* brat (n) (péj.) : sale gosse, sale gamin, sale gamine, sale morveux, sale morveuse, petit morveux, petite morveuse - army brat : enfant de militaires
* bravado (n) : bravade (f) - in a voice full of bravado : d’un ton bravache
* brave (a) : brave, hardi(e), courageux/-euse - the brave (a. subst. pl.) : les braves - the home of the brave : la patrie des braves
* bravely (adv.) : courageusement
* brawl (n) : mêlée (f) - mass brawl : mêlée générale
* brawny (a) : musclé, baraqué (syn. beefy)
* bray (n) : braiment
* bray (v) (intr.) : braire
* braying (n) : braiment
* brazen (a) : effronté
* brazenly (adv.) : effrontément
* Brazil (n pr.) : (le) Brésil
* Brazilian (a) : brésilien(ne) (sans maj.)
* Brazilian (n pr.) : Brésilien (le ressortissant)
* BrE (sigle) (abr. de British English : anglais britannique) : angl. brit.
* breach (n) (pl. breaches) : 1/ brèche (f) ; 2/ (fig.) infraction (f)
* breach (v) (tr.) : 1/ faire une brèche dans ; 2/ enfreindre
* bread (n) : pain - mouldy bread : pain moisi - raisin bread : pain aux raisins (secs) - wholemeal bread : pain complet - bread crumb : miette de pain (aussi breadcrumb) - the bread line : la soupe populaire (f) (l’institution) (aussi breadline) - bread riot : émeute du pain, émeute pour le pain
* breadcrumb (n) : miette de pain - trail of breadcrumbs : fil d’ariane (ausi breadcrumb trail) ; 2/ (pl. breadcrumbs) panure (f), chapelure (f)
* breadline (n) : soupe populaire (f) - to stand in a breadline : faire la queue à la soupe populaire
* breadth (n) : largeur (f)
* breadwinner (n) : soutien de famille
* break (n) : 1/ (Mus.) coupure (f), interruption (f) (d'un morceau pour laisser la place à un solo) ; 2/ pause (f) - to be on a break : être en pause - Give me a break! : Lâche-moi / Lâchez-moi ! - to take a break : faire une pause - prayer break : pause pour la prière - tea break : pause pour le thé ; 3/ vacances (f pl.) - beach break : vacances à la plage, séjour à la plage - spring break : vacances de printemps, relâche scolaire de printemps ; 4/ (Éduc.) récréation (f), récré (f) (fam.) (syn. amér. recess) ; 5/ brisants (pl.) - beach break : a/ brisants de sable ; b/ plage à brisants, site sur fond sableux - reef break : a/ brisants de récif ; b/ (Surf.) site sur récifs, site sur fond rocheux - shore break : rouleaux de bord (pl.)
* break (v) (broke, broken) : 1/ (tr.) casser, briser, rompre, péter (fam.) - to break the sound barrier : briser le mur du son - to break a string : péter une corde (de guitare) ; (intr.) se casser, se briser, se rompre ; 2/ (tr.) couper, interrompre ; (intr.) s’interrompre ; 3/ (intr.) - to break into (a house) : s'introduire par effraction dans (une maison) ; 4/ (tr.) annoncer, faire part de (news, une nouvelle), entamer (a subject, un sujet) - Who shall break the news to her? : Qui se chargera de lui apprendre la nouvelle ? - to break bad news : annoncr une mauvaise nouvelle - to break one’s mind to sb : faire part de ce que l’on pense à qn ; (intr.) [dawn, aube] percer, poindre - Morning has broken : Le matin point
* breakage (n) : bris, casse (f), rupture (f) - glass breakage : bris de verre, bris de vitre
* break apart (v) (intr.) : se rompre, se briser, se disloquer - The bridge broke apart : Le pont se disloqua
* breakaway (a) : séparatiste
* breakdown (n) : 1/ décomposition (f), répartition (f), ventilation (f) - a breakdown of employees by category : une répartition des employés par catégorie - (the) breakdown in terms of time : (la) répartition horaire ; 2/ défaillance (f), panne (f), avarie (f) - the breakdown van (brit.) : la dépanneuse ; 3/ (fig.) effondrement, désintégration (f) - a breakdown of society : un effondrement de la société, une désintégration sociale
* break down (v) (broke, broken) : 1/ (tr.) décomposer (into, en) ; (intr.) se décomposer (into, en), se réduire (into, à) ; 2/ (intr.) s’effondrer - to break down into tears : éclater en sanglots ; 3/ tomber en panne
* breaker (n) : 1/ - ball breaker : casse-couille (masc.) - circuit breaker : disjoncteur, coupe-circuit - law breaker : contrevenant à la loi, délinquant - quarantine breaker : personne réfractaire à la quarantaine ; 2/ (Éduc.) personne faisant une pause, personne prenant un congé - spring breaker (amér.) : élève ou étudiant faisant la pause de printemps, élève ou étudiant en relâche scolaire de printemps
* breakeven (n) : seuil de rentabilité, équilibre financier - to be at breakeven : être en équilibre financier, être au seuil de rentabilité - breakeven point : point d'équilibre, seuil de rentabilité
* break-in (n) : 1/ dressage (d’un cheval) ; 2/ effraction (f), cambriolage - attempted break-in : tentative d’effraction (f) - car break-in : vol par effraction dans une voiture
* break in (v) (broke, broken) (tr.) : dresser (un cheval)
* breakneck (a) : de casse-cou, dangereux - at breakneck speed : à toute allure, à tombeau ouvert, à toute berzingue (fam.)
* (the) breaks (n pl.) (fam.) : destin - These are the breaks : C’est le destin
* breakthrough (n) : 1/ percée (f), avancée (f) (syn. advance) - to be no small breakthrough : être une avancée considérable ; 2/ découverte (f), progrès important, innovation (f) (syn. discovery) - breakthrough product : produit novateur
* break up (v) (broke, broken) : (tr.) briser, démanteler ; (intr.) [couple] rompre, [groupe musical] se disloquer, éclater (syn. to split up - to disband)
* breakwater (n) : brise-lames (masc.), môle
* breast (n) : 1/ poitrine (f) - duck breast : magret de canard ; 2/ sein - sagging breasts : seins qui tombent - breast cancer : cancer du sein - breast pump : tire-lait (masc.)
* breast feed (vc) (tr. et intr.) : allaiter, donner le sein (à)
* breast feeding (n) : allaitement, le sein (anton. bottle feeding, le biberon)
* breastpin (n) : épingle de cravate (f)
* breath (n) : 1/ haleine (f), souffle - with bated breath : en retenant son souffle - to be out of breath : être essoufflé - to get one’s breath back : reprendre son souffle - to hold one’s breath : retenir son souffle - to take sb’s breath away : couper le souffle à qn - morning breath : haleine du matin, haleine matinale ; 2/ souffle (d’air) - breath of air : souffle d’air - Can we go get a breath of fresh air for a sec? : On peut aller prendre l’air un petit moment ?
* breathable (a) : respirable
* breathalyser (brit.) / breathalizer (amér.) (n) : alcooltest, étylotest, alcoomètre
* breathe (v) (tr. et intr.) : respirer
* breather (n) : pause (f), petite pause - I need a breather : Il faut que je souffle un peu - to take a breather : a/ reprendre son souffle ; b/ (fig.) faire une pause
* breathing (n) : respiration (f) - agonal breathing : respiration d’agonisant(e) - breathing difficulties : ennuis respiratoires
* bred (prét. de to breed)
* bred (pp de to breed)
* breed (n) : race (f), espèce (f) - a rare breed : une espèce rare
* breed (v) (bred, bred) : 1/ (tr.) élever, faire l’élevage de ; 2/ (tr.) engendrer - Repetitive work breeds boredom : Un travail monotone engendre l’ennui ; (intr.) se multiplier - to breed like flies : se multipier comme des mouches
* breeder (n) : 1/ reproducteur - The human breeder looks not more than two generations ahead, if that : Le reproducteur humain ne se projette pas au-delà de deux générations, et encore ; 2/ éleveur - cattle breeder : éleveur de bovins
* breeding (n) : 1/ reproduction (f) - breeding ground : pépinière (f) (for, de) ; 2/ éducation (f), savoir-vivre - He lacks breeding : Il manque de savoir-vivre
* breeze (n) : brise (f) - summer breeze : brise d’été, brise estivale
* brethren (n) (pl. irrég. de brother) : frères (en religion, en esprit, etc.)
* Breton (a) : breton(ne) (sans maj.)
* Breton (n pr.) : Breton (habitant), Bretonne (habitante)
* brew (n) : 1/ bière (f), mousse (f) (fam.) - Anybody want a brew? : Quelqu’un veut-il une bière ? ; 2/ (brit., région.) thé - Shall I make some brew? : Je fais du thé ?
* brew (v) (tr.) : 1/ (tr.) brasser (de la bière) ; (intr.) [bière] fermenter ; 2/ (tr.) faire, préparer (du café, du thé) ; (intr.) [thé] infuser, [café] passer ; 3/ (fig.) - [storm] to be brewing : [tempête] se préparer
* brewer (n) : brasseur
* brewery (n) (pl. breweries) : brasserie (f)
* briar (n) : églantier
* bribe (n) (fa) : pot-de-vin
* bribe (v) (tr.) : soudoyer (qn), acheter (qn), corrompre (qn)
* bribery (n) : corruption par pots de vin, paiement de pots de vin - media bribery : corruption des médias
* brick (n) : 1/ brique (f) - dry-laid bricks : briques posées à sec (pl.) ; 2/ - to drop a brick (brit.) : faire une gaffe
* bricklayer (n) : maçon (pour les briques)
* bride (n) (fa) : mariée, jeune mariée, future mariée, future (n)
* bridegroom (n) : marié, jeune marié, futur marié, futur (n)
* bridesmaid (n) : demoiselle d’honneur
* bridge (n) : 1/ pont - It’s water under the bridge : De l’eau a coulé sous les ponts depuis, C’est de l’histoire ancienne, C’est du passé maintenant - log bridge : pont de rondins, pont en rondins - railway bridge : pont de chemin de fer - signature bridge : pont emblématique, pont distinctif - suspension bridge : pont suspendu (syn. hanging bridge) - trestle bridge (amér.) : pont à chevalets ; 2/ arête (f) (of the nose, du nez)
* bridle (n) : bride (f) (for horses, de cheval) - bridle path : sente cavalière (f), piste cavalière (f)
* brief (a) : bref/brève - a brief piece of news : une nouvelle brève, une brève
* brief (n) : 1/ instructions (f pl.), consignes (f pl.), ordre de mission, topo (fam.) 2/ - individual product brief : description de produit ; 3/ dossier, mémoire (m), abrégé, exposé ; 4/ pl. briefs : slip (sing.) (anglic.) (voir briefs)
* brief (v) (tr.) : mettre au courant, mettre au parfum (fam.), breffer (francis.), affranchir, donner des instructions à, fournir des directives à, donner son ordre de mission à
* briefcase (n) : mallette (f), contenant d'affaires (jarg.)
* briefcase-size (ac) : de la taille d’une mallette
* briefing (n) : 1/ affranchissement, mise au courant, mise au parfum (fam.) - to attend a briefing : assister à une réunion d’information ; 2/ instructions (f pl.), consignes (f pl.), directives (f pl.)
* briefs (n pl.) : slip (sing.) (anglicisme) - men’s briefs : slips d’hommes
* brier (n) : bruyère (f)
* brig (n) : 1/ brick ; 2/ (arg. amér.) taule (f)
* brigade (n) : (Milit.) brigade
* bright (a) : 1/ brillant(e) - bright object : objet brillant - bright speck : point brillant ; 2/ vif / vive - bright yellow : jaune vif ; 3/ radieux/-ieuse - a bright future : un avenir radieux
* brighten (v) : 1/ (tr.) faire briller, éclairer ; (intr.) s’éclairer ; 2/ aviver
* brighten up (v) : (tr.) dérider ; (intr.) se dérider
* bright-hued (ac) : aux teintes vives
* brightly (adv.) : avec éclat
* brightness (n) : éclat, luminosité (f), intensité lumineuse (f) - brightness control : réglage de la luminosité
* brights (n pl.) : 1/ couleurs (f pl.) ; 2/ (amér.) feux de route
* brim (n) : bord, rebord (of a hat, d'un chapeau, of a cup, d'une tasse) - well brim : bordure de puits, margelle de puits
* brim (v) (intr.) : être plein(e) à déborder
* brine (n) : saumure (f)
* bring (v) (brought, brought) (tr.) : 1/ apporter (qch) - to bring a new meaning to a term : donner une nouvelle acception à une expression ; 2/ amener (qn) ; 3/ (expressions) - to bring sth to a halt : forcer qch à s’arrêter - to bring sth to the boil : amener qch à ébullition - to bring sth up to date : mettre qch à jour - to bring sth up to scratch : mettre qch à niveau
* bring about (v) (brought, brought) (tr.) : entraîner, amener
* bring back (v) (brought, brought) (tr.) : rapporter (qch)
* bring down (v) (brought, brought) (tr.) : 1/ amener (qn), apporter (qch) ; 2/ faire baisser ; 3/ abattre, descendre - to bring the whole place down : faire tout sauter ; 4/ poser (un avion) - [pilot] to bring down an aircraft : [pilote] poser un appareil ; 5/ renverser (un roi, etc.) ; 5/ donner le cafard à (qn), déprimer (qn)
* bring in (v) (brought, brought) (tr.) : rapporter (de l’argent) - How much money will it bring in? : Combien d’argent cela rapportera-t-il ?
* bring out (v) (brought, brought) (tr.) : sortir, publier
* bring up (v) (brought, brought) (tr.) : éduquer, élever
* brink (n) : bord (du précipice, de la guerre, etc.) - on the brink of war : au bord de la guerre, à deux doigts de la guerre - brink of war policy : politique du bord de l’abîme, politique de la corde raide
* brinksman (n) : stratège de la corde raide, stratège du bord de l’abîme, stratège du risque calculé
* brinksmanship (n) : stratégie de la corde raide, stratégie du bord de l’abîme, stratégie du risque calculé
* brisk (a) : 1/ vif/vive, alerte, actif/ive ; 3/ brusque, [ton, voix] sec / sèche
* brisken (v) : (tr.) activer ; (intr.) s’activer
* briskly (adv.) : 1/ vivement ; 2/ brusquement, sèchement
* bristle (n) : poil, soie (f) - stiff bristles : soies raides (d’une brosse)
* bristle (v) (intr.) : [poil] se hérisser
* Brit (a) (tronc. de British) (fam.) : britannique, angliche (fam.)
* Brit (n pr.) (tronc. de Briton) (fam.) : Britannique (le ressortissant), Angliche (fam.)
* briticism (n) : briticisme (var. britishism)
* British (a) : britannique (sans maj.) - British English : (l’)anglais britannique - the British Museum : le Musée britannique - the British pound : la livre britannique
* British-based (ac) : implanté en Grande-Bretagne
* britishism (n) : briticisme (var. briticism)
* Briton (n pr.) : Britannique (le ressortissant), Angliche (fam.) - fellow Briton : compatriote britannique
* Brittany (n pr.) : (la) Bretagne
* brittle (a) : fragile, cassant(e)
* broach (n) : 1/ foret ; 2/ broche (f)
* broach (v) (tr.) : 1/ (Usin.) brocher ; 2/ (angl. gén.) entamer (a discussion, une discussion), aborder (a subject, un sujet) - to broach an awkward subject : aborder un sujet délicat
* broaching (n) : (Usin.) brochage - broaching machine : machine à brocher
* broad (a) : large - broad range of products : large gamme de produits
* broadband (n) : large bande (f) - broadband modem : modem à large bande, modem à haut débit
* broadcast (n) : émission (f) (radio, télé) - live broadcast : émission en direct - pre-recorded broadcast : émission en différé - broadcast journalist : journaliste de radiotélédiffusion, journaliste de radiotélévision
* broadcast (v) (-cast, -cast ou régulier) (tr.) : diffuser (à la radio, à la télé)
* broadcast (prét. de to broadcast)
* broadcast (pp de to broadcast)
* broadcaster (n) : diffuseur - private broadcaster (selon le cas) radiodiffuseur privé, télédiffuseur privé, chaîne de radio privée, chaîne de télé privée - public broadcaster (selon le cas) radiodiffuseur public, télédiffuseur public, chaîne publique de radio, chaîne publique de télévision - radio broadcaster radiodiffuseur, chaîne de radio - state broadcaster a/ radio d’État, radio nationale, b/ télévision d’État, télévision nationale, c/ radiotélédiffusion d’État, radiotélédiffusion nationale, (l’)audiovisuel public - television broadcaster : télédiffuseur, chaîne de télé
* broadcasting (n) : 1/ diffusion (f) - broadcasting company : société de diffusion ; 2/ (selon le cas) radiodifusion (f), télédiffusion (f) - radio broadcasting : radiodiffusion - TV broadcasting : télédiffusion
* broaden (v) : (tr.) élargir ; (intr.) s’élargir
* broad-leafed (a) : feuillu(e) - broad-leafed forest : forêt de feuillus
* broadly (adv.) : 1/ largement ; 2/ en gros, grosso modo
* broad-minded (ac) : aux idées larges
* broadsheet (n) : 1/ (abr. de broadsheet format ou size) grand format, plein format; 2/ (abr. de broadsheet paper) journal grand format, journal plein format, journal sérieux
* brochure (n) : brochure (f), plaquette (f)
* brogue (n) : accent irlandais
* broil (v) : (tr.) faire griller (de la viande), faire cuire (de la viande) sur le grill ; (intr.) (amér.) (fig.) griller, connaître une chaleur torride
* broiler (n) : volaille à point pour la broche - broiler chicken : poulet à la broche électrique
* broil pan (n) : lèchefrite (f)
* broke (prét. de to break)
* broke (a) (tjrs attribut) : sans le sou, sans un rond (fam.), fauché (fam.) - to go broke : se retrouver sans le sou - to go for broke : jouer son va-tout, jouer le tout pour le tout
* broken (pp de to break)
* broken (ppa) : 1/ brisé - broken tools : (Usin.) outils (de coupe) brisés, bris d'outils (de coupe) - broken stones : pierraille (f) (sing.) ; 2/ (fig.) [langue parlée] écorché - broken English : anglais écorché, mauvais anglais, anglais approximatif, anglais boiteux
* broker (n) : 1/ courtier/-ière (dans le commerce, les assurances, etc.) - investment broker : courtier en placements - real-estate broker (amér.) : agent immobilier ; 2/ - power broker (amér.) : personnage influent, homme influent, femme influente, éminence grise (f)
* broker (v) (tr.) : négocier - to broker a ceasefire : négocier un cessez-le-feu
* brokerage (n) : commerce des valeurs en bourse, courtage (syn. stockbroking)
* brolly (n) (abr. fam. de umbrella) : parapluie, pépin (fam.)
* bronchitis (n) : bronchite (f)
* bronco (n) (pl. broncos) (amér.) : cheval sauvage - bronco buster : champion de rodéo - bronco busting : dressage de chevaux sauvages
* brood (n) : couvée (f), nichée (f)
* brood (v) : 1/ (tr.) couver (eggs, des œufs) ; 2/ (intr.) méditer tristement (over, sur)
* broody (a) : pensif/-ive, maussade
* brook (n) : ruisseau
* brook (v) (tr.) : supporter, tolérer, admettre
* broom (n) : balai
* broth (n) : bouillon - Too many cooks spoil the broth : Deux patrons font chavirer la barque
* brothel (n) : bordel, maison close (f)
* brother (n) : frère
* brotherhood (n) : 1/ fraternité (f) ; 2/ confrérie (f)
* brought (prét. de to bring)
* brought (pp de to bring)
* brow (n) : 1/ front ; 2/ sourcil ; 3/ expression faciale (f), visage ; 4/ sommet (de colline)
* browbeat (v) (-beat, -beaten) (tr.) : traiter avec hauteur, traiter avec dédain
* browbeat (prét. de to browbeat)
* browbeaten (pp de to browbeat)
* brown (a) : 1/ brun(e) - brown goods : (l’)électronique grand public - brown hyena : hyène brune - to be in a brown study : être absorbé dans ses pensées, être dans les nuages - to do sb brown : filouter qn ; 2/ marron (la couleur)
* brown (v) : (intr.) [plants, végétaux] jaunir
* brownfield (n) (amér.) : friche industrielle (f), site industriel désaffecté
* brownie (n) (amér.) : gateau au chocolat, brownie - hash brownie : brownie au cannabis (syn. pot brownie)
* browning (n) : jaunissement (des végétaux) - [tree, arbre] to be prone to browning : être sujet au jaunissement
* to brown-nose (v) (tr.) : lécher le cul de (qn) (vulg.), cirer les pompes de (qn)
* brown-nosing (n) : (la) lèche (vulg.), (le) cirage de pompes, (le) faillotage
* brown off (v) (brit.) (tr.) : 1/ énerver ; 2/ saouler (fam.), gaver (fam.) - to be browned off : en avoir plus que marre, en avoir ras la casquette
* browse (v) (Inf.) : (tr.) feuilleter, explorer, naviguer sur - to browse search results : compulser les résultats d'une recherche - to browse the Internet : explorer l'Internet ; (intr.) flâner, se promener, baguenauder (fam.), musarder - to browse through a directory : parcourir un répertoire
* browser (n) : (Inf.) logiciel de navigation, lecteur navigationnel, navigateur
* browsing (nv) : (Inf.) survol, exploration, navigation, consultation, flânerie, baguenaudage, furetage (Q), butinage (sur l'Internet)
* bruiser (n) : malabar, armoire à glace
* bruit (v) (arch., amér.) (tr.) : ébruiter - to bruit a rumour : ébruiter une rumeur
* brunet (a) (amér.) (en parlant d’un homme) : brun, aux cheveux bruns
* brunet (n) (amér.) : (en parlant d’un homme) brun
* brunette (a) (amér.) : (en parlant d’une femme) brune, aux cheveux bruns - Men prefer blondes but marry brunettes : Les hommes préfèrent les blondes mais épousent des brunes
* brunette (n) (amér.) : (en parlant d’une femme) brune (n)
* brunt (n) : poids - to bear the brunt of… : soutenir le choc de, subir ou essuyer le plus gros d’(une attaque)
* brush (n) (pl. brushes) : 1/ brosse (f) ; 2/ pinceau
* brushstroke (n) : coup de pinceau
* brush up (v) (tr.) : rafraîchir - to brush up one’s English : se remettre à son anglais, rafraîchir ses connaissances en anglais
* brutal (a) : brutal(e)
* brutality (n) : brutalité (f)
* brutalise (brit.) / brutalize (amér.) (v) (tr.) : 1/ rendre brutal(e) ; 2/ brutaliser, maltraiter - Student brutalised by cops (Titre) : Un étudiant maltraité par les flics
* BS (init. de bachelor of science : licence ès sciences)
* bsh. (n) (Mes.) (abr. de bushel : boisseau)
* bubble (n) : bulle (f) - housing bubble : bulle immobilière - speculative bubble : bulle spéculative - the tech bubble : la bulle technologique, la déconfiture des valeurs technologiques (en 2000) - bubble wrap : papier bulle
* bubbly (a) : pétillant(e), à bulles
* bubbly (n) : (selon le cas) champagne, mousseux (n)
* bubonic (a) : bubonique - bubonic plague : (la) peste bubonique
* buck (n) : 1/ [animal] mâle ; 2/ gaillard - old buck : mon vieux ; 3/ (amér.) (fam.) dollar
* bucket (n) : 1/ seau - rust bucket : a/ tas de ferraille, tas de rouille ; b/ (selon le cas) vieux rafiot, vieux tacot, épave (f) - bucket drumming : percussion de seaux - bucket list : liste de choses à faire avant de mourir ; 2/ godet - bucket loader : chargeur à godets ; 3/ (expressions) - a drop in a bucket (amér.) : une goutte d’eau dans l’océan - to kick the bucket : casser sa pipe (fam.), lâcher la rampe (fam.), trépasser
* buckle (n) : boucle (f) (de ceinture, de chaussure), attache (f)
* buckle (v) : 1/ (tr.) boucler ; 2/ (tr.) déformer, gauchir ; (intr.) [métal] se déformer, [roue] se voiler, [jambes] se dérober ; 3/ (fig.) céder
* buckshot (n ind. sing.) : chevrotine (f)
* buck tooth (nc) (svt au pl. buck teeth) : dent qui avance, dent de lapin
* buck-toothed (ac) : qui a des dents de lapin
* buck up (v) (brit.) : (tr.) remonter le moral à (qn) - to buck oneself up : se remonter le moral ; (intr.) a/ se secouer ; b/ se grouiller (fam.), se magner (fam.)
* buddy (n) (amér.) : copain, pote (fam.) - my buddies and me : mes potes et moi - buddy movie : duo de choc, binôme de flics, film à tandem
* budge (v) (intr.) : 1/ bouger : 2/ changer d'avis
* budgerigar (n) : perruche (f)
* budget (n) : budget
* budget (v) (tr. et intr.) : budgetiser
* budgetary (a) : budgétaire - budgetary crisis : crise budgétaire (f)
* budgie (n) (abr. du précédent) : perruche (f)
* buff (n) : 1/ chamois ; 2/ amateur/-trice, mordu(e), passionné - film buff : cinéphile (m/f) (syn. film fan - film enthusiast - cinema lover) - computer buff : mordu de l’informatique - history buff : passionné d’histoire
* buff (v) (tr.) : 1/ polir ; 2/ émeuler
* buffer (n) : 1/ (Usin.) tampon - to hold one week’s stock as a parts buffer : avoir une semaine de stock de pièces - buffer stocks : stocks régulateurs ; 2/ tampon (de wagon), pare-choc (d’automobile) ; 3/ (Inf.) mémoire-tampon (f)
* buffer (v) (tr.) : (Inf.) mettre en mémoire-tampon
* buffering (n) : (Inf.) mise en mémoire-tampon
* buffet (v) (tr.) : [vagues] battre, [vent, pluie] cingler
* buffeting (n) : coups (pl.), chocs (pl.) - to take the buffeting : supporter les coups, tenir le coup
* buffoon (n) : bouffon
* buffoonish (a) : buffonesque
* bug (n) : 1/ insecte, petite bête (f) (fam.), bestiole (f) (fam.) ; 2/ punaise (f) ; 3/ microbe, saleté (f), saloperie (f) (qu’on attrape) ; 4/ (fig.) virus (au sens d’intérêt pour qch) - to get bitten by the bug of… : être mordu par le virus de…
* bugbear (n) : bête noire (f), cauchemar
* bugger (n) (brit.) (vulg.) : bougre - Silly bugger! : Bougre d’imbécile !
* bugger about (v) (intr.) (brit.) (vulg.) : faire le con, faire l’idiot, faire l’âne
* bugger all / bugger-all (n) (brit.) (vulg.) : que dalle (syn. sod all / sod-all, fuck all / fuck-all) - It costs me bugger-all : Ça ne me coûte que dalle
* bugger off (v) (intr.) (brit.) : foutre le camp, aller se faire foutre - Bugger off! : Vas te faire foutre / Allez vous faire foutre !
* buggery (n) (brit.) (vulg.) : sodomie
* Buggery! (interj.) (brit.) (vulg.) : Merde !, Putain !
* buggy (n) : chariot - overhead motorised buggy : chariot aérien automatisé - moon buggy : jeep lunaire (f)
* bugle (n) : clairon
* bug out (v) (intr.) : déguerpir, prendre la poudre d'escampette (fam.), se tirer (fam.), se casser (fam.)
* bug-out (mod.) : - bug-out bag : sac d'évacuation, sac de survie, sac d'escampette - bug-out location : retraite de survie
* bug-ridden (ac) : 1/ infesté de bestioles, (selon le cas) plein(e) de puces, (selon le cas) plein(e) de punaises ; 2/ (fig.) [programme informatique] infesté de bogues, truffé d’erreurs
* build (v) (built, built) : 1/ (tr.) bâtir, construire, édifier - to build sth into… : incorporer qch à…, intégrer qch à… ; 2/ (tr.) fabriquer - to build (sth) to order : fabriquer (qch) à la demande - to build (sth) to stock : fabriquer (qch) selon les stocks ; 3/ (tr.) fonder, établir (on, sur) ; (intr.) tirer parti (on, de)
* build (n) : 1/ constitution (f), carrure (f) (d'un homme) ; 2/ structure (f) (d'un objet) - the basic build of a tyre : la structure de bas d’un pneu ; 3/ (Inf.) version (f)
* builder (n) : 1/ constructeur/-euse, bâtisseur/-euse - aircraft builder : constructeur d’avions, constructeur aéronautique - field builder : faiseur de champs ; 2/ - vocabulary builder : entraînement de vocabulaire, leçons de vocabulaire (f pl.)
* buildering (n) : (la) grimpe urbaine
* building (n) : 1/ (l’activité) construction (f) (d’ouvrages, de structures de toute sorte) - log building : (la) construction en rondins - wall building : construction de murs - building work : travail sur une chantier de construction ; 2/ (l’activité) construction (de machines), fabrication (f) - building to order : fabrication à la demande - machine building : construction de machines-outils, fabrication de machines-outils - building society : société de crédit immobilier ; 3/ (fig.) édification (f), développement - skill building : acquisition de compétences, développement de compétences ; 4/ (le résultat, a building) bâtiment, édifice, construction (f), bâtisse (fam.) (f), immeuble - to collapse a building : provoquer l’effondrement d’un bâtiment - building at risk : chef-d’œuvre en péril - log building : bâtiment en rondins - high-rise building : immeuble de grande hauteur, tour (f) - multi-story building : immeuble - office building : immeuble de bureaux
* build up (v) (built, built) : (tr.) faire monter - to build up tension : faire monter la tension ; (intr.) : s’accumuler (syn. to pile up)
* build-up (n) : accumulation (f) - debris build-up : accumulation de débris
* built (prét. de to build)
* built (pp de to build)
* built-in (ac) : incorporé (d’origine), intégré - built-in cupboard : placard mural
* bulb (n) (fa p) : 1/ bulbe, oignon ; 2/ ampoule (électrique) (f) - 100-watt bulb : ampoule de 100 watts - The bulb blew : L'ampoule a sauté / grillé
* Bulgaria (n pr.) : (la) Bulgarie
* Bulgarian (a) : bulgare (sans maj.)
* Bulgarian (n pr.) : Bulgare (le ressortissant, la ressortisante)
* bulge out (v) (intr.) : se renfler
* bulging (a) : [yeux] globuleux/-euse, [muscles] protubérant(e) - bulging biceps : gros biceps
* bulk (n) : 1/ majeure partie (f), plus grande partie (f), plus grosse partie (f), essentiel (n) - the bulk of… : la plus grosse partie de… - the bulk of the customers : le gros des clients ; 2/ volume ; 3/ vrac - in bulk : en vrac - bulk cargo : fret en vrac, marchandises en vrac (pl.) - bulk carrier : vraquier, transporteur de vrac
* bulky (a) : 1/ [objet] volumineux/-euse, encombrant(e) ; 2/ [individu] corpulent(e)
* bulkiness (n) : 1/ caractère volumineux ; 2/ corpulence (f)
* bull (a) : [marché] spéculateur à la hausse, haussier/-ière - bull market : marché haussier
* bull (n) : taureau
* bull (n) (amér.) (abr. de bullshit : conneries (f pl.), foutaises (f pl.)) - a load of bull : un tas de conneries
* bull (v) (intr.) : (Bourse) spéculer à la hausse
* bullcrap (n) (amér.) : conneries (f pl.), foutaises (f pl.) - That is such a load of bullcrap! : C’est vraiment un tas de conneries !
* bulldoze (v) (tr.) : raser
* bulldozer (n) : bulldozer, bouteur (off.) - bulldozer operator : conducteur de bouteur
* bullet (n) (fa) : balle (f) (le projectile) - to fire rifle bullets : tirer des coups de fusil - bullet time (Photog.) : effet de ralenti à la Matrix (syn. frozen time)
* bulletin (n) : avis, annonce (f) - news bulletin (brit.) : bulletin d'informations, bulletin d'infos, bulletin de nouvelles - bulletin board : tableau d’affichage électronique (de petites annonces)
* bullet-proof / bulletproof (ac/a) : à l’épreuve des balles, pare-balles, blindé - bullet-proof vest : gilet pare-balles - bulletproof windows : vitres blindées (f pl.)
* bullhorn (n) (amér.) : porte-voix, mégaphone
* bullion (n) : (selon le cas) argent en lingots, or en lingots - stock of bullion : encaisse métallique (f)
* bullish (a) : (Bourse) haussier/-ière - bullish tendency : tendance haussière (f) (anton. bearish)
* bullring (n) : arène (f)
* bullroarer (n) (brit.) : (Mus.) rhombe (masc.)
* bullshit (n) (amér.) : 1/ connerie (f), conneries (pl.), foutaise (f), foutaises (pl.), merde (f) (vulg.) - I don’t have time for this bullshit! : Je n’ai pas de temps à perdre à ces conneries ! - I’m done with this bullshit! : J’en ai terminé avec cette merde ! - bullshit job : boulot à la con, boulot de merde (vulg.), boulot merdique (vulg.) ; 2/ affabulations (f pl.), mensonges (pl.)
* bullshit! (interj.) : connerie !, conneries !, foutaise !, foutaises !
* bullshit (v) : 1/ (tr.) raconter des conneries à (qn), dire des conneries à (qn), se foutre de (qn) ; (intr.) déconner
* bullwhip (n) : nerf de bœuf, cravache (f)- bullwhip effect : (amplification des variations de la demande à mesure qu'on s'éloigne du client) effet coup de fouet (dans une chaîne logistique)
* bully (n) : brute (n) (f) - a classroom bully : la terreur de la classe, un caïd
* bully (v) (tr.) : 1/ intimider ; 2/ brutaliser, malmener (syn. to ill-treat)
* bully (a) (amér. et colonial) : épatant(e)
* bully (interj.) : bravo ! - Bully for him : Il mérite un bravo
* bullying (n) : intimidation (f)
* bulrush (n) : quenouille (f)
* bulwark (n) : rempart
* bum (n) : 1/ (amér.) clochard, clodo (fam.), vagabond - street bum : clochard ; 2/ fainéant, glandeur, flemmard ; 3/ (brit.) cul, derrière, postérieur, pétard (fam.) (syn. arse, bottom, behind)
* bum (v) (tr.) (tr.) : demander (qch) - to bum a lift (somewhere) : se faire déposer (quelque part)
* bum around (v) (intr.) (amér.) : 1/ vagabonder ; 2/ (fam.) glander, glandouiller
* bumbag (n) (brit.) : banane (f) (sac à la ceinture)
* bummer (n) (arg. amér.) : déception (f) - What a bummer! : Quelle déception !, C’est nul !
* bummer ! (interj.) (arg. amér.) : mince !, zut !, dommage !, quelle poisse !, la poisse !
* bump (v) (intr.) : heurter, cogner - to bump into sb : bousculer qn, rentrer dans qn
* bump along (v) (intr.) : rouler en cahotant
* bumper (n) : pare-choc (masc.)
* bumpy (a) : cahoteux/-euse
* bum rap (n) (arg.) : fausse accusation (f), accusation injuste (f) - I got a bum rap : on m’a accusé injustement ; 2/ coup monté ; 3/ condamnation d’un innocent
* bun (n) : petit pain - chocolate bun : petit pain au chocolat (syn. chocolate-filled roll)
* bunch (n) (pl. bunches) : 1/ groupe, bande (f) - bunch of crooks : bande d’escrocs ; 2/ bouquet - bunch of flowers : bouquet de fleurs
* bunch (v) : (tr.) regrouper, rassembler - to bunch electric cables : regrouper des câbles électriques ; (intr.) se regrouper, se rassembler, s’agglutiner (aussi to bunch up, tr. et intr.)
* bundle (n) : 1/ paquet ; 2/ (arg. amér.) paquet de fric, grosse somme d’argent
* bung (n) : tampon, bouchon, bonde (f)
* bung (v) (tr.) (arg. brit.) : mettre, ficher, foutre (vulg.)
* bungee (n) : corde élastique (f) - bungee cord : cordon élastique - bungee jumping : (le) saut à l’élastique
* bungee (v) (intr.) : sauter à l’élastique
* bungle (v) (tr.) : gâcher
* bung (up) (v) (tr.) : boucher (syn. to stop up, to stuff up)
* bunk (n) : 1/ couchette (f) (in a ship, train, dans un navire, un train) ; 2/ (de la) foutaise, (des) foutaises (f pl.) - to be complete bunk : être un ramassis de conneries - bunk and bilge : foutaises et billevesées (f pl.)
* bunker (n) : 1/ abri souterrain, bunker - nuclear-proof bunker : bunker anti-nucléaire - bunker buster : bombe pénétrante (f) ; 2/ (Naut.) soute (f) (d'un navire) ; 3/ (Golf) bunker
* bunker-busting (ac) : pénétrant(e) - bunker-busting munitions (brit.) : bombes pénétrantes
* bunk off (v) (arg. brit.) : (tr.) ne pas aller (work, au travail, school, en cours) ; (intr.) sécher les cours, sécher
* bunting (n) : guirlande de drapeaux
* buoy (n) : 1/ bouée (f) ; 2/ balise (flottante) (f)
* buoy (v) (tr.) : 1/ maintenir à flot ; 2/ baliser ; 3/ revigorer
* bur (n) : voir burr (n)
* burden (n) : 1/ charge (f) (à porter), fardeau, poids - the debt burden : le fardeau de la dette ; 2/ charge (f), obligation (f) - burden of proof : charge de la preuve, fardeau de la preuve à charge - burden of disproof : obligation de réfuter, fardeau de la preuve à décharge - the burden of disproof is on you : c’est à vous d’apporter le fardeau de la preuve à décharge
* burden (v) (tr.) : charger - [bridge] to be burdened with traffic : [pont] avoir à supporter de la circulation
* burdensome (a) : 1/ lourd(e), pesant(e) : 2/ (fig.) pesant(e), pénible - burdensome limitation : restriction pesante
* bureau (n) : 1/ (brit.) secrétaire (le meuble) (syn. amér. writing desk) ; 2/ (amér.) commode ; 3/ bureau (le lieu) - design bureau : bureau d’études ; 4/ agence - employment bureau : agence pour l’emploi ; 4/ brigade - the homicide bureau : (la) brigade criminelle
* bureaucrat (n) : bureaucrate
* bureaucratic (a) : bureaucratique
* burgeoning (part. prés. adj.) : naissant, en plein essor, en développement
* burger (n) : 1/ steak hâché ; 2/ burger
* burglar (n) : cambrioleur - cat burglar : monte-en-l’air, pl. monte-en-l’air - burglar alarm : dispositif anti-cambriolage
* burglarise (brit.) / burglarize (amér.) (v) (tr.) : cambrioler
* burglary (n) : cambriolage, vol avec effraction
* burgle (v) (tr.) : cambrioler (a house, une maison)
* Burgundian (a) : bourguignon(ne) (sans maj.)
* Burgundian (n pr.) : Bourguignon (l’habitant)
* Burgundy (n pr.) : (la) Bourgogne
* burial (n) : enterrement
* burl (n) : 1/ nœud (dans le bois) ; 2/ loupe (f), nodosité (f)
* burlap (a) (amér.) : en toile de jute, en toile à sac
* burlap (n) (amér.) : toile de jute, toile à sac
* burlap (v) (tr.) : emballer dans de la toile de jute
* burlap-covered (ac) : recouvert(e) de toile - burlap-covered water bottle : gourde recouverte de toile
* burly (a) : costaud(e)
* Burma (n pr) : (la) Birmanie
* Burmese (a) : birman(e) - the Burmese python : le python birman
* Burmese (n) : Birman(e)
* burn (v) (burnt, burnt (brit.) ou régulier) : 1/ (tr.) brûler ; (intr.) brûler- Something’s burning : Il y a quelque chose qui brûle ; 2/ (au fig.) - You’re burning : Tu brûles (au jeu de l’objet caché) - My ears are burning : Mes oreilles tintent - to be burnt : y laisser des plumes
* burner (n) : 1/ (l’artisan) charbonnier - charcoal burner : charbonnier ; 2/ (l’objet) - log burner : poêle à bois - perfume burner : brûle-parfum ; 2/ (Inf.) graveur - CD burner : graveur de CD
* burner (n) (tronc. de burner phone) : portable prépayé, téléphone prépayé
* burning (nv) (Inf.) : gravure (de CD) - CD burning : gravure de CD
* burnish (v) (tr.) : (Usin.) brunir
* burnishing (n) : (Usin.) brunissage - burnishing machine : machine à brunir
* burnoose (n) : burnous
* burn out (v) (brit. burnt, burnt, ou régulier) (intr.) : [ampoule] claquer, sauter - A bulb has burnt out : Une ampoule a claqué
* burnt (prét. brit. de to burn)
* burnt (pp de to burn) : brûlé - to be burnt : (fig.) y laisser des plumes
* burn up (v) (amér.) : 1/ (intr.) être brûlant de fièvre ; 2/ (tr.) (fig.) faire bouillir (qn) (de colère) ; (intr.) (fig.) exploser de fureur
* burn-up (n) (arg. amér.) : emportement, explosion de colère - to have a burn-up : s’emporter
* burp (n) : rot
* burp (v) : (intr.) roter ; (tr.) faire faire un rot à (un bébé)
* burr (n) 1/ bardane (f), chardon ; 2/ (Usin.) bavure (f), barbe (f), ébarbure (f) - to remove burrs : enlever des ébarbures
* burr (v) (tr.) : 1/ échardonner ; 2/ (Usin.) ébavurer, ébarber
* bursar (n) : trésorier, trésorière
* burst (n) : 1/ explosion (f), salve (f), poussée (f) ; 2/ rafale (f), paquet
* burst (v) (burst, burst) (intr.) : éclater, exploser
* burst (pp de to burst)
* burst (prét. de to burst)
* bury (v) (tr.) : enterrer, ensevelir
* bus (n) (pl. buses) : 1/ bus - fleet of buses : flotte de bus ; 2/ (amér.) car - a Greyhound bus : un car Greyhound
* bush (n) (pl. bushes) : douille (f) - ball bushes : douilles à billes
* bushel (n) : 1/ (Mes.) (obs.) boisseau (= 4 pecks) ; 2/ (Mes.) (amér.) boisseau (= 25 kg) ; 3/ (expression) - to hide under the bushel : mettre sous le boisseau - to hide one’s light under the bushel : cacher son talent
* bushing (n) : diabolo - rubber bushing : diabolo en caoutchouc
* bushwhacker (n) (amér.) : 1/ homme des bois ; 2/ (Hist. amér.) franc-tireur
* bushy (a) : 1/ broussailleux/-euse, en broussaille ; 2/ [cheveux, barbe, sourcils] touffu(e), fourni(e) - bushy eyebrows : sourcis touffus
* business (n) (pl. businesses) : 1/ commerce, activités commerciales (f pl.), affaires (f pl.) - to be in business : être dans les affaires - to do business : faire des affaires, faire du commerce - line of business : métier, activité professionnelle - big business : (les) grandes entreprises (f pl.) - core business : cœur de métier - education business : (le) marché éducatif - family-owned business : entreprise familiale (f) - business address : adresse professionnelle (f) - business affairs : affaires commerciales (f pl.) - business centre : quartier d’affaires, quartier commercial - business day : jour ouvrable, jour ouvré - business district : quartier d’affaires, quartier commercial - business insight : sens des affaires - business jet : avion d’affaires, biréacteur d’affaires - business model : modèle d’affaires - business name : raison sociale (f) - business partner : associé - business plan : plan d’affaires (anglic.), plan de création d’entreprise, plan de financement d’entreprise, projet d’entreprise - business planning : planification commerciale (f) - business people : hommes d’affaires, milieux d’affaires, cadres d’entreprise - business school : école de commerce - business trip : déplacement professionnel - business use : usage professionnel ; 2/ questions importantes (f pl.), choses (f pl.) dont discuter, occupations (f pl.), affaires courantes (f pl.) - to make it one’s business to + inf. : se faire un devoir de + inf., faire en sorte de + inf. - new order of business : nouvel ordre du jour ; 3/ affaires (pl.), problèmes, oignons (pl.) (fam.), problème - That is their business : C’est leur affaire, Ça les regarde - This is none of your business : Ce ne sont pas tes / vos affaires / oignons, Ça ne te / vous regarde pas ; 4/ besoins naturels (pl.) - to do one’s business : (pour des animaux) faire ses besoins, se soulager ; 5/ (a business, pl. businesses) entreprise (f), firme (f), société (f), affaire (f), boîte (f) (fam.) - to start one’s own business : démarrer sa propre affaire - business management : (la) gestion d’entreprises ; 6/ industrie (f) - the oil business : l’industrie du pétrole - the pharmaceutical business : l’industrie pharmaceutique - the semi-conductor business : l’industrie des semi-conducteurs
* business-friendly (ac) : favorable aux entreprises (anton. business-unfriendly : peu favorable aux entreprises)
* businessman (n) : hommes d’affaires - high-rolling busnessman : homme d’affaires flambeur
* business-unfriendly (ac) : peu favorable aux entreprises (anton. business-friendly : favorable aux entreprises) - business-unfriendly regulatory regime : législation faisant peu de cas des besoins des entreprises
* businesswoman (n) : femme d’affaires (f)
* busing (amér.) / bussing (brit.) (n) : ramassage scolaire
* busk (v) (intr.) : chanter dans les rues, jouer de la musique dans les rues
* busker (n) : chanteur des rues, musicien ambulant, musicien des rues
* buskin (n) : cothurne
* busking (n) : chanter dans les rues, être musicien ambulant
* bust (a) : 1/ foutu(e), fichu(e) ; 2/ en faillite - to go bust : faire faillite
* bust (n) (amér.) : effondrement, échec
* bust (n) : poitrine (f), buste
* bust (v) (bust, bust ou régulier (brit.)) : 1/ (tr.) casser, péter (fam.), abîmer, bousiller ; (intr.) casser, péter - Anything that busts we fix : Tout ce qui pète, on le répare ; 2/ (tr.) acculer à la faillite ; (intr.) faire faillite
* bust (prét. de to bust)
* bust (pp de to bust)
* bust and fix (ac) (formé sur It busts and we fix : Ça pète et on répare) : - a bust and fix approach : la méthode « Ça pète et on répare » / « On attend que ça pète pour réparer (var. ortho. bust ’n’ fix)
* buster (n) : 1/ bousilleur, casseur, briseur - bunker buster : bombe pénétrante (f) ; 2/ (fig.) pourfendeur/-euse - tax haven buster : pourfendeur des paradis fiscaux ; 2/ démystificateur/-trice, déboulonneur/-euse (syn. debunker) - myth buster : démystificateur/-trice ; 3/ dissolvant - clot buster : dissolvant de caillots ; 4/ - jargon buster : glossaire, explication de termes (f); 5/ dresseur/-euse - bronco buster : champion de rodéo
* bustle (n) : remue-ménage
* bustle (v) (tr.) : s’affairer
* bustless (a) : qui n’a pas de poitrine
* busty (a) : à forte poitrine, à la poitrine plantureuse, (hum.) qui a du monde au balcon
* busy (a) : 1/ occupé - I’m busy : J’ai à faire - to be busy + gér. : être occupé à + inf. 2/ [rue] animé
* but (prép.) (après any et composés, all, none) : si ce n’est, excepté, sauf, à part
* but (conj. de coord.) : 1/ mais ; 2/ (suivi de then) en revanche ; 3/ But… (en tête de phrase) : En revanche, Cependant
* but (conj. de restriction) : sans que - It never rains in California but it pours : Il ne pleut jamais en Californie sans qu’il pleuve à flots
* butcher (n) : boucher - a butcher’s knife / butcher knife : un couteau de boucher - a butcher’s slicer : trancheuse (f)
* but for (loc. prép.) : sans
* butler (n) : maître d’hôtel - the butler’s pantry : l’office
* butt (n) (fa) : 1/ ‘amér.) cul, derrière, popotin (fam.), fesses f pl.) - to wipe one’s butt : s’essuyer les fesses - big butt : gros popotin - butt licker (vulg.) : lèche-cul (vulg.) - butt plug : bouchon anal ; 2/ a/ mégot, b/ cigarette (f), sèche (f) (arg.), clope (f) (arg.) ; 3/ crosse (f) (de fusil) ; 4/ cible (f), victime (f) (d’une plaisanterie, d’une blague) ; 5/ croûton (de pain)
* butt (v) (tr.) : enfoncer (une porte) - to butt with one’s head : donner un coup de tête à ; (intr.) - to butt into a conversation : se mêler à une conversation, s’inviter dans une conversation
* butte (n) : butte (f), motte (f)
* butter (n) : beurre - butter market : marché au beurre
* butter (v) (tr.) : beurrer
* buttercup (n) : bouton d’or
* buttered (ppa) : burré, au beurre - buttered popcorn : pop-corn au beurre
* butterfly (n) (pl. butterflies) : papillon
* butthole (n) (amér.) (vulg.) : troufignon (vulg.)
* butt in (v) (intr.) : s’ingérer, interférer
* buttock (n) : fesse (f) - buttock augmentation : (Chir.) augmentation fessière / des fesses
* button (n) : 1/ bouton, touche (f) - focus button : (Photog.) touche de mise au point - like button : (le) bouton j’aime (anton. dislike button : (le) bouton je n’aime pas) - snooze button : bouton d’arrêt momentané (d’un réveil) ; 2/ nombril - belly button : nombril
* button up (v) (tr.) : boutonner (un vêtement)
* buttoned up (ppa) : [vêtement] boutonné
* buttonhole (n) : 1/ boutonnière (f) ; 2/ fleur à la boutonnière
* buttonhook (n) : tire-bouton (masc.)
* buttress (n) : 1/ contrefort ; 2/ (fig.) soutien
* butty (n) (brit.) : sandwich - chip butty : sandwich aux frites - fried egg butty : sandwich aux œufs frits - sausage butty : sandwich à la saucisse
* buy (v) (bought, bought) (tr.) : acheter - to buy oneself sth : s’acheter qch
* buy and pay for (v coord.) (tr.) : acheter et payer (des biens, des services)
* buyer (n) : acheteur - (a) new group of buyers : (une) nouvelle clientèle
* buying (n) : 1/ (l’action) achat ; 2/ achats (pl.) - panic buying : achats dictés par la panique - buying spree : fièvre d’achats
* buy out (v) (bought, bought) (tr.) : 1/ acheter la totalité de ; 2/ louer la totalité de – Did you buy this restaurant out? : Tu as loué tout le resto pour toi seul ? ; 3/ racheter
* buy up (v) (bought, bought) (tr.) : racheter
* buzz (n) : 1/ bourdonnement (des abeilles), chant (des cigales) ; 2/ vrombissement (de moteur) ; 3/ bruit des machines, brouhaha ; 4/ bouche-à-oreille, rumeur (sing.), rumeurs (pl.) - There has been a lot of buzz about the actor’s divorce : les rumeurs au sujet du divorce de l’acteur vont bon train - buzz phrase : locution à la mode - buzz word : mot à la mode, mot branché, terme ronflant, formule en vogue, expression qui fait fureur (aussi buzzword) ; 4/ sentiment de plaisir, sentiment d’exaltation (lié à la prise de drogue) - You get a buzz from it : Tu t’éclates ; 5/ coup de téléphone
* buzz (v) : 1/ (intr.) bourdonner ; 2/ (tr.) [hélicoptère] survoler - [car] to be buzzed : [voiture] se faire survoler (par un hélicoptère) ; 3/ to buzz for sb : appeler qn (au téléphone, par sonnerie, par bip) ; 4/ répandre (une rumeur)
* buzz about (v) (tr.) : - to buzz sth about : divulguer une chose, faire courir le bruit de qch
* buzzard (n) : buse
* buzz off (v) (intr.) : se tirer, dégager - Buzz off! : Tire-toi / Tirez-vous!, Dégage / Dégagez !
* buzzword (n) : mot à la mode, mot branché, terme ronflant, formule en vogue, expression qui fait fureur (var. aussi buzz word)
* by (prép.) : 1/ par + complément d'agent de la voie passive ; 2/ (expression du moyen) par, au moyen de ; 3/ + mode de transport : par, en ; 4/ (+ forme -ing, expression du moyen) en + p. prés. ; 5/ d'après, en fonction de, à ; 6/ (expression d'une surface) sur ; 7/ (+ proportion) de - by a third : d'un tiers - to reduce plant design time by a half : réduire de moitié le temps de conception des équipements ; 8/ (+ mesure) de - to vary by only two inches : varier seulement de deux pouces ; 9/ (+ indication de date ou d’heure) avant, dès, d’ici - by 1987 : a/ dès 1987 ; b/ d’ici 1987 - I’ll be back by midnight : Je serai de retour avant minuit - by the end of the year : d’ici la fin de l’année ; 10/ à côté de, à la hauteur de, près de - to stand by sb : soutenir qn ; 11/ en bordure de - by the Persian Gulf : en bordure du Golfe persique, sur le Golfe persique ; 12/ (dans certaines expressions) après, sur - layer by layer : couche après couche
* by and large (loc. adv.) : dans l’ensemble
* by comparison to / with (loc. prép.) : en comparaison de, par rapport à, auprès de, à côté de (cf in comparison to / with)
* byline (n) : (Presse) signature (f) - cod byline : signature fictive
* by means of (loc. prép.) : au moyen de, par l'intermédiaire de
* bypass (v) (tr.) : contourner, mettre en dérivation, découpler
* by-product (nc) : 1/ sous-produit ; 2/ effet indirect, résultat
* bystander (n) : spectateur, témoin
* by the time (loc. conj.) : le temps que
* by virtue of (loc. prép.) : en vertu de, du seul fait de, de par
* by-word (nc) : proverbe - to have become a by-word : être passé en proverbe - He is the by-word of the village : Il est la fable du village
icxq88iarykc759w8ovprroqfnhrss4
Wikilivres:GUS2Wiki
4
78643
775641
775610
2026-11-02T16:02:28Z
Alexis Jazz
81580
Updating gadget usage statistics from [[Special:GadgetUsage]] ([[phab:T121049]])
775641
wikitext
text/x-wiki
{{#ifexist:Project:GUS2Wiki/top|{{/top}}|This page provides a historical record of [[Special:GadgetUsage]] through its page history. To get the data in CSV format, see wikitext. To customize this message or add categories, create [[/top]].}}
Les données suivantes sont en cache et ont été mises à jour pour la dernière fois le 2026-11-01T07:19:47Z. {{PLURAL:5000|1=Un seul résultat|5000 résultats}} au maximum {{PLURAL:5000|est disponible|sont disponibles}} dans le cache.
{| class="sortable wikitable"
! Gadget !! data-sort-type="number" | Nombre d’utilisateurs !! data-sort-type="number" | Utilisateurs actifs
|-
|AncreTitres || 34 || 0
|-
|ArchiveLinks || 13 || 0
|-
|Barre de luxe || 36 || 1
|-
|BoutonsLiens || 42 || 0
|-
|CategoryAboveAll || 20 || 0
|-
|CategorySeparator || 23 || 0
|-
|CoinsArrondis || 100 || 1
|-
|CollapseSidebox || 36 || 0
|-
|CouleurContributions || 33 || 0
|-
|CouleursLiens || 26 || 0
|-
|DeluxeAdmin || 6 || 1
|-
|DeluxeEdit || 36 || 1
|-
|DeluxeHistory || 47 || 1
|-
|DeluxeImport || 19 || 1
|-
|DeluxeRename || 16 || 1
|-
|DeluxeSummary || 37 || 1
|-
|DevTools || 18 || 1
|-
|DirectPageLink || 26 || 1
|-
|Emoticons || 45 || 0
|-
|EmoticonsToolbar || 46 || 0
|-
|FastRevert || 37 || 1
|-
|FixArrayAltLines || 23 || 1
|-
|FlecheHaut || 67 || 1
|-
|GoogleTrans || 28 || 0
|-
|HotCats || 81 || 2
|-
|JournalDebug || 22 || 1
|-
|JournalEnTable || 3 || 1
|-
|LeftPaneSwitch || 6 || 0
|-
|ListeABordure || 30 || 1
|-
|LiveRC || 30 || 0
|-
|LocalLiveClock || 27 || 1
|-
|Logo || 42 || 0
|-
|MobileView || 18 || 1
|-
|NavigAdmin || 39 || 0
|-
|OngletEditCount || 45 || 0
|-
|OngletEditZeroth || 67 || 0
|-
|OngletGoogle || 29 || 1
|-
|OngletPurge || 57 || 0
|-
|OptimizedSuivi || 18 || 0
|-
|Popups || 61 || 0
|-
|RenommageCategorie || 7 || 2
|-
|RestaurationDeluxe || 12 || 1
|-
|RevertDiff || 35 || 3
|-
|ScriptAutoVersion || 17 || 1
|-
|ScriptSidebox || 32 || 0
|-
|ScriptToolbar || 43 || 0
|-
|SisterProjects || 18 || 0
|-
|SkinPreview || 4 || 0
|-
|Smart patrol || 2 || 1
|-
|SourceLanguage || 24 || 0
|-
|SousPages || 57 || 1
|-
|SpaceToolbar || 30 || 0
|-
|TableUnicode || 56 || 0
|-
|Tableau || 67 || 1
|-
|TitreDeluxe || 56 || 1
|-
|TitreHierarchique || 17 || 0
|-
|UTCLiveClock || 27 || 0
|-
|UnicodeEditRendering || 29 || 1
|-
|WikEd || 30 || 0
|-
|autonum || 2 || 0
|-
|massblock || 3 || 1
|-
|monBrouillon || 20 || 0
|-
|perpagecustomization || 17 || 0
|-
|recentchangesbox || 15 || 0
|-
|searchFocus || 19 || 0
|-
|searchbox || 30 || 1
|}
* [[Spécial:GadgetUsage]]
* [[m:Meta:GUS2Wiki/Script|GUS2Wiki]]
<!-- data in CSV format:
AncreTitres,34,0
ArchiveLinks,13,0
Barre de luxe,36,1
BoutonsLiens,42,0
CategoryAboveAll,20,0
CategorySeparator,23,0
CoinsArrondis,100,1
CollapseSidebox,36,0
CouleurContributions,33,0
CouleursLiens,26,0
DeluxeAdmin,6,1
DeluxeEdit,36,1
DeluxeHistory,47,1
DeluxeImport,19,1
DeluxeRename,16,1
DeluxeSummary,37,1
DevTools,18,1
DirectPageLink,26,1
Emoticons,45,0
EmoticonsToolbar,46,0
FastRevert,37,1
FixArrayAltLines,23,1
FlecheHaut,67,1
GoogleTrans,28,0
HotCats,81,2
JournalDebug,22,1
JournalEnTable,3,1
LeftPaneSwitch,6,0
ListeABordure,30,1
LiveRC,30,0
LocalLiveClock,27,1
Logo,42,0
MobileView,18,1
NavigAdmin,39,0
OngletEditCount,45,0
OngletEditZeroth,67,0
OngletGoogle,29,1
OngletPurge,57,0
OptimizedSuivi,18,0
Popups,61,0
RenommageCategorie,7,2
RestaurationDeluxe,12,1
RevertDiff,35,3
ScriptAutoVersion,17,1
ScriptSidebox,32,0
ScriptToolbar,43,0
SisterProjects,18,0
SkinPreview,4,0
Smart patrol,2,1
SourceLanguage,24,0
SousPages,57,1
SpaceToolbar,30,0
TableUnicode,56,0
Tableau,67,1
TitreDeluxe,56,1
TitreHierarchique,17,0
UTCLiveClock,27,0
UnicodeEditRendering,29,1
WikEd,30,0
autonum,2,0
massblock,3,1
monBrouillon,20,0
perpagecustomization,17,0
recentchangesbox,15,0
searchFocus,19,0
searchbox,30,1
-->
p7p113537q32gz02j7rtawjimt3plp1
Fonctionnement d'un ordinateur/Les microarchitectures pour le x86 : haute performance
0
82608
775634
775128
2026-11-02T15:05:56Z
Mewtow
31375
775634
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
==Introduction : généralités et historique==
Avant de voir les processeurs dans un ordre chronologique, il est bon de faire quelques généralités, histoire de bien cadrer ce qui va suivre. Les architectures Intel et AMD se classent en deux types : une classe de microarchitectures pour la haute performance, une classe de microarchitectures moins performante mais plus économe.
La première est utilisée sur les PC fixes et parfois sur les serveurs, les processeurs pour ''desktop'' et serveur pouvant être différents. Les microarchitectures pour serveurs sont souvent des variantes des microarchitectures pour ''desktop''. Les serveurs ont surtout besoin d'exécuter un grand nombre d programmes/processus en même temps, ce qui fait qu'elles ont besoin d'un grand nombre de cœurs moins de cœurs performants individuellement. Pour les applications ''desktop'', c'est l'inverse : elles ont besoin de cœurs performants, quitte à en avoir moins.
Pour la seconde catégorie, parler de microarchitecture basse consommation serait peu-être un peu exagéré, car elles ne visent pas non les smartphones et autres appareils embarqué. Mais elles le sont suffisamment pour entrer dans un ordinateur portable, où l'autonomie de la batterie est très importante. Dan ce chapitre, nous allons parler exclusivement des processeurs haute performance, les microarchitectures "basse consommation" seront pour le prochain chapitre.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.'
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, le ''scheduler'', la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
===Les différences et points communs entre µarchs d'Intel et d'AMD===
Les microarchitectures Intel et AMD ne sont pas très différentes quand on regarde de loin. Elles ont évoluées dans la même direction, avec une introduction précoce de la superscalarité, puis de l'exécution dans le désordre, du renommage de registre, de la prédiction de branchement, d'un cache de µops, etc. Néanmoins, il y a des différences entre les deux qui relèvent de la tendance générale, avec de nombreuses exceptions :
* Intel préfère utiliser un ''scheduler'' centralisé, alors qu'AMD préfère des stations des ''schedulers'' décentralisés.
* AMD sépare les unités flottantes/SIMD des unités entières, elles ont des ports d'émission bien séparés. Intel, en comparaison, préfère mettre une ALU et une unité FPU/SIMD sur le même port d'émission.
Mais il s'agit là de tendance, avec de nombreuses exceptions. Par exemple, les microarchitectures K6 et Bulldozer d'AMD utilisaient une station de réservation centralisé. De même, les micraorchitectures récentes d'Intel tendent de plus en plus à scinder le ''scheduler''s, de plus en plus avec le temps. Le fait est qu'elle a dû grossir avec le temps, au point de devoir être scindée tellement elle prenait de circuits. La microarchitecture Skylake l'a scindée en une station de réservation pour les µops mémoire et une autre pour le reste. La microarchitecture ''Sunny Cove'' a ensuite scindée la station de réservation mémoire en deux : une pour les lectures et une pour les écritures.
Dans ce chapitre, nous ne parlerons pas des unités de prédiction de branchement, ni de la hiérarchie des caches. Et les raisons sont différentes entre les eux. Les unités de prédiction de branchement ont été discutées dans le chapitre sur la prédiction de branchement. De plus, de nombreux processeurs différents utilisent des unités de prédiction de branchement qui ont des algorithmes similaires. Mais la raison principale est que l'unité de prédiction de branchement est complétement détachée du reste du pipeline, cache d'instruction excepté sur certaines microarchitectures. N'importe quelle unité de branchement peut s'adapter à n'importe quel pipeline/microarchitecture, tant que ses ''itmings'' et délais en cycles d'horloge tiennent la route. Il en est plus ou moins de même pour la hiérarchie de caches. Me système mémoire est assez indépendant du reste de la microarchitecture.
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, des AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Nous verrons pourquoi dans la section suivante.
Pour ce qui est des accès mémoire, les microarchitectures Intel et AMD ont évolué différemment. Intel a suivi cette progression :
* Le Pentium 1 utilisait une unité mémoire intégrée dans le pipeline entier.
* Le Pentium 3 a séparé l'unité mémoire du pipeline entier, mais ne restait capable que d'émettre un accès mémoire par cycle.
* La microarchitectures Core a ajouté la possibilité de faire deux émissions mémoire simultanées : une lecture avec une écriture.
* Par la suite, de nouveaux ports de lectures ont été rajoutés.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en une pile d'opérande. Tout cela a été expliqué dans le chapitre "Un exemple de jeu d'instruction : l'extension x87". Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, le renommage de registres devait une nécessité.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
un autre détail est que les optimisations du renommage de registres sont d'abord arrivées sur le pipeline flottant, avant d'être portées sur le pipeline entier. Un exemple est celui des techniques d'élimination des MOV (''MOV elimination''), qui traitent les instructions MOV entre registres directement dans l'unité de renommage. Ou encore, les techniques avec des idiomes pour mettre à zéro un registre en le Xorant avec lui-même. Elles ont été utilisées d'abord sur les registres flottants/SIMD, avant d'arriver sur les registres généraux. Quelques processeurs les utilsaient pour les registres flottants, mais pas les registres généraux.
Il en est de même avec le renommage dans le ROB versus à banc de registre physique. Quelques CPU renommaient les registres flottants avec un renommage à banc de registre physique, avec que le pipeline entier utilisait encore le renommage dans le ROB. Les processeurs Athlon et Phenom d'AMD étaient dans ce cas, par exemple. Chez Intel, le renommage à banc de registre physique est apparu sur le Pentium 4 et a été appliqué sur tous les registres.
En général, la FPU est précédée par une station de réservation unique (ou par un ''scheduler'' sur certains CPU). Il y a souvent un port d'émission pour l'additionneur flottant et un autre pour le multiplieurs, sauf pour les tout premiers modèles d'Intel et AMD. Il y aussi souvent un port pour les instructions FSTORE, les écritures flottantes. Les ports en question servent à transmettre les données à écrire vers l'unité mémoire, ainsi que l'adresse mémoire associée.
{|class="wikitable"
|+ Station de réservation flottante
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3
|-
| FADD (additionneur flottant) || FMUL (multiplication flottante || FSTORE
|}
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
==Les processeurs x86 d'Intel==
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Nous allons commencer avec les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont superscalaires, mais sans exécution dans le désordre. L'absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre. Les autres processeurs x86 sont tous avec à exécution dans le désordre, à une exception qu'on verra dans le chapitre suivant. AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
Ceci étant dit, commençons par le commencement, avec le Pentium 1.
===Le Pentium 1/MMX : la double émission entière avec deux pipelines===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 (Pentium 3) : l'exécution dans le désordre===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec un ''scheduler'' centralisé, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisé, en sortie de l'unité de renommage. Elles avaient 4 ports d'émission. En réalité, 5 dont deux pour les écritures (un pour l'adresse à transmettre, un pour la donnée). Le tout avait une répartition moitié-moitié pour les calculs et les accès mémoire. Chaque port d'émission pour les calculs était relié à une ALU et une FPU/unité SIMD. Le tableau suivant résume tout.
Vous remarquerez la présence de plusieurs additionneurs flottants. Le premier exécute les opérations x87, l'autre les opérations SIMD. Les opérations SIMD se font sur des registres de 128 bits, mais elles sont en réalité cassées en deux µops, chacune travaillant sur 64 bits.
{|class="wikitable"
|+ Pentium 3
|-
! !! Port d'émission 1 !! Port d'émission 2 !! Port d'émission 3 !! Port d'émission 4 !! Port d'émission 5
|-
! rowspan="3" | µops entières
| ALU entière || ALU entière || LOAD || STORE adresse || STORE donnée
|-
| LEA || ALU SIMD MMX || || ||
|-
| Multiplieur entier || || || ||
|-
! rowspan="3" | µops flottantes
| x87 FADD || FADD 64 bits (SIMD) || ||
|-
| FMUL (x87 et 64 bits) || || ||
|-
| FDIV || || ||
|}
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé un ''scheduler'' centralisé, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core : désambiguïsation mémoire, macro-fusion, ''stack engine'', ''Fetch Input Queue''===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. A part ça, les améliorations sont assez diverses : un cache L2, l'arrivé de la désambiguïsation mémoire, une ''Fetch Input Queue'' et un ''stack engine'' primitif. Le processeur introduit aussi la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération. beaucoup d'optimisations importantes, donc.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 5 ports d'émission, dont un pour les lectures et un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer'', elle-même reliée au cache de données par un port de lecture et un port d'écriture. Les ports de lecture/écriture du cache font 128 bits, pour gérer naturellement les instructions LOAD/STORE dans les registres SIMD. Les trois ports restants sont chacun reliés à des ALU entières et des unités flottantes/SIMD (pour l'extension SSE). Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, une unité SIMD annexe est connectée sur chaque port.
{|class="wikitable"
|+ Microarchitecture Core (Penryn et Merom)
|-
! !! Port d'émission 1 !! Port d'émission 2 !! Port d'émission 3 !! Port d'émission 4 !! Port d'émission 5
|-
! rowspan="3 | µops entières/mémoire
| ALU entière || ALU entière || ALU entière || LOAD || STORE
|-
| ''Barrel shifter'' || Multiplieur entier || ''Barrel shifter'' || ||
|-
|-
| || || ALU branchements || ||
|-
! rowspan="3 | µops flottantes et SIMD
| FMUL et FDIV || FADD || ||
|-
| ALU SIMD entière (128 bits) || ALU SIMD entière (128 bits) || ALU SIMD entière (128 bits) || ||
|-
| || MUL SIMD entière (128 bits) || || ||
|}
Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
===La microarchitecture Nehalem : un vrai ''Loop Stream Detector''===
Les microarchitectures '''Nehalem''' et '''Westmere''' n'introduisent pas d'optimisation majeure. A la rigueur, ils introduisent une prédiction de branchement à deux niveaux, et une macro-fusion plus complète. Les unités de calcul sont assez semblables, de même que leur répartition sur les ports d'émission.
Elle a introduit cependant un vrai ''Loop Stream Detector'' (LSD). Pour rappel, le LSD qui permet d'exécuter les boucles directement depuis la file de µops. Le LSD reconnait les petites boucles, qui tiennent dans la file de µops, et lit les µops adéquates depuis celle-ci. Les instructions de la boucle n'ont donc pas à être chargée et décodées.
La microarchitecture Core avait une sorte de pseudo-''Loop Stream Detector'', mais il était localisé dans la file d'instruction. Il était donc moins performant. Ce pseudo-''Loop Stream Detector'' permettait de ne pas charger les instruction x86, mais elles devaient quand même être décodées et passer par la file de µops. Avec Nehalem, ce n'est plus le cas.
[[File:Intel Nehalem arch.svg|centre|vignette|upright=2.5|Intel Nehalem.]]
===La microarchitecture Sandy Bridge : le cache de µops===
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' ont introduit le renommage à banc de registre physique et l'ajout d'un cache de micro-opérations. L'ajout du cache de micro-opérations est une optimisation particulièrement rentable, particulièrement pour le complexe jeu d’instruction x86. Avec ce cache, les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder des instructions en-dehors de boucles, pas plus. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Sur les processeurs modernes, les décodeurs ne servent que pour 10 à 20% des micro-opérations exécutées, le reste est lu depuis le cache de µops. Cependant, Sandy Bridge a un cache de µops assez limité, qui ne permet pas d’atteindre ces chiffres. De nombreuses instructions ne peuvent pas être mémorisées dans le cache de µops, les limitations quand à son utilisation sont assez importantes. Par exemple, le processeur ne consulte pas le cache de µops à chaque cycle, il ne démarre son utilisation qu'après un branchement (donc une boucle potentielle) et cesse de l'utiliser au premier défaut de cache (dans le cache de µops).
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même.
Pour le reste, ''Sandy Bridge'' n'innove pas trop. Les unités d'exécution sont globalement les mêmes, leur répartition sur les port est peu changée. LA seule différence intéressante est que ''Sandy Bridge'' n'a plus une unité mémoire dédiée aux LOAD et une aux STORE. Elle en a deux qui peuvent faire indifféremment LOAD et STORE. Le processeur peut donc émettre deux LOAD simultanément, ce qui entraine un gain en performance appréciable.
===Les microarchitectures Haswell et Broadwell===
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
{|class="wikitable"
|+ Microarchitecture Haswell et Broadwell
|-
! !! Port d'émission 1 !! Port d'émission 2 !! Port d'émission 3 !! Port d'émission 4 !! Port d'émission 5 !! Port d'émission 6
|-
! rowspan="3" | µops entières/mémoire
| ALU entière || ALU entière || ALU entière || ALU entière || LOAD/STORE || LOAD/STORE
|-
| ''Barrel shifter'' || Manipulation de bits || ''Barrel shifter'' || Manipulation de bits || ||
|-
| || Multiplieur entier || ALU branchements || || ||
|-
! rowspan="4" | µops flottantes et SIMD
| SIMD bit à bit (256 bits) || SIMD bit à bit (256 bits) || SIMD bit à bit (256 bits) || || ||
|-
| || ALU SIMD entière (256 bits) || ALU SIMD entière (256 bits) || || ||
|-
| FMUL + FMA (256 bits) || FMUL + FMA (256 bits) || || || ||
|-
| FDIV || FADD || || || ||
|}
===La microarchitecture Skylake : la fin de la station de réservation centralisé===
L'architecture '''Skylake''' a eu une longévité hors du commun, avec près de 6 ans d’utilisation ininterrompue. Elle a été la concurrentes des microarchitectures Zen 1, Zen 2 et Zen 3 d'AMD. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légèrement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
Un changement extrêmement important concerne la station de réservation centralisé, qui ne l'est plus sur Skylake. Elle est scindée en deux stations de réservations : une pour les µops mémoire, et une autre pour le reste. Mien de rien, c'est à ce moment que la tendance "Intel = station de réservation centralisé " a commencée à s'adoucir. Intel est longtemps resté sur cette organisation avec un '''''Math Scheduler''''' d'un côté, et un '''''MEM scheduler''''' de l'autre.
Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc.
===La microarchitecture ''Sunny Cove'' : la station de réservation mémoire est scindée===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. La microarchitecture''Sunny Cove'' suit la tendance entamée avec Skylake de casser la station de réservation principale en plusieurs stations de réservations plus petites. Cette fois-ci c'est la station de réservation pour les µops mémoire qui est scindée en deux une pour les lectures et une pour les écritures.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
''Sunny Cove'' était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
===La microarchitecture ''Golden Cove'' : un ''Fetch'' doublé===
La microarchitecture '''Golden Cove''' a suivi ''Sunny Cove''. Les microarchitectures suivantes, ''Redwood Cove'' et ''Raptor Cove'', sont des modifications mineures de ''Golden Cove''.
''Golden Cove'' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant. Mais cela change avec ''Golden Cove''.
Golden Cove double la taille du chargement qui passe à 32 octets. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des ''schedulers''s. Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. ''Golden Cove'' conserve les trois ''schedulers'' séparées de ''Sunny Cove'', savoir : pour les instructions entières/flottantes, pour les lectures, pour les écritures.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Niveau unités de calcul, peu de changements avec ''Sunny Cove'', si ce n'est une ALU entière en plus.
{|class="wikitable"
|-
! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5
|-
| ALU entière || ALU entière || ALU entière || ALU entière || ALU entière
|-
| ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' ||
|-
| Branchements || Diviseur || || Branchements ||
|}
===La microarchitecture ''Lion Cove'' : la scission du ''math scheduler''===
La microarchitecture '''''Lion Cove''''' poursuit la tendance à scinder la station de réservation. La station de réservation pour les opérations entières/flottantes est maintenant scindée en deux : une pour les µops entières, une autre pour les µops SIMD/flottantes. Une autre modification, très liée à la précédente, est que le renommage de registre est lui aussi scindé. Les registres SIMD/flottants sont maintenant renommé indépendamment des registres généraux/entiers.
Les raisons sont certainement que cela simplifie grandement la conception du processeur, en séparant les pipelines entiers et SIMD, surtout que les opérations entières et SIMD sont forts différentes : instructions 2-opérandes d'un côté, 2 à 3-opérandes pour le SIMD ; présence de techniques de ''masking'' pour le SIMD, mais pas pour les opérations entières, etc.
Pour le reste, le processeur de 6 à 8 voies d'émission, et l'unité de chargement peut maintenant charger 48 octets, au lieu de 32 auparavant. Des ALU entières et SIMD ont été rajoutés. L'unité d'émission pour les opérations entières dispose de 6 ports d'émission, alors que celle pour les opérations SIMD n'en a que 4.
{|class="wikitable"
|+ Microarchitecture Haswell et Broadwell
|-
! !! Port d'émission 1 !! Port d'émission 2 !! Port d'émission 3 !! Port d'émission 4 !! Port d'émission 5 !! Port d'émission 6
|-
! rowspan="3" | µops entières/mémoire
| ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || ALU entière
|-
| Branchements || ''Barrel shifter'' || Branchements || ''Barrel shifter'' || Branchements || ''Barrel shifter''
|-
| || Multiplieur entier || || Multiplieur entier || || Multiplieur entier
|}
==Les processeurs x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise un ''scheduler'' centralisé. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par un ''scheduler'' centralisé. Les 4 micro-opérations renommées sont écrites dans le ''scheduler'' par groupe de 4, NOP de ''padding'' inclus. le ''scheduler'' centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de le ''scheduler'' est simple. le ''scheduler'' centralisé permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des ''schedulers''s, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une LSQ, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La LSQ est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la LSQ proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a un ''scheduler'' séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. le ''scheduler'' flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux ''schedulers'' : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. le ''scheduler'' entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la LSQ mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la LSQ ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la LSQ. Elles s'exécutent alors l'une après l'autre.
le ''scheduler'' entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans le ''scheduler'' entière. Et cela se marie bien avec un ''scheduler'' unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières au même ''scheduler'' pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la LSQ, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les ''schedulers''s/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===La micro-architecture Bulldozer et ses dérivés===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Un détail très important de Bulldozer est que de nombreux circuits sont partagés entre deux cœurs, à un point qui mérite de nombreuses explications. Les processeurs Bulldozer peuvent exécuter deux programmes "en même temps", sur deux cœurs partiellement fusionnés. Cette technique de partage d'unités de calcul entre coeurs s'appelle le '''cluster multithreading''', ou encore les '''architectures à cœurs conjoints''' (''Conjoined Core Architectures''). Elle a aussi été utilisée sur le processeur Cortex A510, qui partageaient leur FPU entre deux coeurs.
Les deux cœurs sont séparés pour ce qui est du chemin de données, du ''back-end''. Ils disposent de deux bancs de registres généraux et les ALU entières sont dupliquées. Toute la logique d'exécution dans le désordre est aussi dupliquée, avec deux ROBs, des stations de réservations séparées, etc. L'unité mémoire est aussi dupliquée, chaque cœur dispose de ses propres ''load queue'' et ''store queue'', ainsi de que son propre cache L1 de données.
En clair, les chemins de données sont totalement séparés, sauf sur deux points : la FPU et les unités SIMD. La FPU est partagée entre deux coeurs, les unités SIMD sont aussi dans ce cas. Le partage de la FPU permet d'éviter de dupliquer trop de circuits et donc d'économiser des transistors. Le problème est que ce partage est source de dépendances structurelles, ce qui peut entraîner des pertes de performances.
Par contre, le ''front-end'' est presque totalement partagé entre deux cœurs. Il n'y a qu'un seul cache d'instruction pour les deux cœurs, la TLB du cache d'instruction est unique, l'unité de prédiction de branchement est partagée entre deux cœurs, les décodeurs d'instruction aussi sont partagés, même la file de µops ! Les seules unités qui sont dupliquées sont la file d'instruction et l'unité de renommage, présentes en deux exemplaires. Concrètement, les deux programmes ont accès au cache à tour de rôle. Un programme a accès au cache à un instant t, mais le cycle suivant pourra être alloué à l'autre programme. Une unité décide de quel programme peut charger ses données, idem pour le décodage.
[[File:AMD Bulldozer microarchitecture.png|centre|vignette|upright=3|Microarchitecture Bulldozer d'AMD.]]
Au-delà de ce partage entre cœurs, Bulldozer a eu un changement assez important : les stations de réservation sont passées de stations de réservation très décentralisés à une station de réservation unique (pour les µops entières/mémoire), d'environ 40 entrées. L’implémentation de la station de réservation a aussi changée. Car 40 entrées, c'est beaucoup pour l'époque. Elle subit quelques simplifications pour la rendre plus rapide et plus économe en transistors.
Les autres processeurs de l'époque utilisaient une station de réservation qui détectait à chaque instant quelle était l'instruction la plus ancienne, qui soit prête pour l’exécution. Mais Bulldozer n'utilisait pas cette technique. A la place, il mémorisait la place de l'instruction la plus ancienne gra^ce à une ''ancestry table''. Et l'instruction la plus ancienne est privilégiée sur toute les autres. Mais si elle n'est pas prête à s'exécuter, la station de réservation choisit une instruction suivant sa position dans la station de réservation (qui reste une mémoire RAM/associative, ne l'oublions pas).
Bulldozer utilise aussi les optimisations du contournement et du banc de registre, vues dans le chapitre sur les CPU superscalaires. Le banc de registres généraux est dupliqué, afin de garder des bancs de registres avec peu de ports de lecture/écriture. Chaque banc de registre généraux a 4 ports de lecture et deux d'écriture, ce qui permet d'alimenter 2 ALU entières. En tout, cela permet d'alimenter 4 ALU entières, avec 8 ports de lecture et 4 d'écriture. Le tout alimente 2 ALU entières, et 2 AGUs/unités mémoire.
Vu que la FPU est partagée entre deux programmes, sa station de réservation a une taille doublée comparé à la normale. Jugez plutôt : 60 entrées pour la station de réservation, là où les microarchitectures concurrentes d'Intel (''Sandy bridge'') utilisait une station de réservation de 54 entrées pour toutes les instructions entières/flottante/mémoire ! Idem pour le banc de registres flottants, qui n'a pas moins de 160 registres flottants !
Pour ce qui est des instructions SIMD, Bulldozer supporte l'extension AVX, qui fait des calculs du des registres de 256 bits, découpés en entiers/flottants de 64 bits, 32 bits, 16 bits, 8 bits, etc. L'unité SIMD ne gére cependant que des calculs sur 128 bits. Les instructions AVX sont donc découpées en deux µops, chacune traitant la moitié d'un registre SIMD. La performance n'est donc pas améliorée par rapport au SSE. Par contre, les unités SIMD gérent nativement l'opération FMA (une multiplication suivie d'une addition).
{|class="wikitable"
|-
|+ Bulldozer
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4
|-
| FMA - 128 bits || FMA - 128 bits || ALU - 128 bits || ALU - 128 bits
|-
| FMA opérandes entiers - 128 bits || Suffle et permutations || || FSTORE (x87)
|}
La révision Steamroller sépara le ''front-end'' en deux voies distinctes, une par ''thread''. Concrètement, le processeur se contente maintenant de partager la FPU et les unités SIMD entre deux coeurs, pas plus. Pour cela, elle ajouta un second décodeur d'instruction, une seconde file de micro-opération et une seconde unité de renommage de registres, afin d'améliorer les performances.
Niveaux optimisations mineures, les stations de réservation ont été augmentées, elles peuvent mémoriser plus de micro-opérations, idem pour les bancs de registre et les files de lecture/écriture. Un cache de micro-opérations a été ajouté, de même que des optimisations quant au renommage de registre. Des ALU ont aussi été ajoutées, des FPU retirées.
[[File:AMD excavator microarchitecture.png|centre|vignette|upright=3|Microarchitecture Excavator d'AMD.]]
===Les micro-architectures ZEN d'AMD===
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4'''. Elles se ressemblent beaucoup, le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des ''schedulers'', notées ''scheduler'' dans le schéma. Les µops flottantes utilisent une station de réservation unique, alors qu'il y a une station de réservation par ALU entière. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
La micro-architecture Zen 5 se distingue fortement des 4 microarchitectures précédentes. Premièrement, elle passe à une station de réservation centralisé pour les µops entières, une seconde pour els µops mémoire, et une troisième pour les µops SIMD/flottantes. Le retour à une station de réservation aussi peu décentralisé n'avait pas été vu depuis la microarchitecture Bulldozer.
Une autre différence est qu'elle utilise un décodeur ''clustered'', tel que vu dans le chapitre sur les processeurs superscalaires, dans la section "Le décodage superscalaire large".
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s’aperçoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===L'exécution dans le désordre superscalaire du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias. Précisément, il avait deux tables d'alias : une correspondant à l'état spéculatif du CPU, l'autre correspondant à l'état non-spéculatif. La première est utilisée pour renommer les µops, la seconde sert de sauvegarde en cas de mauvaise prédiction de branchement. Quand une cas de mauvaise prédiction de branchement, le pipeline est remis à zéro et l'unité re nommage doit être réinitialisée de manière à reprendre à la bonne instruction. Pour cela, la table d'alias spéculative était réinitiailisée avec le contenu de la seconde.
[[File:MIT 6.823 Computer System Architecture, Fall 2005 - Pentium III and Netburst - 4332527833.jpg|centre|vignette|upright=2.5|Tables d'alias du Pentiçum 4.]]
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de ''schedulers'' ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des ''schedulers'' : centralisés pour Intel, décentralisés pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
[[File:MIT 6.823 Computer System Architecture, Fall 2005 - P-4 Execution Ports - 4332668231.jpg|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:MIT 6.823 Computer System Architecture, Fall 2005 - P-4 Microarchitecture - 4472656667.jpg|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les premiers processeurs Intel
| prevText=Les premiers processeurs Intel
| next=Les microarchitectures pour le x86 : basse consommation
| nextText=Les microarchitectures pour le x86 : basse consommation
}}
</noinclude>
bp7nz3411j5e25s7kg5t1w0gc3vbfbl
Fonctionnement d'un ordinateur/Les microarchitectures pour le x86 : basse consommation
0
84530
775635
774777
2026-11-02T15:06:51Z
Mewtow
31375
/* Les microarchitectures basse consommation */
775635
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les microarchitectures basse consommation==
Qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des cœurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
Économiser de l'énergie correspond généralement à '''économiser des transistors'''. Les transistors ont tendance à consommer de l'énergie, et ce même quand ils ne changent pas d'état, à cause des courants de fuite (consommation statique). Mais il y a des contre-exemples pour lesquels utiliser du transistor d'un côté permet de faire des économies de l'autre. Tout est histoire de vases communicants, au final. La conséquence est que les coeurs E prennent moins de place que les coeurs P, car ils ont moins de transistors. Voici de quoi vous donne rune idée de la différence :
[[File:Intel-ARL-Core-Layout.png|centre|vignette|upright=2|Die des processeurs Intel de microarchitecture ''Raptor lake'' et ''Arrow lake''.]]
Une autre tendance est que les processeurs basse consommation tendent à '''moins spéculer'''. Ils ne font pas de d'exécution spéculative sur les dépendances mémoire, ne font pas de prédiction d'adresse ou de prédiction de valeur, ils n'émettent pas en avance les µops mais attendent sagement que leurs opérandes soient disponibles, etc. Avec cependant une exception de taille : la prédiction de branchement!
Les caches sont aussi fortement modifiés. Un processeur est surtout composé de mémoires caches de nos jours, ce qui fait que la majorité de la consommation d'énergie se fait dedans. Ils sont donc les premiers touchés pour faire des économies. La hiérarchie de caches est modifiée, la taille des caches change et des niveaux de cache peuvent disparaitre. Le préchargement est limité et ne précharge que quand il est certain de ne pas polluer le cache.
===L'exécution dans le désordre basse consommation===
Le ROB et les ''schedulers'' sont des mémoires associatives améliorées, ce qui fait qu'ils consomment beaucoup d'énergie. En conséquence, les toutes premières microarchitectures basse consommation étaient des '''processeurs ''in-order''''', à savoir qu'ils se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée.
Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 250% de performances avec une consommation augmentée de 50%, autant en profiter, quitte à faire des économies ailleurs. Par contre, l'exécution dans le désordre est moins poussée. Le ROB est plus petit, de même que le ou les ''schedulers'', l'unité de renommage est moins performante, la ''store queue'' est plus petite. On met moins de transistors dans ces structures.
Et au passage, revenons sur le fait que les ''schedulers'' sont des mémoires associatives améliorées et consomment donc beaucoup d'énergie. Si le processeur utilise l'émission dans le désordre, il peut passer d'un ''scheduler'' centralisé à un ''scheduler'' décentralisé. Pour un nombre d'entrées identique, le cout en transistors est bien moindre, alors que le cout en performance est acceptable. Il est même possible d'aller encore plus loin et de remplacer les ''schedulers'' par des files de µops, pour passer d'une exécution dans le désordre avec plusieurs files de µops, sans ''schedulers''.
Le renommage de registres est systématiquement utilisé, si l'exécution dans le désordre l'est aussi. Là encore, les gains en performances surcompensent totalement la consommation d'énergie de celle-ci. Par contre, elle est un peu simplifiée. Il y a souvent moins de registres physiques, la ''free list'' est plus petite, les tables de renommage sont plus petites. L'unité de renommage peut donc se retrouver dans des situations où elle manque de registres pour faire le renommage, par exemple. Mais la situation est cependant assez rare.
L'unité de renommage implémente systématiquement les optimisations d'élimination des MOV, les remplacements d'idiomes, les optimisations des calculs triviaux et autres. Leur cout en transistors est très faible, mais le gain en performance est appréciable. Cependant, la vraie raison est que ces optimisations évitent de faire des calculs dans l'ALU ou de bouger des données entre registres. Et ces mouvements/calculs consomment de l'énergie, bien plus que faire le travail dans l'unité de renommage.
===Le décodage est simplifié===
Au niveau du décodage, les processeurs basse consommation utilisent au maximum la '''macro-fusion''' et la '''micro-fusion'''. De telles optimisations permettent de mieux utiliser des petits ROB et des petits ''schedulers''. Il s'agit d'un compromis : on dépense du transistor pour la micro- et macro-fusion, mais on en économise encore plus en réduisant la taille du ROB et des ''schedulers''. Le ROB et les ''schedulers'' sont en effet des sortes de mémoires associatives améliorées et ces dernières consomment beaucoup d'énergie, alors que la micro- et macro-fusion utilisent des circuits combinatoires plus simples.
Les processeurs basse consommation tendent à utiliser le '''prédécodage des instructions''', au lieu d'utiliser un cache de µops. Les processeurs ''in order'' d'ARM utilisent cette technique, de même que les CPU Intel. La raison est qu'un cache de µops demande un budget en transistors assez important. Et qui dit budget en transistors conséquent dit aussi consommation d'énergie en plus. Un cache est en effet une source de consommation énergétique assez importante, et ce même s'il n'est pas utilisé (du fait de courants de fuite). Et les optimisations de consommation permises par le cache de µops ne compensent pas forcément ce cout. Le prédécodage n'a pas autant ce problème, vu que le cache d'instruction est déjà là (même si ajouter les méta-données n'est pas gratuit).
Par contre, si les ''designs'' basse consommation n'ont pas de cache de µops, il n'en est pas de même pour le ''Loop Stream Detector''. Les processeurs basse consommation ont tous une file de µops et intégrer un ''Loop Stream Detector'' n'a pas un cout en transistors conséquent.
===La prédiction de branchements===
Niveau '''''prédiction de branchements''''', ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
Le prédécodage permet aussi de faire des économies au niveau de la prédiction de branchements. Il simplifie les décodeurs, ce qui raccourcit le pipeline du processeur, réduisant ainsi la pénalité de prédiction de branchements. De plus, il est possible de se passer de BTB, en placçant les méta-données de prédiction dans le cache L1 d'instruction. Nous avions vu cette possibilité dans le chapitre sur les optimisations du chargement, aussi je ne vais pas revenir dessus. Mais le fait est que cela permet de faire une double économie : pas de cache de µops, pas de BTB.
===Les optimisations du chemin de données===
Les optimisations des processeurs larges sont aussi utilisées. Par exemple, le réseau de contournement est simplifié avec l'usage d'agrégats, le banc de registre a moins de ports ou est dupliqué, etc.
Un autre détail est que les microarchitecture basse consommation ne mettent pas le paquet sur les instructions SIMD. Elles se contentent d'utiliser des unités de calcul 128 bits. Pour l'AVX, qui utilise des registres de 256 bits, ,voire 512, les calculs sont fait en deux fois. La conséquence est qu'une instruction SIMD de type SSE ou AVX, est décodée en deux micro-opérations 128 bits.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures de Silvermont à Crestmont==
Les microarchitectures qui font suite à l'architecture Bonnell sont, dans l'ordre chronologique : Silvermont, Goldmont, Tremont, Gracemont et Crestmont. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Elles sont aussi superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Les différentes microarchitecture sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Il faut noter que la microarchitecture Tremont pouvait décoder six instructions par cycles, mais émettre seulement 4 µops par cycle. Idem avec Gracemont , qui était limitée à 5 µops par cyle, là encore à cause de l’unité de renommage.
{|class="wikitable"
|-
! Processeur !! Chargement !! Décodage !! Renommage
|-
! Silvermont
| 16 octets par cycle || 2 instructions par cycle || 2 µops par cycle
|-
! Goldmont
| 16 octets par cycle || 3 instructions/cycle || 2 µops par cycle
|-
! Tremont
| 32 octets par cycle || 6 instructions/cycle || 4 µops par cycle
|-
! Gracemont
| 32 octets par cycle || 6 instructions/cycle || 5 µops par cycle
|-
! Crestmont
| 6 octets par cycle || 6 instructions par cycle || 6 µops par cycle
|}
Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, avec exécution dans le désordre, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cela vient avec des optimisations pour réduire le cout en circuit/énergie. Et ces optimisations sont intéressantes à étudier. Par contre, certaines implémentations de l’exécution dans le désordre sont plus consommatrices que d'autres. Et cela permet de faire le lien avec le chapitre précédent.
Dans le chapitre précédent, j'ai dit que les processeurs Intel utilisaient une station de réservation centralisée, alors qu'AMD préférait la décentraliser à l'excès. AMD pousse le bouchon jusqu'à avoir une station de réservation par unité, que ce soit les ALU, les FPU, les unités mémoire, et même les unités de branchement. Il se trouve que la solution d'AMD est nettement meilleure.
L'utilisation de '''stations de réservations décentralisées''' est une première optimisation. Et quand je dis décentralisé, c'est au point où chaque ALU entière a sa propre station de réservation. Cela réduit les performances par rapport à une station de réservation centralisée, ou un moins un système moins décentralisé, mais entraine un gain en consommation d'électricité non négligeable.
Une seconde optimisation est que les stations de réservation pour les opérations flottantes sont séparées des autres. Il n'y a pas, comme sur les microarchitectures Intel, de ports d'émission qui partagent ue ALU et une FPU, voire une ALU et une unité SIMD. La raison est que si le programme exécuté n'utilise pas d'instructions SIMD, les circuits associés peuvent être ''clock gatés'', voire éteints. Les stations de réservation SIMD ou Flottante, les registres SIMD/flottants, les FPU, les unités SIMD : tout cela est simplement mis en veille. Le gain en termes de consommation est alors assez important, surtout avec les instructions SIMD.
Une troisième optimisation est liée aux FPU, et précisément aux stations de réservation associées. Ou plutôt devrais-je dire : la station de réservation flottante. Car si les ALU entières ont chacune leur propre station de réservation, les FPU et les unités SIMD tendent à avoir une seule station de réservation, partagée entre toutes les FPUs. La raison à cela est que cela convient mieux aux opérations flottantes. Elles ont une durée plus longue, attendent plus longtemps dans les stations de réservation, etc. bref, des détails techniques qui font que c'est mieux ainsi.
Une dernière optimisation, liée à la précédente, est l'usage de '''files d'instructions pour les opérations mémoire'''. Si ces processeurs utilisent des stations de réservation pour les opérations entières, ce n'est pas le cas pour les opérations mémoire. Les accès mémoire sont donc exécutés dans l'ordre, contrairement aux autres opérations. Il arrive aussi que ce soit la même chose, mais pour les opérations flottantes, encore que ce soit plus rare. . Les performances mémoire sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine.
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
===Les unités fonctionnelles et l'exécution dans le désordre===
Pour ce qui est de l'exécution dans le désordre, il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de ports du banc de registre, en répartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont fait les modifications suivantes : il rajoute une ALU entière, les stations de réservations augmentent en taille, il fusionne les stations de réservation pour les unités flottantes.
De plus, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Les microarchitectures suivantes ont plus d'unités de calcul.
{|class="wikitable"
|-
|+ Tremont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8 !! Station de réservation 9 !! Station de réservation 10
|-
| 12 entrées || 12 entrées || 7 entrées || 17 entrées || colspan="2" | 13 entrées || 14 entrées || 13 entrées || colspan="2" | 24 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || LOAD || STORE || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || SIMD FMUL (128bits) || SIMD FADD (128bits)
|-
| || || || || || || FDIV ||
|}
{|class="wikitable"
|-
|+ Gracemont
|-
! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS
|-
| 15 entrées || 16 entrées || 16 entrées || 15 entrées || colspan="2" | 42 entrées
| colspan="4" | 22 entrées
| 13 entrées || colspan="2" | 24 entrées
|-
| ALU || ALU || ALU || ALU || Branch || Branch
| LOAD || LOAD || STORE || STORE
| FSTORE (écriture) || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| || MUL entier || MUL entier || || ||
| || || ||
| SIMD FMA (128bits) || SIMD FMA (128bits)
|-
| || DIV entier || DIV entier || || ||
| || || ||
| SIMD FADD (128bits) || SIMD FADD (128bits)
|-
| || LEA || Manipulation de bits || || ||
| || || ||
| SIMD FMUL (128bits) || SIMD FMUL (128bits)
|}
===L'unité mémoire et la FPU des CPU Intel basse consommation===
Silvermont et Goldmont se limitent à une simple unité mémoire, qui a cependant deux ports séparés pour Goldmont. L'économie en circuit est conséquente, car ils se débrouillent avec une ''load queue'' et une ''Store queue'', toutes deux simple port (si on omet le port pour le ''store to load forwarding''). De même, le cache L1 peut se débrouiller avec seulement une implémentation naturelle, à savoir un port de lecture et un d'écriture (le cache est naturellement double port, vu que les entrées de lecture et d'écriture sont séparées sur les cellules de SRAM). Pas besoin d'utiliser de structures matérielle avec un port de plus que de nature, ce qui est un énorme avantage en termes d'économie de circuits et de consommation énergétique.
C'est sur les microarchitecture suivantes que démarrent les hostilités. Tremont ajoute un second port pour émettre une lecture, Gracemont ajoute un second port pour les écritures en plus (4 ports au total). Crestmont uant à elle, dispose de trois ports d'émission pour les lectures et de deux ports d'émission pour les écritures.
De plus, la file de µops mémoire est scindée en deux : une file de µops mémoire, suivie par une station de réservation de petite taille. Les µops mémoire sont accumulées dans la file de µop quand les stations de réservation sont pleines, histoire de ne pas bloquer l'émission en cas de défaut de cache un peu trop prolongé. Prenons une µop mémoire qui est tout juste ''dispatch'' : l'unité de ''dispatch'' regarde si il y a de la place dans la station de réservation. Si c'est le cas, elle envoie la µop mémoire à la station de réservation. Sinon, elle l'envoie dans la file de µops mémoire.
Tremont utilise une file de µops de 21 entrées, alors que la station de réservation en fait 13, ce qui fait un total de 34 entrées. Les performances sont moindres comparé à une station de réservation de 34 entrées, car les µops dans la file de µops mémoire ne peuvent pas être émises tant qu'elles n'ont pas atteint les stations de réservations, même si les conditions sont remplies en matière d'opérandes pour qu'elles puissent passer devant. Mais le gain en circuits est conséquent, de même que l'économie d'énergie, pour une perte de performance plus que tolérable.
Les opérations flottantes étant assez longues, Gracemont utilise le même système pour les opérations flottantes. Une file de µops précède deux stations de réservation. La première accumule des opérations flottantes, destinées à être exécutées dans la FPU (de type SIMD). La file de µops fait 56 entrées, alors que la station de réservation en a 35. Une seconde station de réservation de 18 entrées met en attente les écritures flottantes, à savoir les écritures qui écrivent un flottant simple ou double précision en mémoire RAM.
Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
===Le ''front-end'' : un changement après Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|}
En remplacement du cache de µops, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les architectures parallèles
| nextText=Les architectures parallèles
}}
</noinclude>
bf7a1mo09u33pqju6b67tdzcxzn3n82
775636
775635
2026-11-02T15:09:23Z
Mewtow
31375
/* Les microarchitectures basse consommation */
775636
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les microarchitectures basse consommation==
Qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des cœurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
Économiser de l'énergie correspond généralement à '''économiser des transistors'''. Les transistors ont tendance à consommer de l'énergie, et ce même quand ils ne changent pas d'état, à cause des courants de fuite (consommation statique). Mais il y a des contre-exemples pour lesquels utiliser du transistor d'un côté permet de faire des économies de l'autre. Tout est histoire de vases communicants, au final. La conséquence est que les coeurs E prennent moins de place que les coeurs P, car ils ont moins de transistors. Voici de quoi vous donne rune idée de la différence :
[[File:Intel-ARL-Core-Layout.png|centre|vignette|upright=2|Die des processeurs Intel de microarchitecture ''Raptor lake'' et ''Arrow lake''.]]
Une autre tendance est que les processeurs basse consommation tendent à '''moins spéculer'''. Dans le pire des cas, ils ne font pas de d'exécution spéculative sur les dépendances mémoire, ne font pas de prédiction d'adresse ou de prédiction de valeur, ils n'émettent pas en avance les µops mais attendent sagement que leurs opérandes soient disponibles, etc. Et en-dehors de ca cas extrême, s'ils implémentent de la spéculation, elle est relativement conservatrice. Avec cependant une exception de taille : la prédiction de branchement ! Les processeurs basse consommation mettent le paquet en termes de prédiction de branchements, comme on le verra dans la suite du chapitre.
Les caches sont aussi fortement modifiés. Un processeur est surtout composé de mémoires caches de nos jours, ce qui fait que la majorité de la consommation d'énergie se fait dedans. Ils sont donc les premiers touchés pour faire des économies. La hiérarchie de caches est modifiée, la taille des caches change et des niveaux de cache peuvent disparaitre. Le préchargement est limité et ne précharge que quand il est certain de ne pas polluer le cache.
===L'exécution dans le désordre basse consommation===
Le ROB et les ''schedulers'' sont des mémoires associatives améliorées, ce qui fait qu'ils consomment beaucoup d'énergie. En conséquence, les toutes premières microarchitectures basse consommation étaient des '''processeurs ''in-order''''', à savoir qu'ils se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée.
Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 250% de performances avec une consommation augmentée de 50%, autant en profiter, quitte à faire des économies ailleurs. Par contre, l'exécution dans le désordre est moins poussée. Le ROB est plus petit, de même que le ou les ''schedulers'', l'unité de renommage est moins performante, la ''store queue'' est plus petite. On met moins de transistors dans ces structures.
Et au passage, revenons sur le fait que les ''schedulers'' sont des mémoires associatives améliorées et consomment donc beaucoup d'énergie. Si le processeur utilise l'émission dans le désordre, il peut passer d'un ''scheduler'' centralisé à un ''scheduler'' décentralisé. Pour un nombre d'entrées identique, le cout en transistors est bien moindre, alors que le cout en performance est acceptable. Il est même possible d'aller encore plus loin et de remplacer les ''schedulers'' par des files de µops, pour passer d'une exécution dans le désordre avec plusieurs files de µops, sans ''schedulers''.
Le renommage de registres est systématiquement utilisé, si l'exécution dans le désordre l'est aussi. Là encore, les gains en performances surcompensent totalement la consommation d'énergie de celle-ci. Par contre, elle est un peu simplifiée. Il y a souvent moins de registres physiques, la ''free list'' est plus petite, les tables de renommage sont plus petites. L'unité de renommage peut donc se retrouver dans des situations où elle manque de registres pour faire le renommage, par exemple. Mais la situation est cependant assez rare.
L'unité de renommage implémente systématiquement les optimisations d'élimination des MOV, les remplacements d'idiomes, les optimisations des calculs triviaux et autres. Leur cout en transistors est très faible, mais le gain en performance est appréciable. Cependant, la vraie raison est que ces optimisations évitent de faire des calculs dans l'ALU ou de bouger des données entre registres. Et ces mouvements/calculs consomment de l'énergie, bien plus que faire le travail dans l'unité de renommage.
===Le décodage est simplifié===
Au niveau du décodage, les processeurs basse consommation utilisent au maximum la '''macro-fusion''' et la '''micro-fusion'''. De telles optimisations permettent de mieux utiliser des petits ROB et des petits ''schedulers''. Il s'agit d'un compromis : on dépense du transistor pour la micro- et macro-fusion, mais on en économise encore plus en réduisant la taille du ROB et des ''schedulers''. Le ROB et les ''schedulers'' sont en effet des sortes de mémoires associatives améliorées et ces dernières consomment beaucoup d'énergie, alors que la micro- et macro-fusion utilisent des circuits combinatoires plus simples.
Les processeurs basse consommation tendent à utiliser le '''prédécodage des instructions''', au lieu d'utiliser un cache de µops. Les processeurs ''in order'' d'ARM utilisent cette technique, de même que les CPU Intel. La raison est qu'un cache de µops demande un budget en transistors assez important. Et qui dit budget en transistors conséquent dit aussi consommation d'énergie en plus. Un cache est en effet une source de consommation énergétique assez importante, et ce même s'il n'est pas utilisé (du fait de courants de fuite). Et les optimisations de consommation permises par le cache de µops ne compensent pas forcément ce cout. Le prédécodage n'a pas autant ce problème, vu que le cache d'instruction est déjà là (même si ajouter les méta-données n'est pas gratuit).
Par contre, si les ''designs'' basse consommation n'ont pas de cache de µops, il n'en est pas de même pour le ''Loop Stream Detector''. Les processeurs basse consommation ont tous une file de µops et intégrer un ''Loop Stream Detector'' n'a pas un cout en transistors conséquent.
===La prédiction de branchements===
Niveau '''''prédiction de branchements''''', ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
Le prédécodage permet aussi de faire des économies au niveau de la prédiction de branchements. Il simplifie les décodeurs, ce qui raccourcit le pipeline du processeur, réduisant ainsi la pénalité de prédiction de branchements. De plus, il est possible de se passer de BTB, en placçant les méta-données de prédiction dans le cache L1 d'instruction. Nous avions vu cette possibilité dans le chapitre sur les optimisations du chargement, aussi je ne vais pas revenir dessus. Mais le fait est que cela permet de faire une double économie : pas de cache de µops, pas de BTB.
===Les optimisations du chemin de données===
Les optimisations des processeurs larges sont aussi utilisées. Par exemple, le réseau de contournement est simplifié avec l'usage d'agrégats, le banc de registre a moins de ports ou est dupliqué, etc.
Un autre détail est que les microarchitecture basse consommation ne mettent pas le paquet sur les instructions SIMD. Elles se contentent d'utiliser des unités de calcul 128 bits. Pour l'AVX, qui utilise des registres de 256 bits, ,voire 512, les calculs sont fait en deux fois. La conséquence est qu'une instruction SIMD de type SSE ou AVX, est décodée en deux micro-opérations 128 bits.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures de Silvermont à Crestmont==
Les microarchitectures qui font suite à l'architecture Bonnell sont, dans l'ordre chronologique : Silvermont, Goldmont, Tremont, Gracemont et Crestmont. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Elles sont aussi superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Les différentes microarchitecture sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Il faut noter que la microarchitecture Tremont pouvait décoder six instructions par cycles, mais émettre seulement 4 µops par cycle. Idem avec Gracemont , qui était limitée à 5 µops par cyle, là encore à cause de l’unité de renommage.
{|class="wikitable"
|-
! Processeur !! Chargement !! Décodage !! Renommage
|-
! Silvermont
| 16 octets par cycle || 2 instructions par cycle || 2 µops par cycle
|-
! Goldmont
| 16 octets par cycle || 3 instructions/cycle || 2 µops par cycle
|-
! Tremont
| 32 octets par cycle || 6 instructions/cycle || 4 µops par cycle
|-
! Gracemont
| 32 octets par cycle || 6 instructions/cycle || 5 µops par cycle
|-
! Crestmont
| 6 octets par cycle || 6 instructions par cycle || 6 µops par cycle
|}
Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, avec exécution dans le désordre, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cela vient avec des optimisations pour réduire le cout en circuit/énergie. Et ces optimisations sont intéressantes à étudier. Par contre, certaines implémentations de l’exécution dans le désordre sont plus consommatrices que d'autres. Et cela permet de faire le lien avec le chapitre précédent.
Dans le chapitre précédent, j'ai dit que les processeurs Intel utilisaient une station de réservation centralisée, alors qu'AMD préférait la décentraliser à l'excès. AMD pousse le bouchon jusqu'à avoir une station de réservation par unité, que ce soit les ALU, les FPU, les unités mémoire, et même les unités de branchement. Il se trouve que la solution d'AMD est nettement meilleure.
L'utilisation de '''stations de réservations décentralisées''' est une première optimisation. Et quand je dis décentralisé, c'est au point où chaque ALU entière a sa propre station de réservation. Cela réduit les performances par rapport à une station de réservation centralisée, ou un moins un système moins décentralisé, mais entraine un gain en consommation d'électricité non négligeable.
Une seconde optimisation est que les stations de réservation pour les opérations flottantes sont séparées des autres. Il n'y a pas, comme sur les microarchitectures Intel, de ports d'émission qui partagent ue ALU et une FPU, voire une ALU et une unité SIMD. La raison est que si le programme exécuté n'utilise pas d'instructions SIMD, les circuits associés peuvent être ''clock gatés'', voire éteints. Les stations de réservation SIMD ou Flottante, les registres SIMD/flottants, les FPU, les unités SIMD : tout cela est simplement mis en veille. Le gain en termes de consommation est alors assez important, surtout avec les instructions SIMD.
Une troisième optimisation est liée aux FPU, et précisément aux stations de réservation associées. Ou plutôt devrais-je dire : la station de réservation flottante. Car si les ALU entières ont chacune leur propre station de réservation, les FPU et les unités SIMD tendent à avoir une seule station de réservation, partagée entre toutes les FPUs. La raison à cela est que cela convient mieux aux opérations flottantes. Elles ont une durée plus longue, attendent plus longtemps dans les stations de réservation, etc. bref, des détails techniques qui font que c'est mieux ainsi.
Une dernière optimisation, liée à la précédente, est l'usage de '''files d'instructions pour les opérations mémoire'''. Si ces processeurs utilisent des stations de réservation pour les opérations entières, ce n'est pas le cas pour les opérations mémoire. Les accès mémoire sont donc exécutés dans l'ordre, contrairement aux autres opérations. Il arrive aussi que ce soit la même chose, mais pour les opérations flottantes, encore que ce soit plus rare. . Les performances mémoire sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine.
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
===Les unités fonctionnelles et l'exécution dans le désordre===
Pour ce qui est de l'exécution dans le désordre, il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de ports du banc de registre, en répartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont fait les modifications suivantes : il rajoute une ALU entière, les stations de réservations augmentent en taille, il fusionne les stations de réservation pour les unités flottantes.
De plus, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Les microarchitectures suivantes ont plus d'unités de calcul.
{|class="wikitable"
|-
|+ Tremont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8 !! Station de réservation 9 !! Station de réservation 10
|-
| 12 entrées || 12 entrées || 7 entrées || 17 entrées || colspan="2" | 13 entrées || 14 entrées || 13 entrées || colspan="2" | 24 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || LOAD || STORE || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || SIMD FMUL (128bits) || SIMD FADD (128bits)
|-
| || || || || || || FDIV ||
|}
{|class="wikitable"
|-
|+ Gracemont
|-
! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS
|-
| 15 entrées || 16 entrées || 16 entrées || 15 entrées || colspan="2" | 42 entrées
| colspan="4" | 22 entrées
| 13 entrées || colspan="2" | 24 entrées
|-
| ALU || ALU || ALU || ALU || Branch || Branch
| LOAD || LOAD || STORE || STORE
| FSTORE (écriture) || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| || MUL entier || MUL entier || || ||
| || || ||
| SIMD FMA (128bits) || SIMD FMA (128bits)
|-
| || DIV entier || DIV entier || || ||
| || || ||
| SIMD FADD (128bits) || SIMD FADD (128bits)
|-
| || LEA || Manipulation de bits || || ||
| || || ||
| SIMD FMUL (128bits) || SIMD FMUL (128bits)
|}
===L'unité mémoire et la FPU des CPU Intel basse consommation===
Silvermont et Goldmont se limitent à une simple unité mémoire, qui a cependant deux ports séparés pour Goldmont. L'économie en circuit est conséquente, car ils se débrouillent avec une ''load queue'' et une ''Store queue'', toutes deux simple port (si on omet le port pour le ''store to load forwarding''). De même, le cache L1 peut se débrouiller avec seulement une implémentation naturelle, à savoir un port de lecture et un d'écriture (le cache est naturellement double port, vu que les entrées de lecture et d'écriture sont séparées sur les cellules de SRAM). Pas besoin d'utiliser de structures matérielle avec un port de plus que de nature, ce qui est un énorme avantage en termes d'économie de circuits et de consommation énergétique.
C'est sur les microarchitecture suivantes que démarrent les hostilités. Tremont ajoute un second port pour émettre une lecture, Gracemont ajoute un second port pour les écritures en plus (4 ports au total). Crestmont uant à elle, dispose de trois ports d'émission pour les lectures et de deux ports d'émission pour les écritures.
De plus, la file de µops mémoire est scindée en deux : une file de µops mémoire, suivie par une station de réservation de petite taille. Les µops mémoire sont accumulées dans la file de µop quand les stations de réservation sont pleines, histoire de ne pas bloquer l'émission en cas de défaut de cache un peu trop prolongé. Prenons une µop mémoire qui est tout juste ''dispatch'' : l'unité de ''dispatch'' regarde si il y a de la place dans la station de réservation. Si c'est le cas, elle envoie la µop mémoire à la station de réservation. Sinon, elle l'envoie dans la file de µops mémoire.
Tremont utilise une file de µops de 21 entrées, alors que la station de réservation en fait 13, ce qui fait un total de 34 entrées. Les performances sont moindres comparé à une station de réservation de 34 entrées, car les µops dans la file de µops mémoire ne peuvent pas être émises tant qu'elles n'ont pas atteint les stations de réservations, même si les conditions sont remplies en matière d'opérandes pour qu'elles puissent passer devant. Mais le gain en circuits est conséquent, de même que l'économie d'énergie, pour une perte de performance plus que tolérable.
Les opérations flottantes étant assez longues, Gracemont utilise le même système pour les opérations flottantes. Une file de µops précède deux stations de réservation. La première accumule des opérations flottantes, destinées à être exécutées dans la FPU (de type SIMD). La file de µops fait 56 entrées, alors que la station de réservation en a 35. Une seconde station de réservation de 18 entrées met en attente les écritures flottantes, à savoir les écritures qui écrivent un flottant simple ou double précision en mémoire RAM.
Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
===Le ''front-end'' : un changement après Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|}
En remplacement du cache de µops, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les architectures parallèles
| nextText=Les architectures parallèles
}}
</noinclude>
spop6n7b8auf3nu6hl7e3vujpugt5fk