Objectifs pédagogiques

Les principaux objectifs de cette feuille d'exercices sont :

  • de diversifier la gamme des types utilisés (en particulier les types entiers à largeur spécifiée du langage C) ;
  • de découvrir les opérateurs de calcul sur les mots binaires (opérateurs logiques bits à bits, opérateurs de décalage de bits) ;
  • d'approfondir l'usage des fonctions d'entrées-sorties standards.

Pour traiter ces exercices, il est recommandé d'avoir étudié le cours jusqu'au chapitre C3‑IX inclus (à l'exception du chap. C3‑IV). Néanmoins, certains exercices ne font appel qu'à un nombre ciblé de connaissances et des renvois aux principaux éléments de cours requis sont indiqués au fur et à mesure des questions.

Travail demandé

Les exercices sont presque tous indépendants les uns des autres, mais ordonnés par niveaux croissants de difficultés et de connaissances requises.

Pour coder les programmes, il est vivement recommandé d'utiliser un IDE comme VS Code, à paramétrer pour obtenir une coloration syntaxique adaptée au langage C++ (cf. chap. C2‑X ).

  • Au fur et à mesure, enregistrer chaque fichier source principal en le nommant conformément à la numérotation des exercices, c'est‑à‑dire par exemple :
    C3exoNvX.ino
    où N est le numéro de l'exercice et X la version du programme.
  • Pour chaque exercice, tous les fichiers, y compris les exécutables générés par la chaîne de compilation locale, doivent être regroupés dans un répertoire de projet, lui‑même placé dans un répertoire d'exercices nommé par exemple C3_EXOS, lui‑même placé dans le répertoire principal de programmation, lui‑même placé dans répertoire personnel d'étudiant.
  • La compilation et l'exécution des programmes peuvent être effectués directement dans un terminal intégré à l'IDE, soit « manuellement » avec commande gcc, soit en exploitant les fonctionnalités de l'IDE, typiquement en paramétrant des tâches dans le fichier task.json (cf. chap. C1-II ).

Remarque. L'exercice nº 5 est en relation avec le sujet de TP nº C3‑1.

  1. Limites d'encodage des types entiers et décimaux standards
  2. Coder un programme qui affiche les limites d'encodage des types entiers standards, en utilisant non pas des valeurs numériques mais les pseudo‑constantes définies dans le fichier d'en‑tête limits.h de la bibliothèque standard du langage C (cf. chap. C3‑II ). Sous Linux, on doit obtenir une exécution produisant en sortie standard l'affichage ci‑dessous (sous Windows, les valeurs pour les types long sont différentes).

        TYPE DESCRIPTOR |           MIN. VALUE |           MAX. VALUE 
                   char |                 -128 |                  127 
          unsigned char |                    0 |                  255 
                  short |               -32768 |                32767 
         unsigned short |                    0 |                65535 
                    int |          -2147483648 |           2147483647 
           unsigned int |                    0 |           4294967295 
                   long | -9223372036854775808 |  9223372036854775807 
          unsigned long |                    0 | 18446744073709551615 
              long long | -9223372036854775808 |  9223372036854775807 
     unsigned long long |                    0 | 18446744073709551615 
    

    Pour la première ligne du tableau, on pourra coder l'instruction suivante (cf. chap. C2‑VII ) :
    printf(" %18s | %20s | %20s \n", "TYPE DESCRIPTOR", "MIN. VALUE", "MAX. VALUE");
    et pour les lignes suivantes, on reprendra alors la même chaîne de format en adaptant les spécifications de conversions (cf. chap. C2‑VII ) en fonction des types des arguments à afficher. Par exemple, pour le type char, on codera :
    printf(" %18s | %20d | %20d \n", "char", CHAR_MIN, CHAR_MAX);

    1. Tester d'abord ce programme dans l'environnement OnlineGDB en langage C puis en C++17. Effectuer également la compilation avec GCC sous Linux.
    2. Tester ce programme sous Windows avec la chaîne de compilation MinGW. Que constate‑t‑on ?
    3. * Dans le même esprit, coder un programme qui affiche les limites d'encodage des types décimaux standards (float, double, long double), en utilisant les pseudo‑constantes définies dans le fichier d'en‑tête float.h de la bibliothèque standard du langage C (cf. chap. C3‑V ). Avec les spécifications de conversion adaptées aux types flottants (cf. chap. C3‑V ), et en sélectionnant la version du langage appropriée (cf. chap. C3‑V ), on doit obtenir une exécution produisant en sortie standard l'affichage ci‑dessous.
    4.   TYPE DESCRIPTOR |   DENORM. MIN. VALUE |   NORM. MIN. VALUE |   MAX. VALUE 
                  float |              1.4e-45 |            1.2e-38 |      3.4e+38 
                 double |             4.9e-324 |           2.2e-308 |     1.8e+308 
            long double |            3.6e-4951 |          3.4e-4932 |    1.2e+4932 
      
      Tenter de compiler dans l'environnement OnlineGDB et sous Windows. Que se passe‑t‑il ?
  3. Génération de nombres aléatoires et statistiques
  4. On souhaite tester la qualité du générateur de nombres pseudo‑aléatoires employé par un ordinateur. Pour cela :

    • on simule un grand nombre de lancés d'un dé à 6 faces (six‑sided dice – ou die) ;
    • ce faisant, on calcule les deux principaux paramètres statistiques de l'ensemble des résultats obtenus : la moyenne et l'écart‑type ;
    • pour finir, on calcule pour chacun de ses deux paramètres, la différence avec la valeur de la grandeur probabiliste correspondante, sachant qu'un dé supposé parfait suit une loi de probabilité uniforme discrète – cf. la page Wikipedia W. Il est indispensable de consulter cette page web (ou une documentation équivalente) pour connaître les valeurs de ces grandeurs probabilistes :
      • l'espérance qui correspond à la moyenne ;
      • la racine carrée de la variance qui correspond à l'écart‑type.

    On déclarera une constante nommée NB_OF_ROLLS pour coder le nombre de lancés du dé et on lui donnera successivement les valeurs 10, 1000 et 1000000 pour tester le programme.

    1. Coder un programme qui simule NB_OF_ROLLS lancés du dé (à l'aide des fonctions rand et srand – cf. chap. C3‑II ), qui calcule la moyenne statistique des résultats obtenus et qui compare (c'est‑à‑dire, calcule la différence – en anglais, gap) avec l'espérance mathématique de la loi uniforme discrète donnée via le lien supra (c'est‑à‑dire, la valeur théorique de la moyenne pour un nombre infini de lancés).
    2. Ce programme devrait fournir sur le terminal d'exécution un affichage de la forme :
      >>>>> Random number generation test program <<<<<
      Number of rolls:  1000
      --------------------------------------
      Statistical mean: 3.496
      Math expectation: 3.5
      Gap:              -0.004
      
    3. Au programme précédent, ajouter le calcul de l'écart‑type W des résultats obtenus. Pour cela, On utilisera non pas la formule de définition de l'écart‑type, mais la propriété donnée par la formule en figure ci‑contre. Calculer la différence (gap) avec la racine carrée de la variance de la loi uniforme discrète (c'est‑à‑dire, la valeur théorique de l'écart‑type pour un nombre infini de lancés).
    4. Ce calcul devrait fournir sur le terminal d'exécution un affichage supplémentaire (par rapport à celui du programme précédent) de la forme :
      --------------------------------------
      Standard deviation:   1.76182
      Variance square root: 1.70783
      Gap:                  0.0539921
      
    5. Au programme précédent, ajouter un calcul du déséquilibre de la population des lancés par rapport à la valeur théorique de la médiane W, c'est‑à‑dire la différence entre le nombre de valeurs inférieures et le nombre de valeurs supérieures à la valeur théorique de la médiane, rapportée au nombre de lancés.
    6. Ce calcul devrait fournir sur le terminal d'exécution un affichage supplémentaire (par rapport à celle du programme précédent) de la forme :
      --------------------------------------
      Median unbalance: -0.8 % (expected 0 %)
      
  5. Calculateur d'adresses IPv4
  6. On souhaite coder un calculateur d'adresses IPv4 à l'instar de ceux que l'on peut trouver sur l'Internet  (cf. la capture d'écran ci‑contre). À partir d'une adresse saisie par l'utilisateur en notation décimale pointée CIDR (cf. chap. R1‑III ), c'est‑à‑dire de la forme :
    ▯▯▯.▯▯▯.▯▯▯.▯▯▯/▯▯
    le programme doit déterminer la valeur entière de cette adresse IP puis calculer toutes les adresses nécessaires en notation décimale pointée :

    • le masque du réseau,
    • l'adresse du réseau,
    • la première adresse de machine,
    • la dernière adresse de machine,
    • l'adresse de diffusion générale (broadcast),
    • et le nombre total d'adresses de machines qu'on peut attribuer sur ce réseau.

    Pour tout rappel sur ces notions, on peut se reporter au chapitre R1‑III R.

    On peut tester le bon fonctionnement du programme avec des adresses diverses en comparant les résultats avec ceux obtenus par un calculateur en ligne comme celui dont le lien est donné supra.

    • Les cinq composantes de l'adresse saisie par l'utilisateur seront récupérées via la fonction scanf sous forme d'octets, c'est‑à‑dire de variables de type uint8_t déclarées respectivement ipByte3, ipByte2, ipByte1, ipByte0 par ordre de poids décroissants et ipMask pour la valeur CIDR du masque. Il faut employer les spécifications de conversion appropriées (cf. chap. C2-VI ).
    • À partir des octets de l'adresse saisie, les six adresses à déterminer seront calculées dans des variables de type uint32_t respectivement déclarées hostIPaddress, netIPmask, netIPaddress, firstIPaddress, lastIPaddress et broadcastIPaddress.
    • Les calculs d'adresses peuvent être codés « facilement » à l'aide des opérateurs logiques bits à bits (cf. chap. C3‑III ) et des opérateurs de décalage de bits (cf. chap. C3‑III ).
  7. Encodage de caractères Unicode en UTF‑8
  8. On souhaite coder un programme qui, en boucle, détermine l'encodage UTF‑8 (cf. chap. C3‑IX ) d'un caractère à partir de son numéro – ou point de code hexadécimal – dans le catalogue Unicode (cf. chap. C3‑IX ), saisi par l'utilisateur. On demande deux formats d'affichage des octets du code UTF‑8 :

    • en notation hexadécimale ;
    • sous forme de caractères ASCII étendus dans la page de code Windows‑CP1252 (cf. chap. C3‑VIII ), comme sur l'affiche parodique ci‑contre où le caractère attendu « é » est remplacé par la séquence d'octets affichée Ã©.
    • (Un tel affichage erroné se produit lors d'un transcodage inverse mal choisi, typiquement lors de l'exécution d'un protocole de communication opéré via des machines qui mettent en œuvre des versions différentes du protocole, utilisant des algorithmes d'encodages de caractères différents – cf. chap. Rc2‑VII R).

    Attention : pour arriver à produire l'effet attendu, deux solutions sont possibles pour compiler et exécuter le programme  :

    • sur un PC Windows avec la chaîne de compilation MinGW32 (l'ancienne version 32 bits ne prend pas en charge l'UTF‑8).
    • sur un PC Linux avec la chaîne de compilation GCC, à condition de changer l'encodage des caractères dans le terminal de commandes en ligne (cf. chap. C3‑VIII ).

    Les tests de « bon » fonctionnement seront effectués successivement pour les 4 caractères listés dans le tableau ci‑dessous.

    Glyphe A é 😈
    Point de code (0x) U+41 U+E9 U+20BF U+1F608
    Code UTF-8 (0x) 41 C3 A9 E2 82 BF F0 9F 98 88
    Code UTF-8 (CP1252 )  A  Ã  ©  â  ‚  ¿  ð  Ÿ  ˜  ˆ

    Typiquement, on doit obtenir un affichage comme, par exemple :

    >>>>> Unicode to UTF-8 Program <<<<<
    Type 0 to EXIT or
    type an hexadecimal Unicode point: U+41
    UTF-8 code = 41 (A)
    
    type an hexadecimal Unicode point: U+E9
    UTF-8 code = C3 A9 (Ã ©)
    
    type an hexadecimal Unicode point: U+20BF
    UTF-8 code = E2 82 BF (â ‚ ¿)
    
    

    En fin d'exécution (lorsque l'utilisateur décide d'arrêter, via la saisie du code 0), le programme devra afficher la phrase titre du livre en vignette ci‑dessus, avec l'erreur de transcodage :

    martine écrit en UTF-8
    

    • Le point de code Unicode saisi par l'utilisateur sera enregistré dans une variable déclarée unicodePoint de type uint32_t.
    • Le ou les octets du code UTF‑8 à déterminer seront mémorisés dans une variable de type tableau de uint8_t nommée utf8code.
    • Pour faciliter le codage des opérations sur les bits de ces octets, on pourra copier‑coller en début de programme le code source des pseudo‑fonctions bitRead, bitSet, bitClear et bitWrite issu du fichier d'en‑tête de bibliothèque Arduino.h G (cf. chap. C3‑III .
  9. Simulation de la fonction de transfert du CAN d'une carte Arduino Uno
  10. En théorie, le convertisseur analogique numérique (CAN) du microcontrôleur embarqué sur une carte Arduino Uno opère une conversion linéaire centrée unipolaire. À pleine échelle, sa fonction de transfert admet donc une courbe représentative dite « en escalier », avec des marches de longueur constante sauf la première et la dernière (cf. la figure ci‑contre et le chap. C3‑VII  pour plus de détails).

    On souhaite coder un programme de simulation de cette fonction de transfert pour pouvoir ensuite tester diverses fonctions de mise à l'échelle (par exemple, celle utilisée pour un bargraphe).

    On déclarera au début de la fonction main des constantes mémorisant les principales caractéristiques du CAN :

    • MAX_VOLTAGE pour la valeur maximale sur l'échelle de la tension entrée (5 V) ;
    • MAX_NUMBER pour la valeur maximale de l'excursion du nombre en sortie (1023) ;
    • QUANTUM pour le quantum de conversion, défini en fonction des deux constantes précédentes (cf. cours).
    1. Coder un programme qui, en boucle, calcule la valeur du nombre en sortie Ns théoriquement rendu par la fonction de transfert du CAN pour une valeur de la tension d'entrée ve saisie par l'utilisateur. On prendra la valeur 0 comme condition de fin d'itération de la boucle.
    2. Tester le bon fonctionnement de ce programme en saisissant les valeurs de ve dans le tableau ci‑dessous :
      ve 5 4.990 2.5 1 0.0025 0.0024 0
      Ns 1023 1022 512 205 1 0 0
    3. Coder un programme qui détermine les valeurs de la tension d'entrée aux seuils des marches et la longueur des marches de la courbe en escalier du CAN. Pour cela :
      • coder une boucle for dont la variable d'incrémentation simule une tension d'entrée ve croissante par pas de 0,0001 V sur l'intervalle 0 – 5 V ;
      • dans cette boucle, calculer le nombre en sortie Ns et le comparer avec sa valeur précédente ; en cas de différence (détection d'un seuil), calculer le nombre de quanta que représente la tension d'entrée (ce nombre permet très facilement de déduire la longueur de la marche précédent le seuil).
      Pour alléger l'affichage, on ne retiendra que les 3 premières et les 3 dernières marches, conformément à la capture d'écran ci‑dessous :
      >>>>> Arduino Uno ADC transfer function simulation <<<<<
        Number  |   Voltage   |   Quanta  
            1   |   0.0025    |      0.5 
            2   |   0.0074    |      1.5 
            3   |   0.0123    |      2.5 
         1021   |   4.9830    |   1020.5 
         1022   |   4.9878    |   1021.5 
         1023   |   4.9927    |   1022.5 
      
    4. Dans le contexte d'implémentation d'un bargraphe (cf. TP C3‑2 ), on souhaite que les seuils d'allumage des led successives sont conformes aux valeurs numériques du nombre en sortie Ns inscrites en noir sur la figure ci‑contre.
    5. En reprenant une partie du programme précédent, dans la boucle for d'incrémentation de la tension d'entrée ve :
      • calculer à partir de Ns le nombre courant de led à allumer et mémoriser sa valeur dans une variable déclarée currentLitLedNumber ;
      • on pourra commencer par utiliser la fonction map (cf. chap. C2‑IV ) en récupérant son code source dans le fichier WMath.cpp G ;
        si le résultat n'est pas conforme aux spécifications ci‑dessus, on recherchera une autre solution en s'inspirant de la fonction de transfert du CAN de la carte Arduino ;
      • comparer la valeur de currentLitLedNumber avec sa valeur précédente (en utilisant une variable déclarée previousLitLedNumber) pour détecter les seuils d'allumage des led du bargraphe.
      On produira une sortie standard conforme à la capture d'écran ci‑dessous :
      >>>>> Bargraph transfer function simulation <<<<<
        ADC Number  |   LED  
             64     |    1 
            192     |    2 
            320     |    3 
            448     |    4 
            576     |    5 
            704     |    6 
            832     |    7 
            960     |    8