NSI, Première • Exercices corrigés à Montréal

Exercices corrigés : représentation des données, réels et texte (NSI Première)

Voici dix exercices corrigés de NSI sur la représentation des nombres réels et du texte, au niveau de la classe de Première du programme français, tel qu'il est suivi au Lycée Marie de France et au Collège Stanislas à Montréal.

Le fil de la série : une machine ne manipule que des suites finies de bits, alors que les réels et les alphabets du monde sont infinis. Toute représentation est donc un COMPROMIS, et chaque bug de ce chapitre vient d'un programmeur qui a oublié lequel.

Deux pièges reviennent d'un exercice à l'autre : on ne teste jamais l'égalité de deux flottants ni ne pilote une boucle avec, et le nombre de caractères d'une chaîne n'est pas sa taille en octets.

Série autocorrigée Tape tes réponses sous chaque question : la page te dit juste ou faux avant d'ouvrir la correction. Avec un compte, chaque bonne réponse du premier coup rapporte des points.

Ce chapitre fait partie de NSI en Première
Avant de commencer Fiche de révision : les pièges et la méthode de ce chapitre

Avant ce chapitre

Ces notions sont supposées acquises ici. Si le premier exercice résiste, le blocage vient presque toujours de l'une d'elles, pas du chapitre lui-même.

Remonter plus loin : la chaîne complète (4 chapitres) ↓

Le chemin de remédiation, du plus ancien au plus proche. Un élève qui reprend ce chapitre de zéro le reprend dans cet ordre.

  1. 1Algorithmique et PythonSeconde, Mathématiques
  2. 2Python : types, contrôle, fonctions et tableaux
  3. 3Algorithmique et ScratchQuatrième, Mathématiques
  4. 4Algorithmique et ScratchTroisième, Mathématiques

Rappel de cours

  • En binaire, les positions à droite de la virgule pèsent 12\dfrac{1}{2}, 14\dfrac{1}{4}, 18\dfrac{1}{8}, etc.
  • Un nombre décimal s'écrit exactement en binaire si et seulement si son dénominateur réduit est une puissance de 2. Ce n'est pas le cas de 0,10{,}1.
  • Flottant IEEE 754 double précision : 6464 bits, dont 11 de signe, 1111 d'exposant et 5252 de mantisse, soit environ 1616 chiffres significatifs.
  • Les flottants ne sont pas répartis uniformément : l'écart entre deux voisins double à chaque puissance de 2.
  • 0,1+0,20{,}1+0{,}2 ne vaut pas 0,30{,}3. On ne teste jamais l'égalité de deux flottants, on compare à une tolérance près.
  • Absorption : ajouter un nombre trop petit à un grand ne change rien du tout au résultat.
  • Une variable de boucle doit être un ENTIER, jamais un flottant.
  • ASCII : 77 bits, 128128 caractères. AA vaut 6565, aa vaut 9797, le chiffre 00 vaut 4848, l'espace vaut 3232.
  • Unicode est un répertoire de points de code ; UTF-8 est un encodage à longueur variable, de 11 à 44 octets, compatible ASCII.
  • Nombre de caractères et nombre d'octets sont deux grandeurs distinctes : elles ne coïncident que pour un texte purement ASCII.

Partie A : Les bases (/50)

Exercice 1 : Écrire un nombre à virgule en binaire

En binaire, les chiffres situés à droite de la virgule ont pour poids 21=122^{-1}=\dfrac{1}{2}, 22=142^{-2}=\dfrac{1}{4}, 23=182^{-3}=\dfrac{1}{8}, et ainsi de suite, exactement comme les dixièmes et les centièmes en décimal.

L'écriture ci-dessous se lit donc 101,1012101{,}101_{2}.

14021111/201/411/8partie entièrepartie fractionnairevirgule
  • a) Convertissez 101,1012101{,}101_{2} en décimal, en détaillant la somme des poids.
  • b) Convertissez 0,6250{,}625 en binaire par la méthode des multiplications successives par 2.
  • c) Appliquez la même méthode à 0,10{,}1. Que constatez-vous au bout de quelques étapes ?
  • d) Quels sont les nombres décimaux qui s'écrivent EXACTEMENT en binaire avec un nombre fini de chiffres ? Donnez le critère.
  • e) Le nombre 13\dfrac{1}{3} n'a pas d'écriture décimale finie. Est-ce un défaut du système décimal ou une propriété du nombre ? Reliez au cas de 0,10{,}1 en binaire.

Tape tes réponses, la page te dit juste ou faux 0/7

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) 101,1012=5,625101{,}101_{2} = 5{,}625
  • b) 0,625=0,10120{,}625 = 0{,}101_{2}
  • c) 0,1=0,0001120{,}1 = 0{,}0\overline{0011}_{2}, infini
  • d) Dénominateur puissance de 2
  • e) Propriété du couple nombre et base

a) 101,1012=4+0+1+12+0+18=5+0,5+0,125=5,625101{,}101_{2}=4+0+1+\dfrac{1}{2}+0+\dfrac{1}{8}=5+0{,}5+0{,}125=5{,}625. On additionne simplement les poids des positions où le bit vaut 1.

b) 0,625×2=1,250{,}625\times 2=1{,}25, on note 11 et il reste 0,250{,}25. 0,25×2=0,50{,}25\times 2=0{,}5, on note 00 et il reste 0,50{,}5. 0,5×2=1,00{,}5\times 2=1{,}0, on note 11 et il reste 00. Le processus s'arrête : 0,625=0,10120{,}625=0{,}101_{2}.

c) 0,1×2=0,20{,}1\times 2=0{,}2, on note 00. 0,2×2=0,40{,}2\times 2=0{,}4, on note 00. 0,4×2=0,80{,}4\times 2=0{,}8, on note 00. 0,8×2=1,60{,}8\times 2=1{,}6, on note 11, reste 0,60{,}6. 0,6×2=1,20{,}6\times 2=1{,}2, on note 11, reste 0,20{,}2. On retrouve 0,20{,}2, déjà rencontré : le processus BOUCLE. L'écriture est 0,0001120{,}0\overline{0011}_{2}, périodique et infinie.

d) Ce sont exactement les nombres dont le dénominateur, une fois la fraction réduite, est une PUISSANCE DE 2. C'est le cas de 0,625=580{,}625=\dfrac{5}{8}, de 0,50{,}5 ou de 0,750{,}75 ; ce n'est pas le cas de 0,1=1100{,}1=\dfrac{1}{10}, dont le dénominateur contient un facteur 5.

e) C'est une propriété du COUPLE nombre-base, pas un défaut d'un système. En base 10, une fraction s'écrit exactement si son dénominateur ne contient que des facteurs 2 et 5 ; en base 2, seulement des facteurs 2. Ainsi 13\dfrac{1}{3} échoue dans les deux bases, tandis que 110\dfrac{1}{10} passe en décimal et échoue en binaire. C'est de là que viennent toutes les surprises numériques des langages de programmation.

Exercice 2 : La représentation en virgule flottante

