Les données numériques

Écrit par Jean-Michel CHRISOSTOME. Publié dans Blog

Vous retrouverez les liens et références à la fin de cet article.

1 - Les bits

Le bit est la plus petite unité numérique définissable. Le bit correspond l'état d'un signal électrique (arrêt ou marche). Un bit peut se résumer à un interrupteur contrôlant une ampoule qui est soit en à l'arrêt (0) soit en fonctionnement (1). Seul, il ne représente rien et on ne peut pas définir de donnée plus complexe que OUI ou NON. Pour créer l'informatique il aura été nécessaire de regrouper les bits en blocs nommés octets.

 
Représentation d'une séquence de bits sous forme de signal électrique
La ligne horizontale représente le temps, et la ligne vertical l'état du signal

2 - Les octets

Stockage des octets

Sur les premiers systèmes d’exploitations publics (GECOS créé en 1962 par la General Electric) les octets étaient des assemblages de 6 bits stockés sur des fiches en carton appelées “cartes perforées” et regroupés par “mots” de 36 bits. La première carte perforée a été créée vers 1725 pour automatiser des métiers à tisser.

Plus tard, avec l’apparition du standard ASCII, les octets sont devenus des assemblages de 8 bits.

Un bit pouvant avoir 2 valeurs (0 ou 1), un octet comportant 8 bits permet donc de coder 28 = 2×2×2×2×2×2×2×2 = 256 valeurs (de 0 à 255).

Un octet peut être représenté de différentes manières:

  • En binaire allant de 00000000 à 11111111
  • En hexadécimal (base 16) de 00 à FF (aussi écrit: 0x00 à 0xFF)
  • En décimal non signé de 0 à 255
  • En décimal signé de -128 à 127

