Seconde · Mathématiques · Chapitre 13

Python : ensembles de nombres et intervalles

Six exercices progressifs, de l'affectation aux boucles, sur les ensembles de nombres et les intervalles.
Ex. 1 — Affectation et types de nombres Ex. 2 — Reconnaître un entier Ex. 3 — Appartenance à un intervalle Ex. 4 — Réunion et intersection Ex. 5 — Boucle for : compter les entiers Ex. 6 — Boucle while : encadrer un irrationnel
Tous ces programmes se testent tels quels dans un éditeur Python (Capytale, Basthon, Thonny). Trois rappels de syntaxe : = est l'affectation (« prend la valeur de »), == est le test d'égalité ; les inégalités s'écrivent < <= > >= ; les blocs sont délimités par l'indentation (4 espaces), jamais par des accolades.
Exercice 1
Affectation et types de nombres

On exécute le programme suivant, ligne après ligne.

a = 7
b = 2
q = a / b
d = a // b
r = a % b
  1. Donner la valeur affichée par print(q), print(d) et print(r).
  2. Pour chacune de ces trois valeurs, préciser le plus petit ensemble parmi $\mathbb{N}$, $\mathbb{Z}$, $\mathbb{D}$ auquel elle appartient.
  3. On exécute ensuite les trois lignes ci-dessous. Que valent $a$ et $b$ à la fin ?
a = a + 10
b = a
a = 0
  1. Que renvoie 6 / 3 ? Et 6 // 3 ? Commenter.

a. Trois divisions différentes.

  • / est la division décimale : $q = 3{,}5$.
  • // est le quotient de la division euclidienne : $d = 3$.
  • % en est le reste : $r = 1$.

On retrouve bien l'égalité de la division euclidienne : $7 = 2\times 3 + 1$, avec $0 \leqslant 1 \lt 2$.

b. On cherche à chaque fois le plus petit ensemble qui contient la valeur.

  • $3{,}5 \in \mathbb{D}$ : c'est un décimal, mais pas un entier.
  • $3 \in \mathbb{N}$ et $1 \in \mathbb{N}$ : ce sont des entiers positifs.

Rappel des inclusions : $\mathbb{N} \subset \mathbb{Z} \subset \mathbb{D} \subset \mathbb{Q} \subset \mathbb{R}$.

c. Une affectation se lit de droite à gauche : on calcule d'abord la valeur à droite, puis on la range dans la variable de gauche.

$a = a + 10$ : on calcule $7+10 = 17$, donc $a$ vaut $17$.
$b = a$ : on copie la valeur actuelle de $a$, donc $b$ vaut $17$.
$a = 0$ : $a$ vaut maintenant $0$, mais $b$ garde $17$.

À la fin : $a = 0$ et $b = 17$. La copie a figé la valeur au moment où elle a été faite.

d. Attention au type du résultat.

6 / 3 renvoie 2.0 et non 2 : l'opérateur / renvoie toujours un nombre à virgule (type float), même quand la division tombe juste. 6 // 3 renvoie 2, un entier (type int).

💡 Mathématiquement $\dfrac{6}{3} = 2 \in \mathbb{N}$ dans les deux cas : le type Python décrit la manière de stocker le nombre, pas l'ensemble auquel il appartient.

Exercice 2
Reconnaître un entier

On veut écrire un programme qui demande un nombre à l'utilisateur, puis affiche le plus petit ensemble parmi $\mathbb{N}$ et $\mathbb{Z}$ auquel il appartient — ou signale qu'il n'est pas entier.

x = float(input("Entrer un nombre : "))

if ... :
    print("x appartient a N")
elif ... :
    print("x appartient a Z")
else:
    print("x n'est pas un entier")

On dispose de la fonction int(x), qui renvoie la partie entière de $x$ tronquée vers $0$ : int(2.7) vaut $2$ et int(-2.7) vaut $-2$.

  1. Compléter les deux conditions.
  2. Qu'affiche le programme pour $x = 5$, puis $x = -3$, puis $x = 2{,}5$ ?
  3. Pourquoi la première condition doit-elle être testée avant la seconde ?
  4. Pourquoi écrit-on x == int(x) et non x = int(x) ?

a. « Être un entier » se traduit par « être égal à sa partie entière ».

if x == int(x) and x >= 0:
    print("x appartient a N")
elif x == int(x):
    print("x appartient a Z")
else:
    print("x n'est pas un entier")

Le connecteur and exige que les deux conditions soient vraies en même temps : entier et positif.

b. On déroule les trois cas.

$x$$x$ == int($x$)$x \geqslant 0$Affichage
$5$vraivraiappartient à $\mathbb{N}$
$-3$vraifauxappartient à $\mathbb{Z}$
$2{,}5$fauxvrain'est pas un entier