Un ordinateur ne stocke pas les réels avec un nombre fixe de décimales, mais sous la forme ±m×2e\pm m\times 2^{e} : un signe, une mantisse mm et un exposant ee. C'est la norme IEEE 754.

En double précision, un nombre occupe 6464 bits : 11 pour le signe, 1111 pour l'exposant et 5252 pour la mantisse.

sexposantmantisse1 bit11 bits52 bitsun flottant double précision : 64 bits
  • a) Combien de valeurs différentes le champ d'exposant peut-il coder ? Combien la mantisse ?
  • b) Combien de nombres flottants différents une machine peut-elle représenter au total, à l'ordre de grandeur près ? Comparez à l'infinité des réels.
  • c) Le format simple précision utilise 3232 bits, dont 2323 de mantisse. Combien de chiffres décimaux significatifs cela représente-t-il environ, sachant que 2238,4×1062^{23}\approx 8{,}4\times 10^{6} ?
  • d) Pourquoi les flottants ne sont-ils PAS répartis uniformément sur la droite des réels ? Illustrez avec l'intervalle [1;2][1;2] comparé à [2;4][2;4].
  • e) Que se passe-t-il quand un calcul produit un nombre plus grand que le plus grand flottant représentable ? Et plus petit que le plus petit non nul ?

Tape tes réponses, la page te dit juste ou faux 0/8

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) 211=2 0482^{11} = 2\ 048 ; 2524,5×10152^{52} \approx 4{,}5 \times 10^{15}
  • b) 2641,8×10192^{64} \approx 1{,}8 \times 10^{19} : fini
  • c) Environ 7 chiffres significatifs
  • d) Autant de flottants, écarts doublés
  • e) inf par le haut, 0 par le bas

a) L'exposant sur 1111 bits code 211=20482^{11}=2\,048 valeurs. La mantisse sur 5252 bits en code 2524,5×10152^{52}\approx 4{,}5\times 10^{15}.

b) Le total est de l'ordre de 2641,8×10192^{64}\approx 1{,}8\times 10^{19} valeurs, un peu moins en réalité car certaines combinaisons sont réservées. C'est un nombre FINI, alors que les réels sont en quantité infinie et même non dénombrable : la quasi-totalité des réels n'est donc pas représentable, et tout calcul flottant est un calcul approché.

c) La mantisse distingue environ 8,4×1068{,}4\times 10^{6} valeurs par octave, ce qui correspond à un peu moins de 77 chiffres décimaux, puisque 107=1000000010^{7}=10\,000\,000 est du même ordre. On retient donc environ 77 chiffres significatifs en simple précision, et environ 1616 en double précision.

d) Parce que l'écriture est m×2em\times 2^{e} : à mantisse fixée, changer d'exposant multiplie l'ÉCART entre deux flottants voisins. Sur [1;2][1;2], l'exposant vaut 00 et il y a 2522^{52} valeurs ; sur [2;4][2;4], deux fois plus long, l'exposant vaut 11 et il y a exactement le MÊME nombre de valeurs, donc des écarts deux fois plus grands. Les flottants sont serrés près de zéro et de plus en plus espacés en s'éloignant.

e) Trop grand : c'est le DÉPASSEMENT par le haut, et le résultat devient inf\mathrm{inf}, l'infini flottant, qui contamine ensuite tous les calculs. Trop petit : c'est le dépassement par le bas, et le résultat est arrondi à 00, ce qui est plus insidieux car aucune alerte n'est levée et une division ultérieure produira une erreur inexpliquée.

Exercice 3 : Les pièges des flottants

Puisque 0,10{,}1 n'est pas représentable exactement en binaire, la machine stocke la valeur représentable la plus proche. Les erreurs qui en résultent sont minuscules, mais elles se propagent.

En Python, 0,1+0,20{,}1+0{,}2 affiche 0,300000000000000040{,}30000000000000004.

02468les flottants représentablesdeux fois moins serrés à chaque puissance de 2
  • a) Expliquez d'où vient ce chiffre 44 à la dix-septième décimale.
  • b) Que renvoie l'expression 0,1+0,2==0,30{,}1+0{,}2==0{,}3 ? Pourquoi ne faut-il jamais tester l'égalité de deux flottants ?
  • c) Écrivez le test correct à utiliser à la place, avec une tolérance absolue de 10910^{-9}.
  • d) On ajoute 1,01{,}0 et 102010^{-20}. Que vaut le résultat en double précision ? Comment s'appelle ce phénomène ?
  • e) On additionne un million de fois le nombre 0,10{,}1. Le résultat vaut-il exactement 100000100\,000 ? Dans quel sens l'erreur se cumule-t-elle ?

Tape tes réponses, la page te dit juste ou faux 0/6

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) Arrondis de 0,1 et 0,2 cumulés
  • b) False
  • c) abs(a - b) < 1e-9
  • d) 1,0 : absorption
  • e) Erreurs cumulées dans le même sens

a) Ni 0,10{,}1 ni 0,20{,}2 ne sont représentables exactement : la machine utilise pour chacun le flottant le plus proche, très légèrement différent. La somme de ces deux approximations n'est pas non plus représentable, et le flottant le plus proche de cette somme n'est pas celui qui représente 0,30{,}3. L'écart apparaît à la limite des 1616 chiffres significatifs de la double précision, c'est-à-dire à la dix-septième décimale.

b) Elle renvoie False\mathrm{False}. Tester l'égalité de deux flottants revient à exiger que deux chaînes d'arrondis distinctes aient produit exactement le même motif de 6464 bits, ce qui n'a aucune raison d'arriver. Le test réussit parfois, échoue parfois, et c'est justement ce caractère imprévisible qui en fait un bug redoutable.

c) On écrit abs(ab)<109\mathrm{abs}(a-b)<10^{-9} plutôt que a==ba==b. En pratique on préfère une tolérance RELATIVE, abs(ab)tol×max(abs(a),abs(b))\mathrm{abs}(a-b)\le tol\times \max(\mathrm{abs}(a),\mathrm{abs}(b)), car une tolérance absolue de 10910^{-9} n'a aucun sens pour des nombres de l'ordre de 101510^{15}.

d) Le résultat vaut exactement 1,01{,}0. L'écart relatif entre les deux nombres, 102010^{-20}, est très inférieur à la précision relative de la double précision, environ 101610^{-16} : le petit nombre disparaît entièrement dans l'arrondi. Ce phénomène s'appelle l'ABSORPTION.

e) Non, le résultat n'est pas exactement 100000100\,000. Chaque addition introduit une erreur d'arrondi minuscule, et comme l'approximation de 0,10{,}1 est toujours du même côté, les erreurs se cumulent dans le MÊME sens au lieu de se compenser. L'écart final se lit sur les derniers chiffres significatifs, et c'est exactement pour cela qu'un logiciel de comptabilité ne stocke jamais des euros en flottants, mais des centimes en entiers.

Exercice 4 : Le code ASCII

Le code ASCII associe à chaque caractère un nombre entier compris entre 00 et 127127, codé sur 77 bits. Il couvre les lettres non accentuées, les chiffres, la ponctuation et quelques caractères de commande.

