Représentation numérique

Chapitre 1 de Programmation C avancée : bases binaire et hexadécimale, entiers signés et non signés, complément à deux, et nombres flottants IEEE 754.
Auteur·rice

Dr. El Hadji Bassirou TOURÉ, Département de Mathématiques et Informatique, Faculté des Sciences et Techniques, Université Cheikh Anta Diop de Dakar

Systèmes de Numération

Base Binaire

NoteNotation binaire

En base 2, chaque position représente une puissance de 2. Un nombre binaire \(b_{n-1}b_{n-2}...b_1b_0\) correspond à \(\sum_{i=0}^{n-1} b_i \cdot 2^i\) où \(b_i \in \{0,1\}\). Le bit de poids fort (MSB) est à gauche, le bit de poids faible (LSB) à droite.

1011 2 =? 10 1 23 8 0 22 0 1 21 2 1 20 1 MSB LSB 8+0+2+1=11 10

ImportantConversion décimal \(\rightarrow\) binaire

Division successive par 2, en conservant les restes. Lire les restes de bas en haut (dernier reste = MSB).

13 10 → binaire 13 ÷ 2=6 reste 1 (LSB) 6 ÷ 2=3 reste 0 3 ÷ 2=1 reste 1 1 ÷ 2=0 reste 1 (MSB) 1101 2

Base Hexadécimale

NoteNotation hexadécimale

Base 16 utilise les symboles 0-9 et A-F (A=10, B=11, C=12, D=13, E=14, F=15). Préfixe 0x en C. Chaque chiffre hex = 4 bits exactement.

Hex 0 1 2 3 4 5 6 7 8 9 A B C D E F Dec 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Bin 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111

ImportantConversion binaire \(\leftrightarrow\) hexadécimal

Grouper par 4 bits depuis la droite. Chaque groupe = 1 chiffre hex. Ajouter des zéros à gauche si nécessaire.

10111010 2 → hex 1011 1010 B(11) A(10) = 0xBA

unsigned int x = 0x3A;      // 3A hex = 0011 1010 bin = 58 dec
unsigned int y = 0xFF;      // FF hex = 1111 1111 bin = 255 dec
printf("%02X\n", x);        // Affiche "3A" (toujours 2 caractères par octet)

Représentation des Entiers

Entiers Non Signés (Unsigned)

NoteUnsigned sur \(w\) bits

\(\boxed{B2U_w(\vec{x}) = \sum_{i=0}^{w-1} x_i \cdot 2^i}\) où \(x_i \in \{0,1\}\) est le bit à la position \(i\).
Plage : \([0, 2^w-1]\). Aucun nombre négatif représentable.

unsignedchar: 11011011 2 1 27 1 26 0 25 1 24 1 23 0 22 1 21 1 20 =128+64+16+8+2+1=219

Plagesdestypesunsigned Type Bits Min Max unsignedchar 8 0 255 unsignedshort 16 0 65535 unsignedint 32 0 4294967295 unsignedlong 64 0 2 64 − 1

Complément à Deux (Two’s Complement)

NoteTwo’s complement sur \(w\) bits

\(\boxed{B2T_w(\vec{x}) = -x_{w-1} \cdot 2^{w-1} + \sum_{i=0}^{w-2} x_i \cdot 2^i}\)
Le bit de poids fort (MSB) \(x_{w-1}\) est le bit de signe : 0 = positif, 1 = négatif.
Plage : \([-2^{w-1}, 2^{w-1}-1]\). Asymétrie : \(|T_{min}| = |T_{max}| + 1\).

signedchar: − 3 1 1 1 1 1 1 0 1 signe = − 128+64+32+16+8+4+1= − 3 signedchar: +120 0 1 1 1 1 0 0 0 =0+64+32+16+8+0+0+0=120

ImportantValeurs remarquables (8 bits)

