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

Fiche de révision : représentation des réels et du texte (NSI Première)

Ce chapitre est celui où l'informatique cesse d'être des mathématiques. Un flottant n'est pas un réel : c'est une approximation codée sur 6464 bits, dont le pas grandit avec la valeur. Tant que l'on n'a pas admis cela, les résultats bizarres restent inexplicables.

Vous trouverez ici les huit erreurs qui coûtent des points en devoir et des heures en projet, avec la phrase exacte à écrire pour chacune, l'arbre qui décide comment comparer deux flottants, celui qui compte les octets d'un texte en UTF-8, et une boucle qui ne s'arrête jamais, traitée ligne par ligne.

Le fil du chapitre

Un flottant est une APPROXIMATION dont le pas grandit avec le nombre : on ne teste jamais l'égalité de deux flottants, et un caractère n'est pas un octet.

Ce chapitre fait partie de NSI en Première

Avant ce chapitre

Cette fiche suppose ces notions acquises. Si une méthode ci-dessous reste opaque, c'est presque toujours l'une d'elles qui manque, pas la fiche.

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

L'essentiel

Écrire un nombre à virgule en binaire

  • À droite de la virgule, les positions pèsent 12\dfrac{1}{2}, 14\dfrac{1}{4}, 18\dfrac{1}{8}, 116\dfrac{1}{16}, et ainsi de suite.
  • Ainsi 0,1012=12+18=0,6250{,}101_{2}=\dfrac{1}{2}+\dfrac{1}{8}=0{,}625 en décimal.
  • Un nombre décimal s'écrit EXACTEMENT en binaire si et seulement si son écriture fractionnaire réduite a pour dénominateur une puissance de 22.
  • 0,50{,}5, 0,250{,}25 et 0,750{,}75 sont exacts; 0,10{,}1, 0,20{,}2 et 0,30{,}3 ne le sont pas, car leur dénominateur contient un facteur 55.
  • L'écriture binaire de 0,10{,}1 est périodique et infinie : la machine doit la tronquer, donc l'arrondir.
0,5100000000000000,25010000000000000,100011001100110...chiffres apres la virgule, en binaire0,1 ne se termine jamais
Les deux premières lignes s'arrêtent après un ou deux chiffres : ces nombres sont exacts en binaire. La troisième répète indéfiniment le motif 00110011 : c'est pour cela que 0,10{,}1 doit être arrondi.

La question de l'exactitude ne dépend pas du nombre de bits disponibles : 0,10{,}1 resterait inexact sur mille bits. C'est une propriété arithmétique, pas une limite de mémoire.

Le flottant IEEE 754, et son pas variable

  • Un flottant double précision occupe 6464 bits : 11 bit de SIGNE, 1111 bits d'EXPOSANT et 5252 bits de MANTISSE.
  • Cela donne environ 1515 à 1616 chiffres significatifs décimaux, et pas un de plus.
  • Les flottants ne sont PAS répartis uniformément : il y en a autant entre 11 et 22 qu'entre 22 et 44, ou entre 10241024 et 20482048.
  • Conséquence : l'écart entre deux flottants voisins DOUBLE à chaque puissance de 22 franchie.
  • Au-delà d'environ 9×10159\times 10^{15}, l'écart entre deux flottants consécutifs dépasse 11 : les entiers eux-mêmes ne sont plus tous représentables.
0,51248autant de flottants dans chaque intervalledonc l'ecart double a chaque puissance de 2
Chaque intervalle contient le même nombre de flottants, mais il est deux fois plus large que le précédent : les graduations s'écartent, donc la précision absolue se dégrade à mesure que le nombre grandit.

C'est ce pas variable qui explique l'ABSORPTION : ajouter 11 à 101610^{16} ne change rien, car le résultat exact tomberait entre deux flottants voisins et l'arrondi ramène au point de départ.

Du caractère à l'octet

  • ASCII code 128128 caractères sur 77 bits. Repères : AA vaut 6565, aa vaut 9797, le chiffre 00 vaut 4848, l'espace vaut 3232.
  • L'écart entre une majuscule et sa minuscule vaut donc 3232, ce qui permet de changer la casse par une addition.
  • UNICODE est un RÉPERTOIRE : il associe un numéro, appelé point de code, à chaque caractère du monde. Ce n'est pas un encodage.
  • UTF-8 est un ENCODAGE à longueur variable : de 11 à 44 octets par caractère, et il est compatible avec ASCII sur ses 128128 premiers caractères.
  • Le nombre de CARACTÈRES et le nombre d'OCTETS ne coïncident que pour un texte purement ASCII.