Repères à connaître : le caractère AA vaut 6565, le caractère aa vaut 9797, le chiffre 00 vaut 4848 et l'espace vaut 3232.

  • a) Donnez le code ASCII des caractères CC, cc et 55, en justifiant chaque calcul.
  • b) Quelle opération arithmétique transforme une majuscule en la minuscule correspondante ? Justifiez avec les repères donnés.
  • c) Écrivez 6565 en binaire sur 88 bits, puis en hexadécimal.
  • d) Combien de caractères le code ASCII peut-il représenter au total ? Pourquoi cela s'est-il révélé insuffisant hors du monde anglophone ?
  • e) Un fichier contient le mot NSI\mathrm{NSI} suivi d'un retour à la ligne, dont le code vaut 1010. Donnez la suite des quatre codes, puis la taille du fichier en octets.

Tape tes réponses, la page te dit juste ou faux 0/11

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) 67, 99, 53
  • b) Ajouter 32=2532 = 2^{5}
  • c) 010000012=0x4101000001_{2} = \mathrm{0x41}
  • d) 128 caractères
  • e) 78, 83, 73, 10 : 4 octets

a) Les lettres se suivent dans l'ordre alphabétique : CC est la troisième lettre, donc 65+2=6765+2=67. De même cc vaut 97+2=9997+2=99. Le chiffre 55 vaut 48+5=5348+5=53. On ne mémorise que les points de départ, jamais la table entière.

b) Il suffit d'AJOUTER 3232, puisque 9765=3297-65=32. L'écart est le même pour toutes les lettres, ce qui n'est pas un hasard : 32=2532=2^{5}, si bien que passer d'une casse à l'autre revient à basculer un seul bit, le sixième.

c) 65=64+1=26+2065=64+1=2^{6}+2^{0}, donc 65=01000001265=01000001_{2}. En hexadécimal, on regroupe par quartets : 01000100 vaut 44 et 00010001 vaut 11, d'où 65=411665=41_{16}, noté 0x41\mathrm{0x41}.

d) Sur 77 bits, 27=1282^{7}=128 caractères. C'est insuffisant dès qu'on quitte l'anglais : il n'y a de place ni pour les lettres accentuées du français, ni pour le β\beta allemand, ni pour les alphabets cyrillique, grec, arabe, encore moins pour les milliers d'idéogrammes chinois.

e) NN vaut 65+13=7865+13=78, SS vaut 65+18=8365+18=83, II vaut 65+8=7365+8=73, et le retour à la ligne vaut 1010. La suite est donc 7878, 8383, 7373, 1010. Chaque code tenant sur un octet, le fichier pèse 44 octets.

Exercice 5 : Unicode et UTF-8

Unicode attribue à chaque caractère de toutes les écritures du monde un numéro unique, le POINT DE CODE, noté U+\mathrm{U+} suivi de son écriture hexadécimale. Unicode ne dit pas comment ranger ces numéros en mémoire.

UTF-8 est l'encodage qui répond à cette seconde question : il utilise de 11 à 44 octets par caractère, et les 128128 premiers points de code y sont codés exactement comme en ASCII.

110xxxxx10xxxxxxpremier octet : 110 en têteoctet suivant : 10 en tête
  • a) Le caractère A\mathrm{A} a pour point de code U+0041\mathrm{U+0041}. Combien d'octets occupe-t-il en UTF-8 ? Justifiez.
  • b) Le caractère e\mathrm{e} accentué a pour point de code U+00E9\mathrm{U+00E9}, soit 233233 en décimal. Peut-il tenir sur un octet en UTF-8 ? Combien lui en faut-il ?
  • c) À quoi servent les bits de tête 110110 et 1010 visibles sur la figure ? Quel avantage cela donne-t-il à un programme qui lit un fichier ?
  • d) Un document contient 40004\,000 caractères, dont 36003\,600 non accentués et 400400 accentués, plus un caractère par ligne de retour à la ligne pour 8080 lignes. Calculez sa taille en octets.
  • e) Distinguez précisément, en une phrase chacun, le nombre de CARACTÈRES et le nombre d'OCTETS d'une chaîne. Lequel des deux la fonction len\mathrm{len} renvoie-t-elle en Python 3 ?

Tape tes réponses, la page te dit juste ou faux 0/6

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) A : 1 octet
  • b) é : 2 octets, C3 A9
  • c) 110 et 10 : auto-synchronisation
  • d) 4 4804\ 480 octets
  • e) len compte les caractères

a) Un seul octet. Son point de code, 0x41=65\mathrm{0x41}=65, est inférieur à 128128 : il appartient à la plage compatible ASCII, codée sur un octet dont le premier bit vaut 00. C'est la propriété de compatibilité ascendante qui a fait le succès d'UTF-8, tout fichier ASCII est déjà un fichier UTF-8 valide.

b) Non, il ne peut pas tenir sur un octet UTF-8 : 233233 dépasse 127127, et un octet dont le premier bit vaut 11 est réservé aux séquences multi-octets. Il lui faut DEUX octets, 0xC3\mathrm{0xC3} puis 0xA9\mathrm{0xA9}.

c) Ils indiquent la STRUCTURE de la séquence : un octet commençant par 110110 annonce un caractère sur deux octets, et tout octet commençant par 1010 est un octet de continuation. Un programme peut donc, en lisant un octet quelconque au milieu d'un fichier, savoir immédiatement s'il est au début d'un caractère ou au milieu, et se resynchroniser sans revenir au début. C'est ce qu'on appelle l'auto-synchronisation.

d) Les 36003\,600 caractères non accentués font 36003\,600 octets. Les 400400 accentués font 400×2=800400\times 2=800 octets. Les 8080 retours à la ligne font 8080 octets. Total : 3600+800+80=44803600+800+80=4\,480 octets, soit environ 4,4 kio4{,}4\ \mathrm{kio}, pour 40804\,080 caractères.

e) Le nombre de caractères est le nombre de symboles perçus par un lecteur humain ; le nombre d'octets est la place réellement occupée en mémoire, qui dépend de l'encodage choisi. En Python 3, len\mathrm{len} appliquée à une chaîne renvoie le nombre de CARACTÈRES ; pour obtenir les octets, il faut d'abord encoder la chaîne, par exemple avec la méthode d'encodage en UTF-8.

Partie B : Problèmes et raisonnement (/50)

Exercice 6 : Problème : la boucle qui ne s'arrête jamais

Le programme suivant devrait s'arrêter lorsque xx atteint 1,01{,}0. Il ne s'arrête pas, et la garde placée sur nn le montre.

python
x = 0.0
n = 0
while x != 1.0:
    x = x + 0.1
    n = n + 1
    if n > 20:
        print("la boucle ne s'arrete pas, x vaut", x)
        break
  • a) Expliquez précisément pourquoi la condition d'arrêt n'est jamais vérifiée.
  • b) Combien de tours la boucle fait-elle avant que la garde ne s'active ? Que vaut alors xx, à l'affichage près ?
  • c) Réécrivez la boucle avec une condition d'inégalité. Le problème est-il totalement résolu ?
  • d) Réécrivez-la avec un compteur ENTIER, en calculant xx à partir de ce compteur. Pourquoi cette version est-elle la bonne ?
  • e) Généralisez : donnez la règle à retenir sur l'emploi des flottants comme variable de boucle.