c. C'est une conséquence directe de l'inclusion $\mathbb{N} \subset \mathbb{Z}$.

Tout entier naturel est aussi un entier relatif : si on testait $\mathbb{Z}$ en premier, $5$ déclencherait ce premier test et le programme n'afficherait jamais $\mathbb{N}$. Dans une cascade if / elif / else, on va toujours du plus petit ensemble vers le plus grand.

d. Ce sont deux instructions de natures totalement différentes.

  • x == int(x) est un test : il ne modifie rien et renvoie True ou False.
  • x = int(x) est une affectation : elle écraserait $x$ par sa partie entière, et $2{,}5$ deviendrait $2$.

💡 Remarque : la troncature vers $0$ de int ne gêne pas ici, car on se contente de tester une égalité.

Exercice 3
Appartenance à un intervalle

On considère l'intervalle $I = [-2\,;\,5[$.

  1. Recopier et compléter la fonction ci-dessous, qui doit renvoyer True si $x \in I$ et False sinon.
def appartient(x):
    return ...
  1. Que renvoie appartient(-2) ? appartient(5) ? appartient(4.999) ?
  2. Écrire de même une fonction appartient_J pour $J = \left]-\infty\,;\,3\right]$.

a. Un crochet fermé donne une inégalité large, un crochet ouvert une inégalité stricte.

def appartient(x):
    return -2 <= x and x < 5

Python accepte aussi l'écriture condensée, plus proche de celle du cours :

def appartient(x):
    return -2 <= x < 5

b. On teste les bornes, là où tout se joue.

  • appartient(-2)True : le crochet est fermé en $-2$, la borne est dans $I$.
  • appartient(5)False : le crochet est ouvert en $5$, la borne est exclue.
  • appartient(4.999)True : on est juste en dessous de $5$.

💡 Tester les deux bornes est le réflexe à avoir pour vérifier un programme d'appartenance : c'est exactement là que se cachent les erreurs de crochet.

c. $-\infty$ n'est pas un nombre : il ne peut pas apparaître dans un test.

def appartient_J(x):
    return x <= 3

Il ne reste qu'une seule condition, celle de droite. C'est cohérent avec le cours : du côté de l'infini, le crochet est toujours ouvert, car aucune borne n'y est atteinte.

Exercice 4
Réunion et intersection

On pose $I = \left]-3\,;\,4\right]$ et $J = [0\,;\,7[$.

  1. Écrire deux fonctions dans_I(x) et dans_J(x).
  2. En déduire deux fonctions dans_inter(x) et dans_union(x), testant l'appartenance à $I \cap J$ et à $I \cup J$. On les écrira à partir des deux premières, sans réécrire les inégalités.
  3. Compléter le tableau des valeurs renvoyées pour $x = -1$, $x = 0$, $x = 4$ et $x = 5$.
  4. En déduire $I \cap J$ et $I \cup J$ sous forme d'intervalles.

a. On traduit chaque crochet en inégalité.

def dans_I(x):
    return -3 < x <= 4

def dans_J(x):
    return 0 <= x < 7

b. « Et » pour l'intersection, « ou » pour la réunion.

def dans_inter(x):
    return dans_I(x) and dans_J(x)

def dans_union(x):
    return dans_I(x) or dans_J(x)

C'est la traduction littérale des définitions : $x \in I \cap J$ signifie « $x \in I$ et $x \in J$ », $x \in I \cup J$ signifie « $x \in I$ ou $x \in J$ » (au sens inclusif : l'un, l'autre, ou les deux).

c. Tableau des quatre valeurs.

$x$dans_Idans_Jdans_interdans_union
$-1$TrueFalseFalseTrue
$0$TrueTrueTrueTrue
$4$TrueTrueTrueTrue
$5$FalseTrueFalseTrue

d. L'intersection retient les bornes les plus « serrées », la réunion les plus larges.

\[ I \cap J = [0\,;\,4] \qquad\qquad I \cup J = \left]-3\,;\,7\right[ \]

Chaque borne conserve le crochet de l'intervalle dont elle provient : le $0$ fermé vient de $J$, le $4$ fermé vient de $I$, le $-3$ ouvert de $I$ et le $7$ ouvert de $J$.

💡 La réunion est ici un intervalle parce que $I$ et $J$ se chevauchent ($I \cap J \neq \varnothing$). Avec $I = [0\,;\,1]$ et $J = [5\,;\,6]$, la fonction dans_union fonctionnerait toujours, mais $I \cup J$ ne serait pas un intervalle.

Exercice 5
Boucle for : compter les entiers

On veut compter les entiers relatifs contenus dans un intervalle. On rappelle que range(a, b) parcourt les entiers de $a$ inclus à $b$ exclu.

  1. Écrire un programme qui affiche tous les entiers de $[-4\,;\,11]$.
  2. Le modifier pour qu'il affiche combien il y en a, à l'aide d'un compteur.
  3. Adapter le programme aux intervalles $\left]-4\,;\,11\right]$ puis $\left]-4\,;\,11\right[$. Combien d'entiers dans chaque cas ?
  4. Conjecturer le nombre d'entiers de $[a\,;\,b]$, où $a$ et $b$ sont deux entiers tels que $a \leqslant b$.

a. La borne de droite étant exclue, on écrit $12$ pour atteindre $11$.

for k in range(-4, 12):
    print(k)

b. Un compteur se crée avant la boucle, s'incrémente dedans, s'affiche après.

n = 0
for k in range(-4, 12):
    n = n + 1
print(n)

Le programme affiche $16$. Remarquer que n = n + 1 n'est pas une équation (elle n'aurait pas de solution !) mais une affectation : « $n$ prend la valeur de $n$ augmentée de $1$ ».