c1a1f1é2_136 caracteresoctets : 1+1+1+2+1+3 = 9
Chaque case est un caractère, le nombre en dessous est son coût en octets. Six caractères, mais neuf octets : l'accent en coûte deux et le symbole monétaire trois.

En Python, `len(s)` compte les CARACTÈRES; `len(s.encode('utf-8'))` compte les OCTETS. Les deux diffèrent dès qu'un accent apparaît, ce qui arrive dans presque tout texte français.

Les pièges qui coûtent des points

Les erreurs ci-dessous sont celles que je corrige le plus souvent en séance. Chacune coûte des points sur une copie, même quand le raisonnement est juste.

1. Tester l'égalité de deux flottants

2 points, et un programme qui ne fait rien de ce qu'on attendait

Ce qu'il ne faut pas écrire

« if x == 0.3 : ... »

Ce qu'il faut écrire

« On compare à une TOLÉRANCE : if abs(x - 0.3) < 1e-9 : ... Deux flottants issus de calculs différents ne sont presque jamais égaux bit à bit. »

Pourquoi : L'égalité de flottants demande que TOUS les bits coïncident. Or 0,1+0,20{,}1+0{,}2 vaut 0,300000000000000040{,}30000000000000004 : le test échoue alors que le calcul est correct.

2. Croire que 0,1 est exact parce qu'il est court à écrire

1,5 point, et la justification de tout l'exercice

Ce qu'il ne faut pas écrire

« 0,10{,}1 ne fait qu'un chiffre après la virgule, il est donc exact en machine. »

Ce qu'il faut écrire

« La brièveté DÉCIMALE ne dit rien de l'écriture BINAIRE. 0,1=1100{,}1=\frac{1}{10} et 10=2×510=2\times 5 : le facteur 55 interdit une écriture binaire finie, donc la machine arrondit. »

Pourquoi : Le critère est arithmétique : un nombre est exact en binaire si son dénominateur réduit est une puissance de 22. C'est le même phénomène que 13\frac{1}{3} en décimal, qui donne 0,3330{,}333\dots

3. Utiliser un flottant comme variable de boucle

toute la question, et une boucle infinie à l'exécution

Ce qu'il ne faut pas écrire

« x = 0.0 puis while x != 1.0 : x = x + 0.1 »

Ce qu'il faut écrire

« Une variable de boucle doit être un ENTIER : for i in range(10) : x = i / 10. On ne compte jamais avec des flottants. »

Pourquoi : Après dix additions, xx vaut 0,99999999999999990{,}9999999999999999 et non 1,01{,}0 : la condition d'arrêt n'est jamais atteinte. Le compteur entier, lui, est exact par construction.

4. Confondre le nombre de caractères et le nombre d'octets

1,5 point, et le dimensionnement d'un fichier

Ce qu'il ne faut pas écrire

« La chaîne compte 66 caractères, elle occupe donc 66 octets. »

Ce qu'il faut écrire

« En UTF-8, un caractère occupe de 11 à 44 octets. Ici l'accent en coûte 22 et le symbole euro 33 : 66 caractères font 99 octets. »

Pourquoi : L'égalité entre caractères et octets n'est vraie qu'en ASCII pur. Tout texte français la brise dès le premier accent, et c'est exactement ce que l'énoncé choisit.

5. Confondre Unicode et UTF-8

1 point, et la question de définition qui ouvre souvent l'exercice

Ce qu'il ne faut pas écrire

« Le caractère est codé en Unicode sur trois octets. »

Ce qu'il faut écrire

« UNICODE lui attribue un point de code, un numéro; UTF-8 est l'ENCODAGE qui décide combien d'octets servent à l'écrire. Le même point de code occupe 22 octets en UTF-16 et 33 en UTF-8. »

Pourquoi : L'un est un catalogue, l'autre une manière de l'écrire sur un disque. Les confondre rend incompréhensible l'existence de plusieurs encodages pour un seul répertoire.

6. Se tromper sur les codes ASCII des chiffres

1,5 point, et toute conversion de chaîne en nombre écrite à la main

Ce qu'il ne faut pas écrire

« Le caractère `7` a pour code ASCII 77. »

Ce qu'il faut écrire

« Le caractère `7` a pour code 5555, car le chiffre `0` vaut 4848. On retrouve la valeur numérique par la soustraction : 5548=755-48=7. »