\(-1\) : Tous les bits à 1 \(\rightarrow\) 0xFF \(= 11111111_2\)
\(T_{min} = -128\) : Bit de signe à 1, reste à 0 \(\rightarrow\) 0x80 \(= 10000000_2\)
\(T_{max} = 127\) : Bit de signe à 0, reste à 1 \(\rightarrow\) 0x7F \(= 01111111_2\)
Négation : \(-x = \sim x + 1\) (inverser tous les bits, ajouter 1)

Négation: 5 →− 5 (8bits) x =5 0 0 0 0 0 1 0 1 0x05 ∼ x 1 1 1 1 1 0 1 0 0xFA ∼ x +1 1 1 1 1 1 0 1 1 0xFB = − 5

Conversions Signed \(\leftrightarrow\) Unsigned

NotePrincipe de conversion

La conversion entre signed et unsigned préserve le motif binaire mais change son interprétation. Aucun bit n’est modifié.

Mêmemotifbinaire,deuxinterprétations 1 1 1 1 1 1 0 1 0xFD unsigned:253 signed:-3

ImportantFormules de conversion (sur \(w\) bits)

Signed \(\rightarrow\) Unsigned : \(T2U_w(x) = \begin{cases} x & \text{si } x \geq 0 \\ x + 2^w & \text{si } x < 0 \end{cases}\)
Unsigned \(\rightarrow\) Signed : \(U2T_w(u) = \begin{cases} u & \text{si } u \leq T_{max} \\ u - 2^w & \text{si } u > T_{max} \end{cases}\)

signed char sc = -1;              // 0xFF en mémoire
unsigned char uc = (unsigned char)sc;  // Toujours 0xFF, interprété comme 255
int x = -1;
unsigned int y = 0;
if (x < y)   // FAUX! x converti en unsigned: 0xFFFFFFFF > 0
    printf("x < y\n");  // N'est PAS exécuté!
AvertissementComparaisons signed/unsigned

Quand un opérande est signed et l’autre unsigned, le signed est implicitement converti en unsigned. Cela peut inverser le résultat attendu des comparaisons.

Piègesdecomparaison(32bits) Expression Type Évaluation Résultat -1<0U unsigned 0xFFFFFFFF<0 0 (faux) -1>-2 signed − 1 > − 2 1 (vrai) 2147483647>-2147483648 signed Tmax>Tmin 1 (vrai) 2147483647U>-2147483648 unsigned Tmax> 2147483648 0 (faux)

Extension de Signe

NoteExtension lors de la promotion de type

Quand on convertit un entier de \(w\) bits vers \(w'\) bits (\(w' > w\)) :
Unsigned : Extension par zéros (zero extension) Signed : Extension du bit de signe (sign extension)

Extensionunsigned:8 → 16bits uint8:250 1 1 1 1 1 0 1 0 0xFA uint16:250 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0x00FA

Extensionsigned:8 → 16bits int8:-6 1 1 1 1 1 0 1 0 0xFA int16:-6 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 0 0xFFFA bitdesignerépliqué

int8_t x = -6;           // 0xFA
int16_t y = x;           // 0xFFFA (bit de signe étendu)
uint8_t a = 250;         // 0xFA
uint16_t b = a;          // 0x00FA (zéros ajoutés)
int8_t neg = -1;         // 0xFF
uint32_t big = (uint8_t)neg;  // 0x000000FF (255), pas 0xFFFFFFFF

Dépassements et Débordements

Overflow Unsigned

NoteComportement unsigned (défini par la norme C)

L’arithmétique unsigned est définie modulo \(2^w\). Le débordement “wrap around” est garanti. \(\boxed{\text{Résultat} = (\text{valeur mathématique}) \mod 2^w}\)

Wrap-aroundunsigned8bits:lecarryperdu 255: 1 1 1 1 1 1 1 1 0xFF +1: 0 0 0 0 0 0 0 1 =: 1 carry 0 0 0 0 0 0 0 0 0x00 =0 perdu