Tape tes réponses, la page te dit juste ou faux 0/6

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) x ne vaut jamais 1.0
  • b) 21 tours, x vaut environ 2,1
  • c) x < 1.0 : dix ou onze tours
  • d) Compteur entier, x = k / 10
  • e) Jamais de flottant pour piloter

a) Parce que 0,10{,}1 n'est pas représentable exactement : la variable xx ne prend jamais la valeur exacte 1,01{,}0. Après dix additions elle vaut 0,99999999999999990{,}9999999999999999, un flottant très proche de 11 mais différent de lui. Le test d'égalité échoue donc, et la boucle continue indéfiniment.

b) La garde s'active quand nn dépasse 2020, donc au vingt et unième tour. La variable xx vaut alors environ 2,12{,}1, avec les dernières décimales polluées par les arrondis cumulés : le programme est déjà passé deux fois au-delà de la cible sans jamais l'atteindre.

c) On écrit while x<1,0\mathrm{while}\ x<1{,}0. La boucle s'arrête bien, mais le problème n'est pas totalement résolu : selon le sens des arrondis, elle peut faire dix ou onze tours, et l'on ne sait pas lequel sans exécuter le programme. Le comportement reste dépendant de la représentation machine.

d) On écrit une boucle sur un entier, par exemple pour kk allant de 00 à 99, et l'on pose x=k/10x=k/10 à l'intérieur. Cette version est la bonne parce que le compteur est un ENTIER, représenté exactement : le nombre de tours est garanti, connu à la lecture, et les erreurs d'arrondi n'affectent plus que la valeur de xx, jamais le déroulement du programme.

e) La règle est simple : on ne pilote JAMAIS une boucle avec un flottant. La variable de contrôle doit être un entier, et les valeurs réelles se calculent à partir de cet entier. Corollaire, on ne teste jamais l'égalité de deux flottants, on compare toujours à une tolérance près.

Exercice 7 : Problème : le fichier illisible

Un fichier texte français, enregistré en UTF-8, est ouvert par un programme qui suppose l'encodage Latin-1. Le mot « résumé » s'affiche « résumé ».

Rappel : en UTF-8, le caractère e\mathrm{e} accentué s'encode sur deux octets, 0xC3\mathrm{0xC3} et 0xA9\mathrm{0xA9}. En Latin-1, chaque octet est interprété comme un caractère unique, et 0xC3\mathrm{0xC3} y désigne le A\mathrm{A} tilde majuscule tandis que 0xA9\mathrm{0xA9} désigne le symbole du copyright.

  • a) Expliquez, octet par octet, pourquoi un seul caractère accentué en produit deux à l'affichage.
  • b) Le mot « résumé » compte 66 caractères. Combien d'octets occupe-t-il en UTF-8 ? Combien de caractères le lecteur Latin-1 croit-il lire ?
  • c) Le fichier fait 44804\,480 octets. Combien de caractères le lecteur Latin-1 annonce-t-il ? Comparez aux 40804\,080 caractères réels.
  • d) L'inverse se produit aussi : un fichier Latin-1 lu comme de l'UTF-8. Que se passe-t-il alors, et pourquoi l'erreur est-elle ici plus visible ?
  • e) Quelles deux précautions un programme doit-il prendre pour éviter durablement ce type de bug ?

Tape tes réponses, la page te dit juste ou faux 0/7

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) Deux octets, deux caractères Latin-1
  • b) 8 octets, 8 caractères lus
  • c) 4 4804\ 480 au lieu de 4 0804\ 080
  • d) Décodage UTF-8 en erreur
  • e) Déclarer l'encodage, UTF-8 partout

a) Le caractère accentué est stocké sur DEUX octets, 0xC3\mathrm{0xC3} et 0xA9\mathrm{0xA9}. Le lecteur Latin-1 ignore la notion de séquence multi-octets : il traite chaque octet isolément et le convertit en un caractère. Il affiche donc le caractère de 0xC3\mathrm{0xC3}, puis celui de 0xA9\mathrm{0xA9}, soit deux symboles là où il n'y avait qu'une lettre.

b) Le mot compte 44 caractères non accentués, à un octet chacun, et 22 caractères accentués, à deux octets chacun : 4+4=84+4=8 octets. Le lecteur Latin-1 croit lire 88 caractères au lieu de 66.

c) Le lecteur Latin-1 annonce autant de caractères que d'octets, soit 44804\,480. C'est 400400 de plus que les 40804\,080 caractères réels, exactement le nombre de caractères accentués du document, chacun ayant été compté deux fois.

d) Le décodage UTF-8 ÉCHOUE, car les octets isolés supérieurs à 127127 ne forment pas des séquences valides : le programme lève une erreur au lieu d'afficher n'importe quoi. L'erreur est donc plus visible, et paradoxalement plus facile à corriger : un plantage franc vaut mieux qu'un texte silencieusement abîmé, que l'on risque de réenregistrer tel quel et de perdre définitivement.

e) Premièrement, TOUJOURS spécifier l'encodage explicitement à l'ouverture d'un fichier, plutôt que de se fier à la valeur par défaut du système, qui diffère d'une machine à l'autre. Deuxièmement, choisir UTF-8 partout, à l'écriture comme à la lecture, et le déclarer dans les en-têtes des formats qui le permettent, comme les pages web.

python
mot = "résumé"

print(len(mot))                      # 6 : nombre de CARACTERES
print(len(mot.encode("utf-8")))      # 8 : nombre d'OCTETS

# ouvrir un fichier en declarant TOUJOURS l'encodage
with open("notes.txt", "r", encoding="utf-8") as f:
    contenu = f.read()

Exercice 8 : Cinq affirmations à corriger

Chacune des cinq affirmations suivantes est FAUSSE. Dites pourquoi et donnez l'énoncé correct.

  • 1) « Un ordinateur en double précision est exact jusqu'à la seizième décimale, donc 0,1+0,20{,}1+0{,}2 vaut exactement 0,30{,}3. »
  • 2) « Unicode est un encodage qui range les caractères en mémoire. »
  • 3) « En UTF-8, chaque caractère occupe deux octets. »
  • 4) « Les flottants sont répartis régulièrement sur la droite des réels. »
  • 5) « En Python 3, la longueur d'une chaîne est égale à la taille en octets du fichier qui la contient. »

Tape tes réponses, la page te dit juste ou faux 0/5

1)
2)
3)
4)
5)
Voir la correction

Réponses

  • 1) Pas de représentation exacte
  • 2) Unicode : répertoire
  • 3) UTF-8 : 1 à 4 octets
  • 4) Écarts croissants
  • 5) Caractères contre octets