Pourquoi : Le code d'un caractère et la valeur qu'il représente sont deux choses différentes. La soustraction du code de `0` est le geste standard de toute conversion manuelle.

7. Ignorer l'absorption

2 points, et la conclusion sur la perte de précision

Ce qu'il ne faut pas écrire

« 1016+110^{16}+1 vaut 1000000000000000110000000000000001, comme en mathématiques. »

Ce qu'il faut écrire

« Au voisinage de 101610^{16}, l'écart entre deux flottants voisins dépasse 11 : le résultat exact n'est pas représentable et l'arrondi renvoie 101610^{16}. L'addition n'a rien changé. »

Pourquoi : C'est la conséquence directe du pas variable : loin de zéro, les flottants sont trop espacés pour distinguer deux entiers consécutifs. Additionner un petit terme à un grand le fait disparaître.

8. Croire les flottants régulièrement espacés

1,5 point, et l'explication de la perte de précision

Ce qu'il ne faut pas écrire

« Il y a autant de flottants entre 00 et 11 qu'entre 11 et 22. »

Ce qu'il faut écrire

« Il y en a énormément plus entre 00 et 11 : chaque intervalle [2k;2k+1][2^{k}\,;2^{k+1}] contient le même nombre de flottants, donc plus on descend vers zéro, plus ils sont serrés. »

Pourquoi : L'exposant code l'ordre de grandeur, la mantisse la précision RELATIVE. La précision relative est constante, donc la précision absolue se dégrade quand le nombre grandit.

Quelle méthode choisir

Comment comparer, selon ce que l'on manipule

On regarde le TYPE des valeurs comparées, et l'origine de ces valeurs.

  • Si les deux valeurs sont des ENTIERS le test d'égalité est sûr : les entiers Python sont exacts et de taille illimitée

    Exemple : if n == 100 : ...

  • Si au moins une valeur est un FLOTTANT issu d'un calcul comparer à une tolérance : abs(a - b) < eps, avec eps de l'ordre de 10910^{-9}

    Exemple : abs(0.1 + 0.2 - 0.3) < 1e-9 est vrai, alors que l'égalité est fausse

  • Si les nombres comparés sont TRÈS GRANDS ou très petits utiliser une tolérance RELATIVE : abs(a - b) < eps * abs(b)

    Une tolérance absolue de 10910^{-9} n'a aucun sens sur des nombres de l'ordre de 101210^{12}, où le pas des flottants dépasse déjà cette valeur.

  • Si on veut compter ou indexer utiliser des ENTIERS, toujours : range, indices de liste, compteurs de boucle

    Diviser après coup, comme x = i / 10, garde le compteur exact et ne coûte rien.

  • Si on manipule des sommes d'argent compter en CENTIMES avec des entiers, jamais en euros avec des flottants

Si aucune branche ne s'applique, c'est souvent qu'il ne faut pas comparer du tout : chercher plutôt le minimum, trier, ou tester une inégalité large, qui sont robustes là où l'égalité ne l'est pas.

Compter les octets d'un texte en UTF-8

On classe chaque caractère selon son point de code Unicode, puis on additionne.

  • Si le caractère est dans la table ASCII, point de code inférieur à 128128 11 octet

    Exemple : les lettres non accentuées, les chiffres, l'espace, la ponctuation courante

  • Si le point de code est compris entre 128128 et 20472047 22 octets

    Exemple : les lettres accentuées du français, comme ee accent aigu

  • Si le point de code est compris entre 20482048 et 6553565535 33 octets

    Exemple : le symbole euro, les idéogrammes chinois courants

  • Si le point de code dépasse 6553565535 44 octets

    Exemple : les émojis

  • Si on veut la réponse en Python plutôt qu'à la main len(s) donne les caractères, len(s.encode('utf-8')) donne les octets

Attention à l'encodage du fichier source lui-même : un fichier enregistré en Latin-1 et lu comme de l'UTF-8 produit les caractères parasites que l'on voit à la place des accents. C'est un problème d'encodage, pas de contenu.

La rédaction attendue

Le correcteur coche des étapes. Les voici dans l'ordre, avec la phrase de conclusion qu'il attend mot pour mot.

Justifier qu'un nombre est ou n'est pas exact en binaire

