TD — Séance 2 — Représentation numérique

Bases, entiers, complément à deux, conversions, débordements, masques, IEEE 754

TD de la séance 2 de Programmation C avancée : bases, entiers, complément à deux, bits, flottants IEEE 754.
Auteur

Dr. El Hadji Bassirou TOURÉ · DMI/FST/UCAD

NoteObjectif

Lire et écrire des motifs de bits sans machine : changer de base, lire un dump little-endian avec différents types, prévoir une conversion, une extension, une troncature, un débordement, manipuler des champs de bits et encoder un flottant IEEE 754. Pour chaque réponse, nommer la règle appliquée. Machine x86-64 little-endian, Linux, gcc 13, options -std=c17 -Wall -Wextra : char 1 octet (signé), short 2, int et unsigned 4, long et size_t 8 ; entiers signés en complément à deux, flottants IEEE 754.

Exercice 1 : changements de base et complément à deux

  1. Compléter le tableau (entiers non signés sur 8 bits) ; chaque ligne donne une seule des trois écritures.

    binaire (8 bits) hexadécimal décimal
    1110 0101
    0x3d
    172
    0101 1011
    0xc7
    77
  2. Convertir 172 et 77 en binaire par divisions successives par 2, en écrivant chaque division et chaque reste.

  3. Écrire 0x7e2d en binaire sur 16 bits, puis \(1011\,0110\,0001\,1101_2\) en hexadécimal, sans passer par le décimal.

  4. Sur 8 bits, calculer \(-x\) par \(\mathord{\sim}x + 1\) pour \(x = 83\), \(x = 1\) et \(x = -128\), en écrivant \(x\), \(\mathord{\sim}x\) et \(\mathord{\sim}x + 1\) en binaire et en hexadécimal. Commenter la valeur obtenue pour \(-128\).

  5. Calculer \(T2U_8(-73)\), \(U2T_8(201)\), \(T2U_{16}(-900)\) et \(U2T_{32}(2500000000)\).

Exercice 2 : un dump little-endian

Seize octets ont été relevés à partir de l’adresse 0x00 :

Addr +0 +1 +2 +3 +4 +5 +6 +7
0x00 b5 4b e7 ff 00 80 6b 00
0x08 2e fb ff ff 15 cd 5b 07
unsigned char *u = (unsigned char *)0x00;      signed char *s = (signed char *)0x00;
unsigned short *uh = (unsigned short *)0x02;   short *h = (short *)0x02;
unsigned int *un = (unsigned int *)0x08;       int *n = (int *)0x08;
  1. u[0] et s[0].
  2. u[1] et s[1] ; quel caractère ASCII ce code représente-t-il ('A' vaut 65) ?
  3. uh[0] et h[0], en hexadécimal puis en décimal.
  4. uh[1] et h[1] ; que remarquez-vous ?
  5. h[2].
  6. un[0] et n[0].
  7. n[1], en hexadécimal puis en décimal.

Exercice 3 : extension et troncature

#include <stdio.h>
#include <stdint.h>

int main(void)
{
    int8_t a = -41;
    int16_t b = a;
    uint16_t c = (uint8_t)a;
    uint32_t d = a;
    int e = 70000;
    short f = (short)e;
    unsigned char g = (unsigned char)e;
    signed char h = (signed char)(e >> 8);
    int k = 45000;
    short m = (short)k;
    printf("%d %u %u\n", b, c, d);
    printf("%d %u %d %d\n", f, g, h, m);
    return 0;
}
  1. Pour chaque variable, donner son motif en hexadécimal (sur la taille de son type) et sa valeur, en nommant la règle (extension par des zéros, extension de signe, troncature) et en disant si le résultat est défini ou défini par l’implémentation :

    variable type hexadécimal valeur
    a int8_t d7 -41
    b int16_t
    c uint16_t
    d uint32_t
    e int 00011170 70000
    f short
    g unsigned char
    h signed char
    k int 0000afc8 45000
    m short
  2. Écrire les deux lignes affichées.

  3. Les variables b, d, f, m et e sont rangées respectivement aux adresses 0x00, 0x04, 0x08, 0x0a et 0x0c. Remplir le dump (les octets 0x02 et 0x03 valent 00) :

    Addr +0 +1 +2 +3 +4 +5 +6 +7
    0x00
    0x08

Exercice 4 : débordements et comparaisons

  1. Poser en binaire les opérations suivantes sur uint8_t, avec les retenues : \(185 + 90\), \(64 + 63\), \(3 - 10\). Donner chaque résultat, et dire si le test s < a détecte une retenue perdue, ou un emprunt pour la soustraction. Même question pour \(61000 + 7000\) sur uint16_t.
  2. Pour chaque ligne du programme suivant, dire si le résultat est défini, défini par l’implémentation ou indéfini, et donner la valeur quand elle existe.