1) FAUX. La double précision garantit environ 1616 chiffres SIGNIFICATIFS, ce qui n'est pas la même chose qu'un nombre de décimales exactes, et surtout 0,10{,}1 et 0,20{,}2 ne sont pas représentables exactement en binaire. Leur somme vaut 0,300000000000000040{,}30000000000000004 et diffère de 0,30{,}3.

2) FAUX. Unicode est un RÉPERTOIRE : il attribue un numéro, le point de code, à chaque caractère, sans dire comment le stocker. C'est l'encodage, UTF-8, UTF-16 ou UTF-32, qui décide de la mise en octets.

3) FAUX. UTF-8 est un codage à longueur VARIABLE, de 11 à 44 octets selon le caractère. Les 128128 premiers points de code, ceux d'ASCII, tiennent sur un seul octet, les lettres accentuées sur deux, la plupart des idéogrammes sur trois.

4) FAUX. L'écart entre deux flottants consécutifs DOUBLE à chaque puissance de deux : ils sont très serrés près de zéro et de plus en plus espacés vers les grandes valeurs. C'est la conséquence directe de l'écriture m×2em\times 2^{e}.

5) FAUX. La fonction de longueur renvoie un nombre de CARACTÈRES, alors que la taille du fichier se compte en OCTETS. Les deux ne coïncident que si tous les caractères sont codés sur un octet, c'est-à-dire pour un texte purement ASCII, sans le moindre accent.

Exercice 9 : Problème : comparer correctement deux flottants

Puisque l'égalité stricte est inutilisable, on écrit une fonction de comparaison approchée. Le code ci-dessous en propose une, à tolérance relative.

python
def presque_egaux(a, b, tol=1e-9):
    """Vrai si a et b sont egaux a la tolerance relative pres."""
    if a == b:                       # cas exact, et cas des zeros
        return True
    echelle = max(abs(a), abs(b))
    return abs(a - b) <= tol * echelle

print(0.1 + 0.2 == 0.3)              # False
print(presque_egaux(0.1 + 0.2, 0.3)) # True
  • a) Expliquez le rôle de la première ligne du corps de la fonction, celle qui teste l'égalité stricte.
  • b) Pourquoi utiliser une tolérance RELATIVE plutôt qu'absolue ? Donnez un cas où une tolérance absolue de 10910^{-9} est absurde.
  • c) Testez mentalement la fonction sur a=0a=0 et b=1020b=10^{-20}. Que renvoie-t-elle ? Est-ce le comportement souhaité ?
  • d) Proposez une amélioration qui règle ce cas, en combinant une tolérance absolue et une tolérance relative.
  • e) Cette fonction est-elle transitive, c'est-à-dire vaut-il que si aa est proche de bb et bb proche de cc, alors aa est proche de cc ? Justifiez et concluez sur l'usage prudent de ce genre de test.

Tape tes réponses, la page te dit juste ou faux 0/5

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) Égalité stricte : cas des zéros
  • b) Précision relative des flottants
  • c) False pour 0 et 102010^{-20}
  • d) Plancher absolu et tolérance relative
  • e) Non transitive

a) Elle traite les cas où les deux valeurs sont rigoureusement identiques, ce qui arrive souvent en pratique. Surtout, elle règle le cas particulier de deux zéros : l'échelle valant alors 00, la tolérance relative serait nulle et le test suivant échouerait à reconnaître que 00 est égal à 00.

b) Parce que la précision d'un flottant est elle-même RELATIVE : environ 1616 chiffres significatifs, quel que soit l'ordre de grandeur. Une tolérance absolue de 10910^{-9} est absurde pour deux nombres de l'ordre de 101510^{15}, où l'écart entre deux flottants consécutifs dépasse déjà 11 : le test échouerait toujours, même pour des valeurs voisines. Elle est tout aussi absurde pour des nombres de l'ordre de 102010^{-20}, où elle déclarerait égaux des nombres différant d'un facteur mille.

c) L'échelle vaut max(0,1020)=1020\max(0, 10^{-20})=10^{-20}, et la tolérance 109×1020=102910^{-9}\times 10^{-20}=10^{-29}. L'écart 102010^{-20} lui est très supérieur, donc la fonction renvoie faux. Ce n'est pas le comportement souhaité : à l'échelle d'un calcul ordinaire, 102010^{-20} est indiscernable de zéro, et il faudrait les déclarer égaux.

d) On ajoute un plancher absolu : renvoyer vrai si abs(ab)max(tolabs, tolrel×max(abs(a),abs(b)))\mathrm{abs}(a-b)\le \max(tol_{abs},\ tol_{rel}\times \max(\mathrm{abs}(a),\mathrm{abs}(b))), avec par exemple tolabs=1012tol_{abs}=10^{-12}. Le plancher prend le relais près de zéro, la tolérance relative gouverne partout ailleurs. C'est exactement la stratégie de la fonction de comparaison approchée de la bibliothèque standard.

e) Non, elle n'est PAS transitive. Avec une tolérance de 0,10{,}1 en absolu, 1,001{,}00 est proche de 1,091{,}09 et 1,091{,}09 est proche de 1,181{,}18, alors que 1,001{,}00 et 1,181{,}18 diffèrent de 0,180{,}18. La proximité approchée n'est donc pas une relation d'équivalence, et l'on ne peut pas s'en servir pour regrouper des valeurs ni pour trier. Elle sert à valider un résultat de calcul, rien de plus.

python
def presque_egaux(a, b, tol_rel=1e-9, tol_abs=1e-12):
    """Vrai si a et b sont egaux a une tolerance pres :
    absolue pres de zero, relative partout ailleurs."""
    return abs(a - b) <= max(tol_abs, tol_rel * max(abs(a), abs(b)))

print(presque_egaux(0.1 + 0.2, 0.3))   # True
print(presque_egaux(0.0, 1e-20))       # True : le plancher absolu joue
print(presque_egaux(1e15, 1e15 + 1))   # True : ecart relatif 1e-15

Exercice 10 : Problème : une histoire des représentations

Les choix de représentation ne sont pas tombés du ciel : chacun répond à un problème de son époque. La frise ci-dessous en donne les jalons principaux.

1837machine analytique de Babbage1936machine de Turing1945architecture de von Neumann1963code ASCII normalisé1985norme IEEE 7541991Unicode1993UTF-8 publié
  • a) Qu'apporte l'architecture de von Neumann, en 1945, quant à la représentation des données ?
  • b) Le code ASCII est normalisé en 1963 sur 77 bits. Pourquoi ce choix, alors que les machines travaillaient déjà par octets de 88 bits ?
  • c) La norme IEEE 754 date de 1985. Que se passait-il avant, et pourquoi une norme était-elle devenue indispensable ?
  • d) Unicode paraît en 1991 et UTF-8 en 1993. Pourquoi deux dates, et pourquoi UTF-8 s'est-il imposé face à UTF-32, qui code tout caractère sur quatre octets ?
  • e) En une phrase de synthèse, dites ce que ces quatre normes ont en commun quant au rôle d'une norme en informatique.