unsigned char x = 255;
x++;                      // x = 0 (défini, wrap-around: 256 mod 256 = 0)
unsigned char y = 0;
y--;                      // y = 255 (défini: -1 + 256 = 255)
// Détection d'overflow AVANT addition (sûr)
if (b > UINT_MAX - a)     // Vérifie si a + b dépasserait UINT_MAX
    printf("Overflow would occur!\n");

Overflow Signed

AvertissementComportement signed (Undefined Behavior)

Le débordement d’entiers signés est un Undefined Behavior (UB) en C. Le compilateur peut supposer qu’il ne se produit jamais et optimiser le code en conséquence. Le résultat est imprévisible.

Overflowsigned8bits(UB):changementdesigne 127: 0 1 1 1 1 1 1 1 T max +1: 0 0 0 0 0 0 0 1 =: 1 0 0 0 0 0 0 0 = − 128 (UB!)

#include <limits.h>
int a = 2000000000, b = 1000000000;
if ((b > 0 && a > INT_MAX - b) || (b < 0 && a < INT_MIN - b)) {
    printf("Overflow se produirait!\n");
} else {
    int sum = a + b;  // Safe
}
AvertissementBugs de sécurité réels (CVE)

CVE-2009-1385 (Linux kernel e1000 driver): Un overflow d’entier permettait un buffer overflow.
CVE-2021-43267 (Linux kernel TIPC): Un overflow dans la validation de taille permettait une exécution de code.

Représentation IEEE 754 (Flottants)

Pourquoi les flottants ?

Les entiers ne suffisent pas pour : très grands nombres (\(1.5 \times 10^{11}\) m), très petits nombres (\(9.11 \times 10^{-31}\) kg), nombres fractionnaires (\(\pi\), 19.99 €).

AstuceLe problème de la virgule fixe

Virgule fixe gaspille des bits : pour \(0.000001\) la partie entière est inutile, pour \(10^{15}\) la partie fractionnaire est inutile. Solution : Faire “flotter” la virgule → virgule flottante.

Nombres binaires fractionnaires

NoteReprésentation binaire fractionnaire

\(\boxed{b_m b_{m-1} \ldots b_1 b_0 \textcolor{red}{.} b_{-1} b_{-2} \ldots b_{-n} = \sum_{i=-n}^{m} b_i \times 2^i}\)
Bits à gauche du point : poids \(2^0, 2^1, 2^2, ...\)
Bits à droite du point : poids \(2^{-1} = 0.5\), \(2^{-2} = 0.25\), \(2^{-3} = 0.125\), …

AstuceExemple : \(101.11_2 = ?\)

\(101.11_2 = 1 \times 2^2 + 0 \times 2^1 + 1 \times 2^0 + 1 \times 2^{-1} + 1 \times 2^{-2} = 4 + 0 + 1 + 0.5 + 0.25 = \mathbf{5.75}\)

Formule IEEE 754

NoteFormule IEEE 754

\(\boxed{V = (-1)^s \times M \times 2^E}\)
\(s\) = Signe (0 = positif, 1 = négatif), \(M\) = Mantisse (chiffres significatifs), \(E\) = Exposant (puissance de 2)

Structure d’un float (32 bits)

s exposant fraction(mantisse) 1bit 8bits 23bits

ImportantDécodage des champs

Signe : \(s = 0\) → positif, \(s = 1\) → négatif
Exposant biaisé : \(\text{exposant stocké} = E + 127\) donc \(E = \text{exposant stocké} - 127\)
Mantisse avec “1 implicite” : \(M = 1.\text{fraction}\) (le “1.” n’est pas stocké)

Exemple : convertir 13.0 en float

  1. Signe : \(13.0\) positif → \(s = 0\)
  2. Binaire : \(13 = 1101_2\) donc \(13.0 = 1101.0_2\)
  3. Normaliser : \(1101.0_2 = 1.101_2 \times 2^3\) → \(E = 3\)
  4. Exposant biaisé : \(3 + 127 = 130 = 10000010_2\)
  5. Fraction : \(101\) + 20 zéros