Quand l'utiliser : L'énoncé donne un nombre décimal et demande s'il est représentable exactement en machine.

  1. 1 Écrire le nombre sous forme de FRACTION irréductible : « 0,1=1100{,}1=\dfrac{1}{10} ».
  2. 2 Décomposer le dénominateur en facteurs premiers : « 10=2×510=2\times 5 ».
  3. 3 Appliquer le critère en le citant : « un nombre s'écrit exactement en binaire si et seulement si son dénominateur réduit est une puissance de 22 ».
  4. 4 Conclure sur ce cas précis, en nommant le facteur qui bloque : « le facteur 55 empêche toute écriture binaire finie ».
  5. 5 Ajouter la conséquence pratique : « la machine stocke donc une valeur arrondie, légèrement différente de 0,10{,}1 ».

Phrase de conclusion

0,10{,}1 s'écrit 110\dfrac{1}{10}, dont le dénominateur 10=2×510=2\times 5 n'est pas une puissance de 22 : son écriture binaire est infinie et périodique, la machine en conserve donc une valeur arrondie sur 5252 bits de mantisse.

Le piège : Invoquer le nombre de bits disponibles. Le problème n'est pas la taille de la mantisse : 0,10{,}1 resterait inexact avec un million de bits, exactement comme 13\frac{1}{3} reste infini en décimal.

Barème : 0,5 point pour la fraction, 0,5 point pour la décomposition, 1 point pour le critère cité, 1 point pour la conclusion sur l'arrondi.

Vérifier avant de rendre

Cinq minutes de vérification récupèrent plus de points qu'un exercice de plus commencé à la hâte.

L'exercice type décortiqué

La boucle qui ne s'arrête jamais

On veut afficher les valeurs 0,00{,}0 puis 0,10{,}1 et ainsi de suite jusqu'à 0,90{,}9. Un élève propose le programme ci-dessous, qui ne s'arrête jamais.

1. Expliquer pourquoi la boucle est infinie. 2. Quelle valeur exacte prend x après dix additions ? 3. Corriger le programme avec un compteur entier. 4. Proposer une correction par comparaison à une tolérance. 5. Laquelle des deux corrections est préférable, et pourquoi ?

12345678910-2-112erreur en unites de 1e-16ni nulle, ni reguliere
Erreur accumulée en additionnant 0,10{,}1 dix fois, en unités de 101610^{-16} : elle n'est ni nulle, ni croissante, ni régulière, ce qui interdit toute prévision de tête.
python
x = 0.0
while x != 1.0:
    print(x)
    x = x + 0.1

Étape 1

0,10{,}1 n'est pas représentable exactement en binaire. Chaque addition ajoute donc une valeur légèrement différente de 0,10{,}1, et les arrondis s'accumulent : xx ne vaut jamais exactement 1,01{,}0.

Pourquoi

La cause est dans la REPRÉSENTATION, pas dans la boucle. Répondre « parce que la condition n'est jamais vraie » décrit le symptôme sans nommer la cause, et ne vaut aucun point.

Étape 2

Après dix additions, xx vaut 0,99999999999999990{,}9999999999999999, soit environ 101610^{-16} de moins que 1,01{,}0. Le test `x != 1.0` reste donc vrai, et la boucle continue indéfiniment.

Pourquoi

Donner la valeur exacte, avec ses seize chiffres, montre que l'on a compris l'ordre de grandeur de l'erreur. Un « environ 11 » ne prouve rien.

Étape 3

Correction par compteur entier : `for i in range(10):` puis `print(i / 10)`. Le compteur est exact, et la division n'est faite qu'à l'affichage.

Pourquoi

C'est la correction de fond : on supprime le flottant de la CONDITION d'arrêt. L'erreur d'arrondi subsiste sur les valeurs affichées, mais elle ne pilote plus le programme.

Étape 4

Correction par tolérance : `while abs(x - 1.0) > 1e-9:` à la place du test d'égalité. La boucle s'arrête bien après dix tours.

Pourquoi

Cette correction traite le symptôme et suffit quand la boucle doit vraiment porter sur des flottants. Le choix de la tolérance, ici 10910^{-9}, doit être bien plus grand que 101610^{-16} et bien plus petit que le pas voulu.

Étape 5

La version à compteur entier est préférable : le nombre de tours y est garanti par construction, indépendamment de toute erreur d'arrondi.

Pourquoi

La question demande un choix ARGUMENTÉ. L'argument attendu est la garantie du nombre d'itérations, pas la brièveté du code.

Étape 6

