CHAPITRE 4 : LE COMPLÉMENT À 2

Nous arrivons à la convention qui a gagné. Tous les processeurs que vous utiliserez, du microcontrôleur à huit bits au serveur à soixante-quatre, codent leurs entiers relatifs en complément à 2. Les types int, long et short de tous les langages courants reposent dessus. Ce chapitre explique la règle, puis montre pourquoi elle est la bonne.

La règle

On l'appelle aussi complément vrai (CV). Elle se déduit de la précédente en ajoutant un geste.

  • Les nombres positifs sont codés en binaire pur, comme toujours.
  • Un nombre négatif est codé en prenant son complément à 1, puis en lui ajoutant 1.

On note cette convention (cà2).

Un exemple complet : −24 sur 8 bits

1. valeur absolue           |-24| = 24
2. en binaire pur               24 = 00011000
3. complement a 1                    11100111
4. on ajoute 1                     + 00000001
                                   ----------
resultat : 11101000  (ca2)

Pour relire un motif, deux méthodes au choix.

Méthode 1, la symétrique. Si le bit de poids fort vaut 1, on applique exactement le même traitement : on inverse tous les bits et on ajoute 1, et on met un signe moins devant le résultat. Sur 11101000 : inversion donne 00010111, plus 1 donne 00011000 = 24. Le motif vaut −24.

Méthode 2, la directe. On lit le motif en binaire pur, mais en donnant au bit de poids fort un poids négatif. Sur 8 bits, ce poids est −128.

11101000 = −128 + 64 + 32 + 8 = −24

Cette seconde lecture est celle du circuit. Elle explique tout le reste du chapitre : en complément à 2, le bit de poids fort n'est pas un drapeau ajouté à côté du nombre, c'est un chiffre ordinaire qui porte simplement un poids négatif. Voilà pourquoi l'arithmétique fonctionne sans aménagement.

Une astuce de calcul mental. Pour coder un négatif à la main, inutile de passer par l'inversion puis l'addition. On parcourt la représentation de la valeur absolue de droite à gauche : on recopie tous les bits jusqu'au premier 1 inclus, puis on inverse tous les suivants.

Sur 24 = 00011000 : on recopie 000 (les trois zéros de droite) et le 1 qui suit, puis on inverse 0001 en 1110. Résultat : 11101000. C'est bien le motif trouvé plus haut, en un seul passage.

Un seul zéro, enfin

Appliquons la règle à zéro, sur 8 bits :

0 en binaire pur        00000000
complement a 1          11111111
+ 1                   + 00000001
                      -----------
                     1 00000000
                     ^
                     |__ cette retenue sort du mot et se perd

On retombe sur 00000000. Le zéro négatif a disparu : il n'existe qu'un seul motif nul. Tester si un registre vaut zéro redevient une comparaison unique, et l'égalité entre deux nombres redevient une comparaison de motifs.

Une étendue asymétrique

Le motif libéré par la disparition du −0 n'est pas gaspillé : il sert à représenter une valeur négative de plus.

de −2n−1 à +(2n−1 − 1)

TailleÉtendue en complément à 2 Type C correspondant
4 bits−8 à +7-
8 bits−128 à +127signed char
16 bits−32 768 à +32 767short
32 bits−2 147 483 648 à +2 147 483 647 int
64 bits−9 223 372 036 854 775 808 à +9 223 372 036 854 775 807long long

Les 256 motifs d'un octet représentent 256 valeurs distinctes : aucun n'est perdu. En revanche l'étendue n'est pas symétrique : il y a un négatif de plus que de positifs.

Conséquence amusante et réelle : sur 8 bits, l'opposé de −128 n'est pas représentable. Si l'on applique la règle à 10000000, on inverse en 01111111, on ajoute 1 et on retombe sur 10000000. En C, -x appliqué au plus petit int ne change rien, c'est un piège classique, et un cas que les analyseurs de code signalent.

Tableau des seize motifs de quatre bits avec leur valeur en signe et valeur absolue, en complement a un et en complement a deux. Les deux zeros des premieres conventions sont signales en rouge.

L'addition : plus rien à faire

C'est l'argument décisif. En complément à 2, on additionne les motifs bit à bit, on jette la retenue qui sort du mot, et le résultat est juste. Pas de report circulaire, pas de comparateur, pas de cas particulier.

  00011000   (+24)
+ 11101000   (-24)
----------
 100000000
 ^ on jette ce bit
   il reste 00000000 = 0   correct

  00101101   (+45)
+ 10110111   (-73)
----------
  11100100   = -128+64+32+4 = -28   correct

  11010011   (-45)
+ 10110111   (-73)
----------
 110001010
 ^ on jette
   il reste 10001010 = -128+8+2 = -118   correct

Et la soustraction ? Elle devient gratuite : soustraire B, c'est additionner l'opposé de B. Le processeur n'a pas besoin d'un circuit soustracteur ; il lui suffit d'un inverseur et d'une retenue initiale à 1, ce qui ne coûte presque rien.

Un seul additionneur fait l'addition et la soustraction, sur les nombres positifs comme négatifs, sans jamais regarder les signes. C'est la raison d'être du complément à 2.

Le débordement : comment le détecter

Un résultat juste suppose qu'il tienne dans l'étendue. Sinon, le calcul est faux, et silencieusement faux.

  01100100   (+100)
+ 00101101   (+45)
----------
  10010001   = -111

attendu : +145, qui depasse +127