Tape tes réponses, la page te dit juste ou faux 0/5

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) Données et instructions en mémoire
  • b) Huitième bit : parité
  • c) Avant 1985 : un format par constructeur
  • d) UTF-8 : compatible et économique
  • e) Une norme permet l'échange

a) Elle pose que les INSTRUCTIONS et les DONNÉES sont stockées dans la même mémoire, sous la même forme binaire. Une même suite de bits peut donc être lue comme un nombre ou comme une instruction selon le contexte : c'est ce qui rend un ordinateur programmable, et c'est aussi ce qui rend indispensable de savoir toujours quel TYPE on manipule.

b) Parce que le huitième bit servait au CONTRÔLE DE PARITÉ, un mécanisme de détection d'erreur sur les liaisons de l'époque, peu fiables. Les 128128 codes de l'ASCII suffisaient à l'anglais, et le bit restant valait mieux comme sécurité de transmission. C'est ce huitième bit libéré qui accueillera plus tard les extensions nationales, dont Latin-1, avec les incompatibilités que l'on sait.

c) Avant 1985, chaque constructeur avait son propre format de flottants, avec des tailles, des arrondis et des comportements différents devant le zéro et l'infini. Un même programme scientifique donnait des résultats différents d'une machine à l'autre, et parfois faux. La norme est devenue indispensable dès que les programmes ont circulé et que les résultats de calcul ont dû être reproductibles.

d) Deux dates parce que ce sont deux choses différentes : Unicode définit le répertoire de caractères, UTF-8 définit une façon de le mettre en octets. UTF-8 s'est imposé pour deux raisons décisives : il est compatible avec ASCII, donc les milliards de fichiers et de protocoles existants sont restés valides sans modification, et il est économique, un texte occidental n'occupant guère plus de place qu'en ASCII, là où UTF-32 le quadruplerait.

e) Toutes quatre montrent qu'une norme, en informatique, ne sert pas à imposer la meilleure solution technique dans l'absolu, mais à permettre à des machines et à des programmes écrits par des gens différents d'échanger sans se tromper sur le sens des bits.

Partie C : les classiques (/50)

Exercice 11 : La virgule fixe sur un octet

Avant les flottants, et encore aujourd'hui dans les microcontrôleurs, on code souvent un nombre positif en VIRGULE FIXE : sur un octet, les 4 bits de gauche donnent la partie entière et les 4 bits de droite la partie fractionnaire, de poids 12\frac{1}{2}, 14\frac{1}{4}, 18\frac{1}{8} et 116\frac{1}{16}. On note l'octet avec une virgule, par exemple 1011,01101011{,}0110.

  • a) Quelle valeur représente l'octet 1011,01101011{,}0110 ?
  • b) Quelle est la plus grande valeur représentable ? Quel est l'écart entre deux valeurs voisines ?
  • c) On veut coder 6,86{,}8. Donnez l'octet obtenu en tronquant, puis celui obtenu en arrondissant au plus proche, et l'erreur commise dans chaque cas.
  • d) Montrez qu'additionner deux nombres en virgule fixe revient à additionner les deux octets comme des entiers. Calculez 3,25+5,53{,}25 + 5{,}5 de cette façon.
  • e) On multiplie 2,52{,}5 par 1,51{,}5 en multipliant les octets vus comme des entiers, 40 et 24. Par quel nombre faut-il diviser le résultat pour retrouver la bonne valeur ?

Tape tes réponses, la page te dit juste ou faux 0/8

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) 18216=11,375\frac{182}{16} = 11{,}375
  • b) 15,937515{,}9375 ; pas 0,06250{,}0625
  • c) 6,756{,}75 tronqué, 6,81256{,}8125 arrondi
  • d) 52+88=14052 + 88 = 140 : 8,758{,}75
  • e) Diviser par 16 après un produit

a) Partie entière : 10112=8+2+1=111011_{2} = 8 + 2 + 1 = 11. Partie fractionnaire : 01100110 donne 14+18=0,375\frac{1}{4} + \frac{1}{8} = 0{,}375. L'octet représente 11,37511{,}375. Autre lecture, très utile : l'octet vu comme un entier vaut 182182, et 18216=11,375\frac{182}{16} = 11{,}375 ; un nombre en virgule fixe est un entier divisé par 24=162^{4} = 16.

b) La plus grande valeur est 1111,11111111{,}1111, soit 25516=15,9375\frac{255}{16} = 15{,}9375. Deux valeurs voisines diffèrent de 116=0,0625\frac{1}{16} = 0{,}0625 : c'est la RÉSOLUTION, la même partout, contrairement aux flottants dont l'écart grandit avec la valeur.

c) 6,8×16=108,86{,}8 \times 16 = 108{,}8. En tronquant, on garde 108, soit 0110,11000110{,}1100, qui vaut 6,756{,}75 : erreur 0,050{,}05. En arrondissant, on garde 109, soit 0110,11010110{,}1101, qui vaut 6,81256{,}8125 : erreur 0,01250{,}0125. Dans les deux cas 6,86{,}8 n'est pas représentable exactement, pour la même raison que 0,10{,}1 en flottant : son dénominateur n'est pas une puissance de 2.

d) Chaque nombre est un entier divisé par 16, donc a16+b16=a+b16\frac{a}{16} + \frac{b}{16} = \frac{a + b}{16} : on additionne les entiers et la virgule reste au même endroit. 3,253{,}25 correspond à 52 et 5,55{,}5 à 88 ; 52+88=14052 + 88 = 140, soit 14016=8,75\frac{140}{16} = 8{,}75, codé 1000,11001000{,}1100. C'est tout l'intérêt de la virgule fixe : un processeur sans calcul flottant sait le faire.

e) a16×b16=a×b256\frac{a}{16} \times \frac{b}{16} = \frac{a \times b}{256} : le produit des entiers est seize fois trop grand pour être relu en virgule fixe. 40×24=96040 \times 24 = 960, et 96016=60\frac{960}{16} = 60, qui se relit 6016=3,75=2,5×1,5\frac{60}{16} = 3{,}75 = 2{,}5 \times 1{,}5. Il faut donc diviser par 16, c'est-à-dire décaler de 4 bits vers la droite, après chaque multiplication.

Exercice 12 : Coder un flottant sur 32 bits

En simple précision, un flottant occupe 32 bits : 1 bit de signe, 8 bits d'exposant et 23 bits de mantisse. Le nombre s'écrit (1)s×1,m×2E(-1)^{s} \times 1{,}m \times 2^{E}, où mm est la suite des bits de mantisse, et l'exposant est stocké avec un DÉCALAGE de 127 : on range E+127E + 127.

  • a) Écrivez 6,256{,}25 en binaire.
  • b) Mettez ce nombre sous la forme 1,m×2E1{,}m \times 2^{E}. Que vaut EE ? Quels sont les premiers bits de la mantisse ?
  • c) Quel exposant stocké obtient-on ? Écrivez-le sur 8 bits.
  • d) Assemblez les 32 bits de 6,25-6{,}25 et donnez l'écriture hexadécimale.
  • e) Décodez le flottant 0x41200000\mathrm{0x41200000}.