Vérification dans l'interpréteur : on repart de `x = 0.0` et on ajoute dix fois `0.1`, puis on affiche `x`. On obtient 0,99999999999999990{,}9999999999999999, et `abs(x - 1.0) < 1e-15` est vrai alors que `x == 1.0` est faux.

Pourquoi

Le contrôle se fait dans l'interpréteur, en trois secondes. C'est le réflexe qui distingue un élève qui a compris d'un élève qui récite le cours.

Conclusion rédigée

La boucle est infinie parce que 0,10{,}1 n'est pas exact en binaire : après dix additions xx vaut 0,99999999999999990{,}9999999999999999 et jamais 1,01{,}0. On corrige soit en pilotant la boucle par un compteur entier, `for i in range(10)`, ce qui est préférable, soit en remplaçant l'égalité par une comparaison à une tolérance.

L'erreur classique sur cet exercice : Corriger en écrivant `while x < 1.0:`. La boucle s'arrête, mais elle exécute ONZE tours au lieu de dix, puisque xx vaut encore 0,99999999999999990{,}9999999999999999 au dixième test. L'inégalité stricte masque le problème sans le résoudre.

À savoir par cœur

  • Un décimal est exact en binaire si et seulement si son dénominateur réduit est une puissance de 22.
  • Double précision : 6464 bits, 11 de signe, 1111 d'exposant, 5252 de mantisse, environ 1616 chiffres significatifs.
  • Autant de flottants entre 11 et 22 qu'entre 10241024 et 20482048 : l'écart absolu double à chaque puissance de 22.
  • Jamais de `==` entre flottants : comparer abs(a - b) à une tolérance.
  • Jamais de flottant comme variable de boucle : compter en entiers, diviser ensuite.
  • ASCII : AA vaut 6565, aa vaut 9797, le chiffre 00 vaut 4848, l'espace vaut 3232.
  • Unicode est un répertoire, UTF-8 un encodage de 11 à 44 octets. Caractères et octets ne coïncident qu'en ASCII pur.

Questions fréquentes

Pourquoi 0.1 + 0.2 ne donne pas 0.3 en Python ?

Parce que ni zéro virgule un ni zéro virgule deux ne s'écrivent exactement en binaire. La machine en stocke des valeurs arrondies, et la somme de deux arrondis n'est pas l'arrondi de la somme. Le résultat affiché est zéro virgule trois suivi de seize zéros puis d'un quatre. C'est normal et cela arrive dans tous les langages.

Comment comparer correctement deux nombres flottants ?

On ne teste jamais l'égalité. On calcule la valeur absolue de leur différence et on la compare à une petite tolérance, par exemple dix puissance moins neuf. Pour des nombres très grands, on utilise une tolérance relative, proportionnelle à l'ordre de grandeur des valeurs comparées.

Quelle est la différence entre Unicode et UTF-8 ?

Unicode est un répertoire : il attribue un numéro, appelé point de code, à chaque caractère existant. UTF-8 est un encodage : il décide comment écrire ce numéro sous forme d'octets, de un à quatre selon le caractère. Un même caractère Unicode s'écrit différemment en UTF-8 et en UTF-16.

Pourquoi le nombre de caractères n'est pas le nombre d'octets ?

Parce que UTF-8 est un encodage à longueur variable. Une lettre non accentuée occupe un octet, une lettre accentuée deux, le symbole euro trois, un émoji quatre. Les deux nombres ne coïncident que pour un texte purement ASCII, ce qui n'est presque jamais le cas d'un texte français.

Combien de chiffres significatifs garde un flottant ?

Environ quinze à seize en double précision, car la mantisse ne dispose que de cinquante-deux bits. Au-delà, les chiffres affichés ne sont plus fiables. C'est aussi pourquoi ajouter un à dix puissance seize ne change rien : le résultat exact n'est plus représentable.

Passer à la pratique

Exercices corrigés : Représentation des données : réels et texte

Une méthode se prouve sur une copie, pas sur une fiche. La série du même chapitre reprend chacun de ces pièges dans un exercice, avec le corrigé rédigé étape par étape.

  • 15 exercices corrigés
  • 150 points
  • 225 minutes
Faire les exercices
Fiche précédente Python : types, contrôle, fonctions et tableaux Fiche suivante 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 à Montréal ?

Contactez-moi pour une première séance. On reprend les réflexes qui coûtent des points en devoir, du test d'égalité sur des flottants jusqu'au comptage des octets d'un texte, puis on les met à l'épreuve sur du code du niveau réel des évaluations de Première.

Site par Studio Squalli