Deux nombres positifs ont donné un résultat négatif. Le débordement se repère par cette règle simple :

Règle de détection. Il y a débordement si et seulement si les deux opérandes ont le même signe et que le résultat porte le signe opposé.

Corollaire utile : en additionnant deux nombres de signes contraires, le débordement est impossible. Le résultat est forcément compris entre les deux opérandes.

Le processeur applique cette règle en matériel et range la réponse dans un indicateur appelé flag de débordement (overflow, noté OF ou V). Un autre indicateur, la retenue (carry, C), signale le débordement pour les nombres non signés. Ce sont deux indicateurs distincts, et confondre les deux est une erreur fréquente : dans l'exemple ci-dessus, aucune retenue ne sort, pourtant il y a bien débordement signé.

L'extension de signe

Dernier point pratique. Que se passe-t-il quand on range un entier 8 bits dans une variable 16 bits ? Si l'on se contente de compléter par des zéros à gauche, −45 = 11010011 deviendrait 0000000011010011, soit 211. Le nombre a changé de signe et de valeur.

La règle correcte est de recopier le bit de signe autant de fois que nécessaire.

  +45 sur  8 bits : 00101101
  +45 sur 16 bits : 0000000000101101   (on complete par des 0)

  -45 sur  8 bits : 11010011
  -45 sur 16 bits : 1111111111010011   (on complete par des 1)

Vérification sur 16 bits avec la lecture directe : le poids du bit de tête est −32 768, et la somme des bits à 1 donne bien −45. Les processeurs disposent d'instructions dédiées pour cela, MOVSX sur x86, par exemple, distinctes de celles qui complètent par des zéros pour les nombres non signés.

Ce qu'il faut retenir

  • Négatif = complément à 1 de la valeur absolue, plus 1. Astuce : recopier depuis la droite jusqu'au premier 1 inclus, puis inverser le reste.
  • Lecture directe : le bit de poids fort porte le poids −2n−1, les autres leur poids habituel.
  • Un seul zéro. Étendue asymétrique : −128 à +127 sur un octet.
  • Addition bit à bit, retenue sortante jetée : le résultat est juste. La soustraction se ramène à une addition.
  • Débordement signé : mêmes signes en entrée, signe contraire en sortie. À ne pas confondre avec la retenue.
  • Élargir un entier signé : on recopie le bit de signe, pas des zéros.

Exercices d'application

Exercice 1

Codez −73 en complément à 2 sur 8 bits par les deux méthodes (inversion puis +1, et l'astuce de droite à gauche), puis vérifiez par la lecture directe.

Voir le corrigéMasquer le corrigé

73 = 64 + 8 + 1 = 01001001.

Méthode classique : complément à 1 = 10110110, plus 1 = 10110111.

Astuce : en partant de la droite de 01001001, on recopie le premier 1, puis on inverse les sept bits restants 0100100 en 1011011. Résultat : 10110111. Identique.

Lecture directe : −128 + 32 + 16 + 4 + 2 + 1 = −128 + 55 = −73. C'est juste.

Exercice 2

Le motif 10000000 est rangé dans un octet. Donnez sa valeur dans les quatre conventions vues jusqu'ici.

Voir le corrigéMasquer le corrigé
  • Binaire pur : 128.
  • Binaire signé : signe négatif, grandeur 0000000 = 0, donc −0.
  • Complément à 1 : on inverse pour obtenir 01111111 = 127, donc −127.
  • Complément à 2 : lecture directe, −128 + 0 = −128.

C'est le motif le plus instructif du jeu : il donne quatre valeurs différentes, et c'est aussi celui qui porte la seule valeur que le complément à 2 gagne sur les deux autres conventions.

Exercice 3

Calculez −100 + (−45) en complément à 2 sur 8 bits. Appliquez la règle de détection du débordement et concluez.

Voir le corrigéMasquer le corrigé

100 = 01100100, donc −100 = 10011100. 45 = 00101101, donc −45 = 11010011.

  10011100   (-100)
+ 11010011   ( -45)
----------
 101101111
 ^ on jette la retenue
   il reste 01101111 = +111

Les deux opérandes sont négatives, le résultat est positif : il y a débordement. Le résultat affiché, +111, est faux.

Et c'est normal : la réponse mathématique est −145, qui sort de l'étendue −128 à +127. On notera que 111 = −145 + 256 : le calcul est juste modulo 256, comme toujours.

Exercice 4

Une variable signed char contient −1. On la copie dans une variable 32 bits. Quel motif obtient-on ? Et si la variable d'origine avait été déclarée unsigned char ?

Voir le corrigéMasquer le corrigé

−1 en complément à 2 sur 8 bits s'écrit 11111111.

Source signée : le compilateur étend le bit de signe, qui vaut 1. On obtient 32 bits à 1, soit FFFFFFFF en hexadécimal : qui se lit bien −1 sur 32 bits.

Source non signée : le motif 11111111 vaut 255, et l'élargissement complète par des zéros. On obtient 000000FF, soit 255.

Même motif de départ, deux résultats sans rapport. C'est l'une des sources d'erreur les plus tenaces en C, et la raison pour laquelle on évite de mélanger types signés et non signés dans une même expression.

Posez le calcul sur une feuille avant d'ouvrir le corrigé : le codage s'apprend au crayon, pas en lecture.

Questions / Réponses

Aucune question. Soyez le premier à poser une question.
★ ★ ★ ★ ★
2 votes. Moyenne 5 sur 5.

Ajouter un commentaire

Anti-spam