Tape tes réponses, la page te dit juste ou faux 0/7

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) 6,25=110,0126{,}25 = 110{,}01_{2}
  • b) 1,1001×221{,}1001 \times 2^{2}
  • c) 129=100000012129 = 10000001_{2}
  • d) 6,25-6{,}25 : 0xC0C80000\mathrm{0xC0C80000}
  • e) 0x41200000=10,0\mathrm{0x41200000} = 10{,}0

a) 6=11026 = 110_{2} et 0,25=14=0,0120{,}25 = \frac{1}{4} = 0{,}01_{2}, donc 6,25=110,0126{,}25 = 110{,}01_{2}.

b) On déplace la virgule de deux rangs vers la gauche : 110,012=1,10012×22110{,}01_{2} = 1{,}1001_{2} \times 2^{2}. Donc E=2E = 2, et la mantisse commence par 10011001 ; le 1 placé avant la virgule n'est PAS stocké, puisqu'il vaut toujours 1.

c) Exposant stocké : 2+127=129=128+1=1000000122 + 127 = 129 = 128 + 1 = 10000001_{2}. Le décalage évite de coder un signe pour l'exposant : les exposants négatifs correspondent aux valeurs stockées inférieures à 127.

d) Signe 1 pour un nombre négatif, exposant 1000000110000001, mantisse 10011001 suivie de 19 zéros. Les 32 bits s'écrivent 110000001100100001\,10000001\,1001000\ldots0, regroupés par quatre : 1100 0000 1100 1000 0000 0000 0000 00001100\ 0000\ 1100\ 1000\ 0000\ 0000\ 0000\ 0000, soit 0xC0C80000\mathrm{0xC0C80000}. Le piège est d'oublier le bit de signe devant l'exposant, ce qui décale tous les regroupements.

e) 0x41200000\mathrm{0x41200000} s'écrit 0100 0001 0010 0000 0100\ 0001\ 0010\ 0000\ \ldots Signe 0 : positif. Exposant 100000102=13010000010_{2} = 130, donc E=130127=3E = 130 - 127 = 3. Mantisse 0101 suivie de zéros : 1,012=1,251{,}01_{2} = 1{,}25. Valeur : 1,25×23=101{,}25 \times 2^{3} = 10. Le flottant vaut 10,010{,}0.

Exercice 13 : ord, chr et les fins de ligne

En Python, ord(c) renvoie le point de code du caractère c, et chr(n) le caractère de point de code n. Un retour à la ligne s'écrit '\n', de code 10 ; un retour chariot s'écrit '\r', de code 13. Les fichiers texte de Linux et de macOS terminent chaque ligne par '\n', ceux de Windows par la paire '\r\n'.

  • a) Donnez ord('A'), chr(97), ord('é') et chr(8364).
  • b) Écrivez une fonction majuscule(c) qui transforme une lettre minuscule non accentuée en majuscule avec ord et chr, sans utiliser upper.
  • c) Que renvoie chr(ord('Z') + 1) ? Pourquoi un chiffrement par décalage écrit naïvement produit-il des symboles au lieu de lettres ?
  • d) Un fichier contient trois lignes, abc, de et f, chacune terminée par une fin de ligne, en ASCII. Quelle est sa taille en octets sous Linux ? sous Windows ?
  • e) Que vaut len('abc\nde\nf\n') ? Un programme compte les lignes d'un fichier Windows en cherchant '\n' : trouve-t-il le bon nombre ? Que risque-t-il en comparant la ligne lue à 'abc' ?

Tape tes réponses, la page te dit juste ou faux 0/8

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) 65, 'a', 233, '€'
  • b) chr(ord(c) - 32)
  • c) '[' : modulo 26 nécessaire
  • d) 9 octets sous Linux, 12 sous Windows
  • e) 9 caractères ; 'abc\r' différent de 'abc'

a) ord('A') vaut 65 et chr(97) vaut 'a', comme en ASCII. ord('é') vaut 233, soit U+00E9\mathrm{U+00E9} : ord renvoie le point de code Unicode, pas un octet de l'encodage. chr(8364) vaut '€', de point de code U+20AC\mathrm{U+20AC}.

b) Les minuscules sont 32 rangs après les majuscules : return chr(ord(c) - 32). On peut aussi écrire chr(ord(c) - ord('a') + ord('A')), qui ne demande de retenir aucun nombre.

c) ord('Z') vaut 90, donc chr(91) vaut '[' : après Z, la table ne recommence pas à A, elle continue avec des symboles. Un décalage doit donc ramener la position dans l'alphabet par un modulo 26 : chr((ord(c) - 65 + k) % 26 + 65).

d) Les caractères valent 3 + 2 + 1 = 6 octets. Sous Linux, trois fins de ligne d'un octet : 9 octets. Sous Windows, trois paires '\r\n' de deux octets : 12 octets. Le même texte n'a pas la même taille selon le système, sans qu'aucun caractère visible ne diffère.

e) len('abc\nde\nf\n') vaut 9 : '\n' est UN caractère, même s'il s'écrit avec deux symboles dans le code source. En cherchant '\n', le programme trouve bien trois lignes, mais la première lue vaut 'abc\r' : elle contient un retour chariot invisible, et la comparaison avec 'abc' échoue. D'où l'habitude de retirer les fins de ligne avec strip, ou d'ouvrir les fichiers en mode texte, qui convertit '\r\n' en '\n'.

python
def majuscule(c):
    return chr(ord(c) - ord('a') + ord('A'))

def decaler(c, k):          # lettre majuscule, retour a A apres Z
    return chr((ord(c) - 65 + k) % 26 + 65)

print(chr(ord('Z') + 1))    # '['
print(decaler('Z', 1))      # 'A'
print(len('abc\nde\nf\n'))  # 9

Exercice 14 : Problème : encoder à la main en UTF-8

UTF-8 range le point de code d'un caractère dans des « cases » de bits libres, notées x, selon le nombre d'octets nécessaires :

1 octet : 0xxxxxxx. 2 octets : 110xxxxx 10xxxxxx. 3 octets : 1110xxxx 10xxxxxx 10xxxxxx. 4 octets : 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx. On remplit les x avec les bits du point de code, alignés à droite.

  • a) Combien de bits libres offre chaque format ? Déduisez-en le plus grand point de code codable sur 1, 2 et 3 octets, en hexadécimal.
  • b) Le symbole de l'euro a pour point de code U+20AC\mathrm{U+20AC}. Combien d'octets lui faut-il ? Encodez-le et donnez les octets en hexadécimal.
  • c) Décodez la séquence d'octets E4 B8 AD\mathrm{E4\ B8\ AD} et donnez le point de code obtenu.
  • d) La chaîne « Prix : 20 € » compte combien de caractères ? Combien d'octets en UTF-8 ?
  • e) Un programme coupe un texte UTF-8 à exactement 12 octets pour l'afficher sur un petit écran. Que se passe-t-il si la coupure tombe au milieu du symbole euro ? Comment un programme peut-il reculer jusqu'au début du caractère ?