#include <stdio.h>
#include <limits.h>

int main(void)
{
    unsigned short p = USHRT_MAX;
    p = p + 1;
    short q = SHRT_MAX;
    q = q + 1;
    int r = INT_MIN;
    r = r - 1;
    unsigned int s = 0;
    s = s - 2;
    int t = 46341;
    t = t * t;
    long u = 46341L * 46341;
    int v = -INT_MAX - 1;
    printf("%u %d %d %u\n", p, q, r, s);
    printf("%d %ld %d\n", t, u, v);
    return 0;
}
  1. Prévoir les six lignes affichées par le programme suivant, en donnant pour chaque comparaison le type dans lequel elle est faite. Quel avertissement gcc donne-t-il, et pour quelle ligne ?
#include <stdio.h>

int main(void)
{
    int t = -5;
    unsigned int n = 3;
    unsigned short us = 3;
    long d = t;
    size_t L = 2;
    printf("%d\n", t < n);
    printf("%d\n", t < (int)n);
    printf("%d\n", t < us);
    printf("%d\n", d < n);
    printf("%d\n", t + L > 0);
    printf("%zu\n", t + L);
    return 0;
}
  1. La fonction suivante doit rendre 1 quand le nom dépasse max caractères. Quel est le type de len - max ? Que rend l’appel nom_trop_long("Gossas", 12), et pourquoi ? Corriger la condition.
#include <stdio.h>
#include <string.h>

int nom_trop_long(const char *nom, size_t max)
{
    size_t len = strlen(nom);
    if (len - max > 0)
        return 1;
    return 0;
}

int main(void)
{
    printf("%d\n", nom_trop_long("Gossas", 12));
    printf("%d\n", nom_trop_long("Ourossogui", 10));
    printf("%zu\n", strlen("Gossas") - (size_t)12);
    return 0;
}

Exercice 5 : une date dans 16 bits

Le système de fichiers FAT range une date dans un uint16_t : les bits 15 à 9 donnent l’année depuis 1980, les bits 8 à 5 le mois, les bits 4 à 0 le jour.

  1. Écrire 0x2256 en binaire, puis retrouver le jour, le mois et l’année.
  2. Écrire les trois expressions C qui extraient le jour, le mois et l’année d’une date d.
  3. Encoder la date du 15 juin 2027 dans un uint16_t e : donner l’expression C et la valeur en hexadécimal, puis les deux octets de e dans un dump, de l’adresse la plus basse à la plus haute.
  4. Écrire une expression qui remplace le mois de e par 7 sans toucher aux autres champs, et donner le résultat.
  5. Comment tester, avec un seul opérateur bit à bit, que le jour est impair ? Appliquer à 0x2256 et à e.

Exercice 6 : IEEE 754

  1. Encoder \(11{,}25\) et \(-0{,}375\) en float : signe, binaire, forme normalisée, exposant biaisé, fraction, mot en hexadécimal.

  2. Un tableau float t[4] est rangé en 0x00 sur une machine little-endian :

    Addr +0 +1 +2 +3 +4 +5 +6 +7
    0x00 00 00 f4 42 00 00 20 be
    0x08 00 00 40 00 01 00 80 7f

    Pour chaque élément, donner le mot de 32 bits, les champs \(s\), \(e\), \(f\), la catégorie (normalisé, dénormalisé, zéro, infini, NaN) et la valeur.

  3. Donner le dump des 8 octets d’un double x = 2.5; (biais 1023, 52 bits de fraction) :

    Addr +0 +1 +2 +3 +4 +5 +6 +7
    0x00

Exercice 7 (approfondissement) : limites de la précision

#include <stdio.h>

int main(void)
{
    float f = 16777216.0f;
    float g = f + 1.0f;
    printf("%.1f %d\n", g, g == f);
    int tours = 0;
    for (float x = 0.0f; x != 1.0f && tours < 20; x += 0.1f)
        tours++;
    printf("tours = %d\n", tours);
    float x = 0.0f;
    for (int k = 0; k < 10; k++)
        x += 0.1f;
    printf("%.9f %d\n", x, x == 1.0f);
    return 0;
}
  1. Pourquoi \(16777216 + 1\) n’est-il pas représentable en float ? Quel est le plus petit entier positif non représentable en float ? Écrire la première ligne affichée.
  2. La boucle for s’arrêterait-elle sans le compteur tours ? Prévoir la valeur affichée de tours.
  3. Prévoir la dernière ligne, et proposer un test qui reconnaît que x vaut 1 « à l’arrondi près ».

Pour cette séance

Le cours

Les slides

À lire en ligne

À télécharger

Retour au sommet