Nombres binaires signés

Les nombres binaires ne sont représentés que par des \(0\) et des \(1\) et nous n’avons pas la possibilité de représenter un signe \(+\) ou \(-\) comme nous le faisons en mathématiques. Une des premières idées qui nous vient pour représenter ce signe serait de détourner le bit de poids fort pour qu’il représente le signe. Par convention, on pourrait dire que si le bit de poids fort est à \(0\), le nombre est positif et que s’il est à \(1\), le nombre est négatif.

Bit de signe

Nous verrons plus tard, dans un contexte bien précis, que la représentation du signe par un bit est en effet utilisée, mais dans le contexte des nombres entiers qui nous concerne actuellement, cette solution est plutôt une mauvaise idée :

  • La première faiblesse de ce système c’est que nous avons deux représentations pour le nombre \(0\), une fois avec le bit de poids fort à zéro (\(+0\)) et une fois avec le bit de poids fort à un (\(-0\)).
  • La deuxième faiblesse c’est que nous ne pouvons pas réutiliser notre système de chaîne de full adders pour additionner deux nombres signés.

Il nous faut donc trouver une autre solution pour représenter les nombres binaires signés. Si on reprend le cercle des nombres et que nous remplissons avec les premiers nombres positifs, nous obtenons la figure ci-dessous.

Demi cercle des nombres signées

Nous observons que lorsque nous tournons dans le sens des aiguilles d’une montre, nous incrémentons la valeur du nombre et lorsque nous tournons dans le sens contraire, nous décrémentons la valeur du nombre représenté. Pour compléter ce cercle avec les nombres négatifs, nous commençons par le plus petit de ces nombres (\(-1\)) et nous le plaçons à gauche du \(0\). Nous complétons avec les nombres négatifs suivants et nous obtenons la figure suivante.

Cercle des nombres signées

Cette manière de faire offre de nombreux avantages :

  • Le nombre zéro n’a qu’une seule représentation (tous les bits à \(0\)).
  • Les nombres positifs signés ont la même représentation que les nombres non signés.
  • Le bit de poids fort nous permet toujours de savoir si un nombre est positif (\(\mathsf{MSB} = 0\)) ou négatif (\(\mathsf{MSB} = 1\)).
  • On a bien partagé l’espace en deux parties égales pour les nombres positifs et les nombres négatifs. Comme on considère le \(0\) comme un nombre positif, le côté négatif, en valeur absolue, va plus loin que le côté positif, mais ça n’a pas vraiment d’importance.
  • L’addition de deux nombres signés peut réutiliser le système de chaîne de full adders. Nous pouvons illustrer avec \(-1 + 2\). En binaire, ça fait \(111_2 + 010_2 = 001_2\) (avec une retenue à \(1\)) et \(001_2 = 1_{10}\) ce qui est bien le résultat de \(-1 + 2\). On voit que la retenue (carry) telle que nous la calculons n’a plus vraiment de sens dans l’addition des nombres signés et nous y reviendrons plus tard.

Cette manière de coder les nombres entiers signés s’appelle le complément à 21 et c’est la méthode la plus souvent utilisée par les ordinateurs pour représenter les nombres binaires signés.

Si nous considérons des nombres sur \(N\) bits :

  • les nombres entre \(0\) et \(2^{N-1}-1\) sont représentés comme les nombres non signés;
  • pour représenter les nombres \(x\) entre \(-2^{N-1}\) et \(-1\), on ajoute on biais de \(2^N\). On calcule \(x' = x + 2^N\) et on représente \(x'\) comme un nombre non signé. Par exemple, avec un système sur 8 bits, pour représenter \(x = -42\), nous calculons \(x' = -42 + 2^8 = -42 + 256 = 214\) et \(214_{10} = 11010110_2\) ce qui correspond à la représentation binaire de \(-42\). Notez que le bit de poids fort et bien \(1\), ce qui est correct pour un nombre négatif.

Note

Dans la technique ci-dessus, nous avons ajouté un biais de \(2^N\) pour représenter les nombres négatifs. Que se passe-t-il si nous ajoutons également ce biais aux nombres positifs ?

Ajouter un biais de \(2^N\) à un nombre positif sur \(N\) bits ne change pas la valeur de ce nombre. En effet, \(2^N\) c’est \(1\) suivi de \(N\) zéros, et ce \(1\) est donc en dehors des \(N\) bits. On peut comparer ce \(1\) à la retenue (carry) et on peut dire que d’ajouter un biais de \(2^N\) à un nombre positif met la retenue à \(1\), mais ça ne change pas la valeur du nombre.

On peut donc représenter tous les nombres signées en ajoutant un biais de \(2^N\).

Nous pouvons reprendre cette idée de biais pour calculer l’opposé d’un nombre. Pour représenter \(-x\), nous pouvons ajouter le biais de \(2^N\) et calculer \(2^N - x\). Avec cette technique \(-42\) est représenté sur 8 bits \(2^8 - 42 = 256 - 42 = 214\) et ça correspond bien à ce que nous avons écrit plus haut. Pour calculer \(2^N - x\), le microprocesseur utilise une technique pour remplacer la soustraction par une addition. Nous pouvons réécrire \(2^N - x\) comme \((2^N - 1) - x + 1\), et \((2^N - 1) - x\) revient à inverser tous les bits de \(x\) (les \(0\) deviennent de \(1\) et les \(1\) deviennent des \(0\)). Nous pouvons donc dire que \(-x = 2^N - x = (2^N - 1) - x + 1 = \overline{x} + 1\). Autement dit, pour calculer l’opposé de \(x\), il suffit d’inverser tous les bits de \(x\) et d’ajouter \(1\).

Illustrons cette technique avec \(42_{10} = 101010_2\). Pour calculer \(-42\), on inverse les bits de \(42\) et on ajoute \(1\) :

\[-42_{10} = \overline{00101010_2} + 1 = 11010101_2 + 1_2 = 11010110_2\]

Notez que l’opposé est une opération symétrique et l’opposé de \(-42\) doit redonner \(42\) :

\[-(-42)_{10} = \overline{11010110_2} + 1_2 = 00101001_2 + 1_2 = 101010_2 = 42\]

Nous venons de voir deux techniques simples pour calculer la représentation d’un nombre négatif en complément à 2. L’opération inverse est simple elle aussi. Supposons que vous souhaitez trouver le nombre qui correspond à la représentation binaire \(10101010_2\) codée sur 8 bits. Le \(1\) en bit de poids fort indique que le nombre est négatif et la première technique consiste à soustraire \(2^N\) : \(x = 10101010_2 - 2_{10}^8 = 170_{10} - 256_{10} = -86_{10}\). La réponse est donc \(-86_{10}\). La deuxième technique est de calculer l’opposé du nombre en inversant les bits et en ajoutant \(1\): \(-x = \overline{10101010_2} + 1_2 = 1010101_2 + 1_2 = 1010110_2 = 86_{10}\), donc \(x = -86_{10}\).