Voici une liste de supports pour stocker nos octets:

    • Sur des cartes perforées par “mots” de 80 caractères de 8 bits (mais aussi les codes bar ou QR codes)
    • Sur des puces électroniques PROM - EPROM - EEPROM (cartes SD, ou disques SSD, barettes de mémoire RAM)
    • Sur des bandes magnétiques, cassettes ou cartouches, puis disquettes (cartouches LTO, cassettes DAT, et aussi sur nos veilles cassettes audio à l'époque des Amstrad et ORIC)
      • Ces données stockées sous forme de signaux électriques étaient décodées par des modem intégrés à nos vieux ordinateurs
  • Sur des disques de métal au format binaire proche des cartes perforées (CDROM, DVDROM, BluRay)
  • Sur des disques magnétiques au format binaire par impulsions électriques (disques dur)

A présent que nous avons défini les octets, nous savons les stocker sur différents types de supports. C'est bien beau tout ça, mais qu'allons nous faire de ces données numériques?

Traitement des octets

Les microprocesseurs ont évolué depuis l'aube de l'informatique et leur aptitude à traiter des mots de plus en plus long et de plus en plus rapidement ont permis aux ordinateurs d'évoluer jusqu'aux capacités époustouflantes qu'on leur connait aujourd'hui. Mais cela n'a pas toujours été ainsi. Aux premières heures de l'informatique grand public, les microprocesseurs ne savaient traiter qu'un nombre de données restreint et pire encore, ils le faisaient beaucoup plus lentement qu'aujourd'hui.

Voici l'évolution de la taille des mots traités par les microprocesseurs (CPU) grand public:

  • 8 bits (Z80, Intel 8008) - Années 1970 - 1980
  • 16 bits (Intel 8086, Motorola 68000) - Années 1980 - 1990
  • 32 bits (Intel 80386, Motorola 68020) - Années 1990 - 2000
  • 64 bits (Intel core 2 duo, MIPS R4000) - Années 1990 - 2000

La taille des mots en bits qu'un microprocesseur sait traiter lui permet également de définir des plages d'adresses pour stocker des données en mémoire. Il faut se représenter la mémoire RAM comme un damier avec des coordonnées pour chaque case. Plus le damier est grand, plus vous aurez un nombre de case important et donc vous aurez besoin plus de bits pour définir ce nombre. Si vous rangez vos données dans le damier mais que vous n'êtes pas en mesure de noter les coordonnées où elles sont stockées, comment allez vous les retrouver?

Voici la quantité de mémoire adressable en fonction de la taille du bus mémoire en bits:

  • 8 bits ⇒ 16 Ko (2 puissance 8)
  • 16 bits ⇒ 64 Ko (2 puissance 16)
  • 32 bits ⇒ 4 Go (2 puissance 32)
  • 64 bits ⇒ 16 777 216 To ou 16 Eo (2 puissance 64)

Bien sûr, il existe des astuces de programmation qui permettent d'adresser plus de mémoire que la limite du bus ne le permet. Mais ce sera au détriment des performances.

3 - Les types de données de base

Maintenant, avec les connaissances que nous avons, nous savons traiter et stocker des données...

Oui mais au fait, quoi comme données???

C'est vrai! Comment faire la différence entre des numéros de série et des vraies données numériques utilisées dans des calculs?

En somme, comment différencier des chiffres utilisés dans du texte et des chiffres utilisés pour des calculs? Comment faire comprendre ça à un microprocesseur?

Le standard ASCII nous indique que chaque caractères imprimables (a, B, c, D, 0, 1, 2, @ etc...) correspond à un code (dit code ASCII) numérique lui donnant une position bien définie dans la table du même nom. Ainsi, avec seulement des chiffres, nous sommes capables de stocker du texte et bien d'autres choses encore.

Par exemple: Le nombre 32768 stocké sous forme de caractères nous coûterait 5 caractères ⇒ 6|5|5|3|5 soit en hexadécimal 33 32 37 36 38.

Le même nombre stocké avec un type binaire adapté (short int par exemple) ne coûterait que 2 caractères ⇒ 80 00

Il existe plusieurs types de données en fonction de l'usage qui leur sera réservé (affichage ou calcul, petits nombres ou très grands nombres, nombres à virgule flottante)

Les types de données présentées ci-dessous sont les types de base tels qu’ils sont utilisés par les microprocesseurs (en langage C, les noms changent en fonction des langages, mais le contenu et le stockage restent les mêmes).

(signed) char

  • Type de donnée: Caractère (character) / Entier signé de faible valeur
  • Taille: 1 octet (8 bits)
  • En C il est recommandé de calculer la taille avec la fonction sizeof(char)
  • Une chaîne de caractère est en fait un tableau de caractères comme suit: nombre de caractères × sizeof(char)
  • Peut contenir: ‘a’ ou ‘z’ ou ‘8’ ou ‘+’
  • Valeur décimale: de -128 à 127
  • Valeur hexadécimale: de 00 à FF
  • Stockage: Non affecté

unsigned char

  • Type de donnée: Caractère (character) non signé / Entier de faible valeur positive
  • Taille: 1 octet (8 bits) / caractère en général (ASCII)
  • En C il est recommandé de calculer la taille avec la fonction sizeof(char)
  • Peut contenir: ‘a’ ou ‘z’ ou ‘8’ ou ‘+’
  • Valeur décimale: de 0 à 255
  • Valeur hexadécimale: de 00 à FF
  • Stockage: Non affecté

(signed) short int

  • Type de donnée: Entier numérique
  • Taille: 2 octets (16 bits)
  • En C il est recommandé de calculer la taille avec la fonction sizeof(short int)
  • Peut contenir: de -32768 à 32767
  • Valeur décimale: de 0 à 65535
  • Valeur hexadécimale: de 00 00 à FF FF
  • Stockage: Dépendant de l’architecture BIG ENDIAN/LITTLE ENDIAN

unsigned short int

  • Type de donnée: Entier numérique positif
  • Taille: 2 octets (16 bits)
  • En C il est recommandé de calculer la taille avec la fonction sizeof(short int)
  • Peut contenir: de 0 à 65535
  • Valeur décimale: de 0 à 65535
  • Valeur hexadécimale: de 00 00 à FF FF
  • Stockage: Dépendant de l’architecture BIG ENDIAN/LITTLE ENDIAN

(signed) int (16 bits/32 bits)

  • Type de donnée: Entier numérique
  • Taille: 2 octets (16 bits) / 4 octets (32 bits)
  • En C il est recommandé de calculer la taille avec la fonction sizeof(int)
  • Peut contenir: de -2 147 483 648 à 2 147 483 647
  • Valeur décimale: de 0 à 4 294 967 295
  • Valeur hexadécimale: de 00 00 00 00 à FF FF FF FF
  • Stockage: Dépendant de l’architecture BIG ENDIAN/LITTLE ENDIAN

unsigned int (16 bits/32 bits)

  • Type de donnée: Entier numérique positif
  • Taille: 2 octets (16 bits) / 4 octets (32 bits)
  • En C il est recommandé de calculer la taille avec la fonction sizeof(int)
  • Peut contenir: de 0 à 4 294 967 295
  • Valeur décimale: de 0 à 4 294 967 295
  • Valeur hexadécimale: de 00 00 00 00 à FF FF FF FF
  • Stockage: Dépendant de l’architecture BIG ENDIAN/LITTLE ENDIAN

(signed) long int (16 bits/32 bits)

  • Type de donnée: Entier numérique
  • Taille: 4 octets (32 bits) / 8 octets (64 bits)
  • En C il est recommandé de calculer la taille avec la fonction sizeof(long int)
  • Peut contenir: de -9 223 372 036 854 775 808 à -9 223 372 036 854 775 807
  • Valeur décimale: de 0 à 18 446 744 073 709 551 615
  • Valeur hexadécimale: de 00 00 00 00 00 00 00 00 à FF FF FF FF FF FF FF FF
  • Stockage: Dépendant de l’architecture BIG ENDIAN/LITTLE ENDIAN

unsigned lont int (16 bits/32 bits)

  • Type de donnée: Entier numérique positif
  • Taille: 4 octets (32 bits) / 8 octets (64 bits)
  • En C il est recommandé de calculer la taille avec la fonction sizeof(long int)
  • Peut contenir: de 0 à 18 446 744 073 709 551 615
  • Valeur décimale: de 0 à 18 446 744 073 709 551 615
  • Valeur hexadécimale: de 00 00 00 00 00 00 00 00 à FF FF FF FF FF FF FF FF
  • Stockage: Dépendant de l’architecture BIG ENDIAN/LITTLE ENDIAN

(signed) long long int

  • Type de donnée: Entier numérique de grande taille
  • Taille: 8 octets (64 bits)
  • En C il est recommandé de calculer la taille avec la fonction sizeof(long long int)
  • Peut contenir: de -9 223 372 036 854 775 808 à -9 223 372 036 854 775 807
  • Valeur décimale: de 0 à 18 446 744 073 709 551 615
  • Valeur hexadécimale: de 00 00 00 00 00 00 00 00 à FF FF FF FF FF FF FF FF
  • Stockage: Dépendant de l’architecture BIG ENDIAN/LITTLE ENDIAN

unsigned long long int

  • Type de donnée: Entier numérique positif de grande taille
  • Taille: 8 octets (64 bits)
  • En C il est recommandé de calculer la taille avec la fonction sizeof(long long int)
  • Peut contenir: de 0 à 18 446 744 073 709 551 615
  • Valeur décimale: de 0 à 18 446 744 073 709 551 615
  • Valeur hexadécimale: de 00 00 00 00 00 00 00 00 à FF FF FF FF FF FF FF FF
  • Stockage: Dépendant de l’architecture BIG ENDIAN/LITTLE ENDIAN

float

  • Type de donnée: Flottant numérique à précision simple
  • Taille: 4 octets (32 bits)
  • En C il est recommandé de calculer la taille avec la fonction sizeof(float)
  • Peut contenir: de -3.4e-38 à 3.4e38 avec une précision à 7 décimales
  • Valeur décimale: de 0 à 4 294 967 295
  • Valeur hexadécimale: de 00 00 00 00 à FF FF FF FF
  • Stockage: Dépendant de l’architecture BIG ENDIAN/LITTLE ENDIAN

double

  • Type de donnée: Flottant numérique à précision double
  • Taille: 8 octets (64 bits)
  • En C il est recommandé de calculer la taille avec la fonction sizeof(double)
  • Peut contenir: de -1.7e-308 à 1.7e308 avec une précision à 15 décimales
  • Valeur décimale: de 0 à 18 446 744 073 709 551 615
  • Valeur hexadécimale: de 00 00 00 00 00 00 00 00 à FF FF FF FF FF FF FF FF
  • Stockage: Dépendant de l’architecture BIG ENDIAN/LITTLE ENDIAN

long double (32 bits / 64 bits)

  • Type de donnée: Flottant numérique de grande taille à forte précision
  • Taille: 10 octets (80 bits) / 16 octets (128 bits)
  • En C il est recommandé de calculer la taille avec la fonction sizeof(long double)
  • Peut contenir: ????
  • Valeur décimale: de 0 à 18 446 744 073 709 551 615
  • Valeur hexadécimale: de 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 à FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
  • Stockage: Dépendant de l’architecture BIG ENDIAN/LITTLE ENDIAN

void * (dépendant de l’architecture)

  • Type de donnée: Pointeur (adresse mémoire)
  • Taille: (complètement dépendant de l’architecture)
  • En C il est recommandé de calculer la taille avec la fonction sizeof(void *)
  • Peut contenir: Toute la plage adressable par l’architecture
  • Valeur décimale: (complètement dépendant de l’architecture)
  • Valeur hexadécimale: (complètement dépendant de l’architecture)
  • Stockage: Dépendant de l’architecture BIG ENDIAN/LITTLE ENDIAN

4 - L'histoire du petit et du grand indien...

Il existe différents ordres de stockage des données numérique de grande taille (16 bits et au delà) dont les plus connus sont nommés LITTLE ENDIAN, BIG ENDIAN ou encore BI-ENDIAN (plus rare). Pour des raisons de culture, de conception et de logique différentes, les deux principales architectures stockent et traitent les données dans un ordre qui lui est propre.

Processeurs BIG-ENDIAN: SPARC, ARM, PowerPC
Le grand boutiste (BIG-ENDIAN) range l’octet de poid fort à l’adresse la plus petite
Processeurs LITTLE-ENDIAN: Intel et compatibles Intel (x86 IA32, IA64 ou amd64)
Le petit boutiste (LITTLE-ENDIAN) range l’octet de poid faible à l’adresse la plus petite

En résumé, on peut dire que les données sont rangées en mémoire en fonction de l'architecture matérielle qui les traite.

Vous trouverez ci-dessous un petit bout de code en C qui, une fois compilé (avec gcc) et éxécuté, va générer un fichier par type (au sens C du terme) en stockant une valeur à l'intérieur afin de vous donner un aperçu de la manière dont les données numériques sont rangées en mémoire. Le binaire enregistre les données dans des fichiers telles qu'elles sont stockées en mémoire. Je vous recommande de vous équiper d'un éditeur hexadécimal pour comparer les contenus des fichiers (Editeur hexadécimal).

#include <stdio.h>
#include <stdlib.h>
 
/* Pour compiler ce code source:
* gcc sizeof-types.c -o sizeof-types
* Pour éxécuter ce programme une fois compilé
* ./sizeof-types (sous UNIX/Linux)
* ou
* sizeof-types.exe (sous Windows)
*/
 
int main(void) {
  FILE *fd;
  char char_val='c';
  short int sint_val=25315;
  int int_val=-1123315;
  long int lint_val=-3223372036854775807L;
  float float_val=3.1415926F;
  double double_val=3.14159265358979323846264338;
  void *void_val=(void * ) &amp;int_val;
  /* Codes couleur pour affichage */
  char btitle[]="\033[04;34;43m";
  char ecol[]="\033[0m";
  char bnom[]="\033[04;33;40m";
  char btaille[]="\033[01;33;40m";
 
  printf("%sType\t\tTaille\t\tType C%s\n", btitle, ecol);
  printf("%sEntier court\t%s%d octets\t%sshort int%s\n", bnom, ecol, sizeof(short int), btaille, ecol);
  printf("%sEntier\t\t%s%d octets\t%sint%s\n", bnom, ecol, sizeof(int), btaille, ecol);
  printf("%sEntier long\t%s%d octets\t%slong int%s\n", bnom, ecol, sizeof(long int), btaille, ecol);
  printf("%sEntier long long\t%s%d octets\t%slong long int%s\n", bnom, ecol, sizeof(long long int), btaille, ecol);
  printf("%sFlottant\t%s%d octets\t%sfloat%s\n", bnom, ecol, sizeof(float), btaille, ecol);
  printf("%sCaractere\t%s%d octets\t%schar%s\n", bnom, ecol, sizeof(char), btaille, ecol);
  printf("%sSans type\t%s%d octets\t%svoid%s\n", bnom, ecol, sizeof(void), btaille, ecol);
  printf("%sDouble\t\t%s%d octets\t%sdouble%s\n", bnom, ecol, sizeof(double), btaille, ecol);
  printf("%sDouble long\t%s%d octets\t%slong double%s\n", bnom, ecol, sizeof(long double), btaille, ecol);
  printf("%sPointeur\t%s%d octets\t%svoid *%s\n", bnom, ecol, sizeof(void *), btaille, ecol);
 
  /* Ecriture de données typées sur le disque */
  /* Lire les fichiers avec un éditeur hexadécimal */
  /* char */
  fd=fopen("./char", "w");
  if (fd) {
    fwrite(&amp;char_val, sizeof(char), (size_t) 1, fd);
    printf("Le caractère (char) 'c' est écrit dans le fichier './char'\n");
    fclose(fd);
  }
  /* short int */
  fd=fopen("./short_int", "w");
  if (fd) {
    fwrite(&amp;sint_val, sizeof(short int), (size_t) 1, fd);
    printf("La valeur (short int) 25315 est écrite dans le fichier './short_int'\n");
    fclose(fd);
  }
  /* int */
  fd=fopen("./int", "w");
  if (fd) {
    fwrite(&amp;int_val, sizeof(int), (size_t) 1, fd);
    printf("La valeur (int) -1 123 315 est écrite dans le fichier './int'\n");
    fclose(fd);
  }
  /* long int */
  fd=fopen("./long_int", "w");
  if (fd) {
    fwrite(&amp;lint_val, sizeof(long int), (size_t) 1, fd);
    printf("La valeur (long int) -3 223 372 036 854 775 807 est écrite dans le fichier './long_int'\n");
    fclose(fd);
  }
  /* float */
  fd=fopen("./float", "w");
  if (fd) {
    fwrite(&amp;float_val, sizeof(float), (size_t) 1, fd);
    printf("La valeur (float) 3.1415926 est écrite dans le fichier './float'\n");
    fclose(fd);
  }
  /* double */
  fd=fopen("./double", "w");
  if (fd) {
    fwrite(&amp;double_val, sizeof(double), (size_t) 1, fd);
    printf("La valeur (double) 3.14159265358979323846264338 est écrite dans le fichier './double'\n");
    fclose(fd);
  }
  /* void * */
  fd=fopen("./void", "w");
  if (fd) {
    fwrite(&amp;void_val, sizeof(void *), (size_t) 1, fd);
    printf("Le pointeur (void *) %p est écrit dans le fichier './pointer'\n", (void *) void_val);
    fclose(fd);
  }
 
  return EXIT_SUCCESS;
}
 

J'ai exécuté ce code sur mon PC actuel (CPU Intel Quad Core 64 Bits) ainsi que sur ma vieille station SUN Ultra Sparc 64 bits elle aussi et je vous ai préparé un petit tableau ci-dessous avec les résultats à comparer. La différence saute aux yeux dès que l'on arrive sur des types plus complexes qu'un simple caractère.

Résultats du source sizeof-types.c compilé sur INTEL et SPARC
Type de baseValeurLITTLE-ENDIAN (mon PC)BIG-ENDIAN (ma vieille station Sun Ultra SPARC 64)
char (8 bits / 1 octet) c 63 63
short int (16 bits) 25315 E3 62 62 E3
int (32 bits) -1123315 0D DC EE FF FF EE DC 0D
float (32 bits) 3.1415926 DA 0F 49 40 40 49 0F DA
long int (64 bits) -3223372036854775807 01 00 58 EC 35 48 44 D3 D3 44 48 35 EC 58 00 01
double (64 bits) PI (26 décimales) 40 09 21 FB 54 44 2D 18 18 2D 44 54 FB 21 09 40

 

Liens et références

Cet article n'a pas pour vocation d'être complet. Si vous souhaitez en savoir plus, voici une liste d'articles traitant en détails des différents sujets soulevés dans cet article. Bonne lecture!