0 10000010 10100000000000000000000 s =0 exp =130 frac

Résultat : 0x41500000. Vérification : \((-1)^0 \times 1.101_2 \times 2^3 = 1.625 \times 8 = 13.0\) ✓

Exemple : \(-13.625\)

AstuceDécorticage de \(-13.625\)

1. Signe : négatif → \(s = 1\)
2. Binaire : \(13 = 1101_2\), \(0.625 = 0.101_2\) → \(13.625 = 1101.101_2\)
3. Normalisation : \(1.101101_2 \times 2^3\)
4. Exposant : \(3 + 127 = 130 = 10000010_2\)
5. Fraction : \(101101\) + 17 zéros
Résultat : 0xC15A0000

Valeurs spéciales

Valeur Quand ? Exemple en C
\(+0\) et \(-0\) Deux représentations de zéro 0.0f, -0.0f
\(+\infty\) et \(-\infty\) Dépassement de capacité 1.0f / 0.0f
NaN Opération indéfinie 0.0f / 0.0f, sqrt(-1)
AvertissementNaN : Not a Number

NaN n’est égal à rien, même pas à lui-même ! Utiliser isnan(x) pour tester.

float x = 0.0f / 0.0f;   // x est NaN
if (x == x)              // FAUX! NaN != NaN
if (isnan(x))            // Correct

Les pièges des flottants

AvertissementPiège n°1 : Représentation inexacte

\(0.1_{10} = 0.0001100110011..._2\) (infini !). Le motif “0011” se répète → erreur d’arrondi.

AvertissementPiège n°2 : Ne JAMAIS comparer avec ==
float a = 0.1f + 0.2f, b = 0.3f;
if (a == b) ...               // DANGER! Peut être faux
if (fabsf(a - b) < 0.00001f)  // Correct: comparer avec tolérance
AvertissementPiège n°3 : Addition non associative
float a = (3.14f + 1e10f) - 1e10f;   // = 0.0 !
float b = 3.14f + (1e10f - 1e10f);   // = 3.14

Cas réel : Patriot Missile (1991)

NoteCe qui s’est passé

Date : 25 février 1991, Guerre du Golfe, Dhahran, Arabie Saoudite.
Bug : L’horloge comptait en incréments de \(0.1\) s approximé sur 24 bits.
Après 100 heures : Erreur cumulée \(\approx 0.34\) s. À \(1676\) m/s → erreur de \(\approx 570\) m.
Conséquence : Le Scud n’a pas été intercepté. 28 soldats tués.

Application en Intelligence Artificielle

NoteLe problème du softmax

\(\text{softmax}(x)_i = \frac{e^{x_i}}{\sum_{j} e^{x_j}}\) est utilisé partout en IA.
Problème : \(e^{1000} = +\infty\) (overflow), \(e^{-1000} = 0\) (underflow) → NaN
Solution : Soustraire le maximum avant de calculer (numériquement stable).

ImportantFormats modernes pour l’IA
Format Bits Usage
float32 32 Standard, haute précision
float16 16 Entraînement GPU accéléré
bfloat16 16 Google TPU
int8 8 Inférence quantifiée

Bonnes Pratiques

Types portables

ImportantUtiliser les types de taille fixe (<stdint.h>)
Type signé Type non signé Taille
int8_t uint8_t 8 bits
int16_t uint16_t 16 bits
int32_t uint32_t 32 bits
int64_t uint64_t 64 bits
#include <stdint.h>
#include <inttypes.h>
uint32_t counter = 0;
printf("counter = %" PRIu32 "\n", counter);

Éviter les pièges signed/unsigned

int index = -1;
unsigned int limit = 100;
if (index >= 0 && (unsigned int)index < limit)  // BON: cast explicite
    printf("Index valide\n");

Options de compilation recommandées

gcc -Wall -Wextra -Werror -Wsign-compare -fsanitize=undefined -g prog.c

Ressources du chapitre

Retour au sommet