c. Ouvrir un crochet, c'est retirer une borne entière.

IntervalleInstructionNombre d'entiers
$[-4\,;\,11]$range(-4, 12)$16$
$\left]-4\,;\,11\right]$range(-3, 12)$15$
$\left]-4\,;\,11\right[$range(-3, 11)$14$

Ici les deux bornes sont entières : chaque crochet ouvert fait donc perdre exactement un entier.

d. On généralise à partir du cas $[-4\,;\,11]$.

\[ \text{nombre d'entiers de } [a\,;\,b] = b - a + 1 \]

Vérification : $11 - (-4) + 1 = 16$. ✔

💡 Le « $+1$ » oublié est l'erreur classique : de $1$ à $10$ il y a $10$ entiers, pas $10 - 1 = 9$. On compte les bornes, pas les écarts.

Exercice 6
Boucle while : encadrer un irrationnel

$\sqrt{2}$ appartient à $\mathbb{R}$ mais pas à $\mathbb{Q}$ : il n'a pas d'écriture décimale exacte. On ne peut donc que l'enfermer dans un intervalle aussi petit que l'on veut. Le programme suivant procède par balayage au pas de $10^{-3}$.

n = 0
while (n / 1000) ** 2 < 2:
    n = n + 1
print((n - 1) / 1000, n / 1000)
  1. Expliquer ce que teste la condition de la boucle, et ce qui se passe à chaque tour.
  2. Le programme affiche 1.414 1.415. Qu'en déduire pour $\sqrt{2}$ ? Quelle est l'amplitude de l'intervalle obtenu ?
  3. Vérifier le résultat à la main en calculant $1{,}414^2$ et $1{,}415^2$.
  4. Que faut-il modifier pour obtenir un encadrement d'amplitude $10^{-5}$ ?

a. Une boucle while répète tant que sa condition est vraie.

À chaque tour, $n$ augmente de $1$, donc le nombre testé $\dfrac{n}{1000}$ avance de $0{,}001$. On continue tant que son carré reste strictement inférieur à $2$ : la boucle s'arrête donc au premier entier $n$ pour lequel $\left(\dfrac{n}{1000}\right)^2 \geqslant 2$.

b. La boucle s'arrête pour $n = 1415$.

Le tour précédent ($n = 1414$) vérifiait encore la condition, donc :

\[ 1{,}414^2 \lt 2 \lt 1{,}415^2 \quad\text{donc}\quad 1{,}414 \lt \sqrt{2} \lt 1{,}415 \]

Autrement dit $\sqrt{2} \in \left]1{,}414\,;\,1{,}415\right[$, intervalle d'amplitude $1{,}415 - 1{,}414 = 10^{-3}$. On dit que $1{,}414$ est une valeur approchée de $\sqrt{2}$ par défaut à $10^{-3}$ près, et $1{,}415$ une valeur approchée par excès.

c. Vérification à la main.

\[ 1{,}414^2 = 1{,}999\,396 \lt 2 \qquad\qquad 1{,}415^2 = 2{,}002\,225 \gt 2 \]

L'encadrement est bien confirmé.

d. Il suffit de changer le pas de balayage.

n = 0
while (n / 100000) ** 2 < 2:
    n = n + 1
print((n - 1) / 100000, n / 100000)

On obtient $1{,}414\,21 \lt \sqrt{2} \lt 1{,}414\,22$, après $141\,422$ tours de boucle : cent fois plus de tours pour deux décimales de plus. Le balayage est simple mais coûteux.

💡 Pourquoi compter avec un entier $n$ plutôt qu'avancer directement par x = x + 0.001 ? Parce que les nombres à virgule de Python sont des valeurs approchées : on peut vérifier que 0.1 + 0.2 == 0.3 renvoie False ! Les erreurs s'accumuleraient à chaque tour. La machine ne manipule jamais $\mathbb{R}$, seulement un sous-ensemble fini de $\mathbb{D}$.