Tape tes réponses, la page te dit juste ou faux 0/9

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) 7, 11, 16 et 21 bits libres
  • b) € : E2 82 AC
  • c) E4 B8 AD : U+4E2D
  • d) 11 caractères, 13 octets
  • e) Reculer sur les octets 10xxxxxx

a) Sur 1 octet, 7 bits libres : jusqu'à 271=127=0x7F2^{7} - 1 = 127 = \mathrm{0x7F}. Sur 2 octets, 5+6=115 + 6 = 11 bits : jusqu'à 2111=2 047=0x7FF2^{11} - 1 = 2\ 047 = \mathrm{0x7FF}. Sur 3 octets, 4+6+6=164 + 6 + 6 = 16 bits : jusqu'à 0xFFFF\mathrm{0xFFFF}. Sur 4 octets, 3+6+6+6=213 + 6 + 6 + 6 = 21 bits.

b) 0x20AC\mathrm{0x20AC} dépasse 0x7FF\mathrm{0x7FF} mais pas 0xFFFF\mathrm{0xFFFF} : il faut 3 octets. En binaire sur 16 bits : 0010 0000 1010 11000010\ 0000\ 1010\ 1100. On répartit 4, 6 et 6 bits : 00100010, puis 000010000010, puis 101100101100. Les octets sont 111000101110\,0010, 1000001010\,000010 et 1010110010\,101100, soit E2 82 AC\mathrm{E2\ 82\ AC}.

c) E4=11100100\mathrm{E4} = 1110\,0100 annonce 3 octets et porte 01000100. B8=10111000\mathrm{B8} = 10\,111000 porte 111000111000. AD=10101101\mathrm{AD} = 10\,101101 porte 101101101101. En recollant : 0100 111000 101101=0100 1110 0010 11012=0x4E2D0100\ 111000\ 101101 = 0100\ 1110\ 0010\ 1101_{2} = \mathrm{0x4E2D}. C'est le point de code U+4E2D\mathrm{U+4E2D}, un idéogramme chinois qui signifie « milieu ».

d) La chaîne compte 11 caractères : P, r, i, x, espace, deux-points, espace, 2, 0, espace et €. Les dix premiers sont en ASCII, un octet chacun ; l'euro en prend trois. Total : 10+3=1310 + 3 = 13 octets.

e) Les 12 premiers octets contiennent les 10 caractères ASCII et les 2 premiers octets de l'euro : la séquence est incomplète, et l'affichage montre un symbole de remplacement ou déclenche une erreur de décodage. Pour reculer au début d'un caractère, il suffit de regarder les octets à partir de la coupure : tout octet de la forme 10xxxxxx10xxxxxx est une continuation ; on recule tant qu'on en rencontre, puis on coupe avant l'octet de tête trouvé. Ici on coupe à 10 octets.

Exercice 15 : Problème : l'ordre des additions compte

En mathématiques, l'addition est associative : (a+b)+c=a+(b+c)(a + b) + c = a + (b + c). Pour les flottants de la double précision, ce n'est plus vrai. On rappelle que les entiers sont représentés exactement jusqu'à 2539,0×10152^{53} \approx 9{,}0 \times 10^{15}, et qu'au-delà l'écart entre deux flottants voisins atteint 2, puis 4, et ainsi de suite.

  • a) Que valent (1e16 + 1.0) - 1e16 et (1e16 - 1e16) + 1.0 en Python ? Expliquez la différence.
  • b) Quel est l'écart entre deux flottants voisins autour de 101610^{16} ? Et autour de 1, sachant qu'il vaut 2522^{-52} ?
  • c) On additionne dix fois 0.1 dans une boucle en partant de 0.0. Le résultat est-il égal à 1.0 ?
  • d) On calcule S=1+12++1106S = 1 + \frac{1}{2} + \dots + \frac{1}{10^{6}} de deux façons : en commençant par 1, ou en commençant par 1106\frac{1}{10^{6}}. Les deux résultats diffèrent dans les dernières décimales. Lequel est le plus précis, et pourquoi ?
  • e) La bibliothèque standard propose math.fsum, qui additionne une liste en compensant les arrondis. Que vaut math.fsum([0.1] * 10) == 1.0 ? Quand faut-il plutôt travailler en entiers ?

Tape tes réponses, la page te dit juste ou faux 0/7

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) 0.0 contre 1.0 : non associative
  • b) 2 près de 101610^{16}, 2,2×10162{,}2 \times 10^{-16} près de 1
  • c) 0.9999999999999999
  • d) Petits termes d'abord
  • e) fsum exact ici ; entiers pour l'argent

a) (1e16 + 1.0) - 1e16 vaut 0.0 : 1016+110^{16} + 1 n'est pas représentable, car l'écart entre flottants voisins y vaut 2, et il est arrondi à 101610^{16} ; la soustraction donne 0. (1e16 - 1e16) + 1.0 vaut 1.0 : la soustraction est exacte et le 1 est ajouté à 0. Même opérations, ordre différent, résultats différents : l'addition flottante n'est pas associative.

b) Autour de 101610^{16}, au-delà de 2532^{53}, l'écart vaut 2. Autour de 1, il vaut 2522,2×10162^{-52} \approx 2{,}2 \times 10^{-16}. L'écart est proportionnel à l'ordre de grandeur : c'est pour cela qu'on parle d'environ 16 chiffres significatifs, et non de 16 décimales.

c) Non : la boucle donne 0.9999999999999999, et le test d'égalité avec 1.0 est faux. Les dix petites erreurs d'arrondi se cumulent au lieu de se compenser.

d) Le résultat obtenu en commençant par les PETITS termes est le plus précis. En partant de 1, la somme atteint vite 10 puis 14, et chaque petit terme 1k\frac{1}{k} est ajouté à un grand nombre : il subit l'arrondi à l'échelle de ce grand nombre, et les erreurs s'accumulent sur un million d'additions. En partant des petits, ils s'additionnent d'abord entre eux, à leur propre échelle, et leur somme n'est absorbée qu'une fois devenue significative. On mesure un écart de l'ordre de 101310^{-13} entre les deux ordres, l'ordre croissant des termes étant environ quinze fois plus proche de la valeur exacte.

e) math.fsum([0.1] * 10) == 1.0 vaut True : fsum garde la trace des erreurs d'arrondi et les réinjecte. On travaille plutôt en ENTIERS dès que le résultat doit être exact par nature, comme des montants d'argent comptés en centimes ou des durées comptées en millisecondes : un entier ne subit aucun arrondi, et l'égalité redevient un test fiable.

Chapitre précédent Python : types, contrôle, fonctions et tableaux Chapitre suivant Algorithmique : preuve, terminaison et coût

Ce chapitre resservira dans

Les chapitres qui le réclament en amont, plus tard dans l'année ou dans les années suivantes.

Voir aussi

Vous cherchez un tuteur de NSI en Première à Montréal ?

Contactez-moi pour une première séance. Ce chapitre semble théorique jusqu'au jour où un programme donne un résultat faux sans planter, et c'est alors le seul qui permette de comprendre pourquoi.

Site par Studio Squalli