ATELIER G

Le bug des parenthèses

1. Une formule à faire calculer

Le problème du jour : confier les calculs à une machine.

D’après le théorème 2 du cours, si \(\Delta=b^2-4ac>0\), l’équation \(ax^2+bx+c=0\) a deux solutions :

\[x_1=\frac{-b-\sqrt{\Delta}}{2a}\qquad\text{et}\qquad x_2=\frac{-b+\sqrt{\Delta}}{2a}.\]

Sans calculatrice, ces formules ne donnent une valeur simple que si \(\Delta\) est un carré parfait. Pour l’équation \(1{,}3x^2-2{,}7x-4{,}1=0\), il faudrait calculer \(\sqrt{28{,}61}\) : c’est hors de portée du calcul mental.

Le but de l’atelier

Écrire un petit programme Python qui calcule les solutions, le vérifier sur un cas connu, puis l’employer sur une équation que l’on ne sait pas résoudre de tête. En chemin, un bug très répandu vous attend.

Dans \(x_1=\dfrac{-b+\sqrt{\Delta}}{2a}\), qu’est-ce qui est divisé par quoi ? une seule réponse

La barre de fraction groupe tout ce qui est au-dessus et tout ce qui est au-dessous : on calcule le numérateur \(-b+\sqrt{\Delta}\) en entier, le dénominateur \(2a\) en entier, puis on divise l’un par l’autre. C’est ce groupement, évident sur le papier, qu’il faudra écrire pour Python.

2. D’où vient la formule ?

La démonstration du théorème 2, en accéléré.

Al-Khwârizmî, vers 830, résolvait ses équations en complétant un carré dessiné. La formule avec des lettres n’est venue que bien plus tard, quand François Viète (1591) puis René Descartes (1637) ont pris l’habitude de désigner les nombres connus par des lettres. Le principe, lui, n’a pas changé : compléter le carré. Dévoilez la démonstration ligne à ligne.

\(ax^2+bx+c=0\)l’équation, avec \(a\neq0\)
\(x^2+\dfrac{b}{a}x+\dfrac{c}{a}=0\)on divise par \(a\)
\(\left(x+\dfrac{b}{2a}\right)^2-\dfrac{b^2}{4a^2}+\dfrac{c}{a}=0\)on complète le carré
\(\left(x+\dfrac{b}{2a}\right)^2=\dfrac{b^2-4ac}{4a^2}=\dfrac{\Delta}{(2a)^2}\)on isole le carré
\(x+\dfrac{b}{2a}=\dfrac{\sqrt{\Delta}}{2a}\) ou \(x+\dfrac{b}{2a}=-\dfrac{\sqrt{\Delta}}{2a}\)si \(\Delta>0\)
\(x=\dfrac{-b+\sqrt{\Delta}}{2a}\) ou \(x=\dfrac{-b-\sqrt{\Delta}}{2a}\)les deux solutions

Le cours raisonne par disjonction des cas : si \(\Delta<0\), un carré ne peut pas être négatif, il n’y a aucune solution ; si \(\Delta=0\), une seule ; si \(\Delta>0\), les deux ci-dessus. Le programme devra faire de même.

À la deuxième ligne, on divise par \(a\). Pourquoi en a-t-on le droit ? une seule réponse

On ne peut pas diviser par \(0\). Or, pour un polynôme du second degré \(ax^2+bx+c\), le coefficient \(a\) n’est jamais nul : sinon il n’y aurait plus de \(x^2\). Le signe de \(a\), lui, n’intervient pas ici.

3. Le programme d’Inès

Un cas connu, pour vérifier l’outil avant de s’en servir.

Inès a recopié les formules dans un programme, en écrivant delta**0.5 pour \(\sqrt{\Delta}\) (élever à la puissance \(0{,}5\), c’est prendre la racine carrée) :

def solutions(a, b, c): delta = b**2 - 4*a*c if delta < 0: return [] # aucune solution x1 = -b - delta**0.5/2*a x2 = -b + delta**0.5/2*a return [x1, x2] print(solutions(1, -5, 4))

Elle le teste sur \(x^2-5x+4=0\), dont les solutions sont connues : \(1\) et \(4\) (\(1-5+4=0\) et \(16-20+4=0\)). Le programme affiche pourtant [3.5, 6.5]. Voici, dans l’ordre, ce que calcule Python pour x2 :

Python calculec’est-à-direrésultat
delta**0.5la racine carrée de \(\Delta=9\)3.0
… / 2ce résultat, divisé par \(2\)1.5
… * apuis multiplié par \(a=1\)1.5
-b + …et enfin ajouté à \(-b=5\)6.5

Python n’a jamais vu la barre de fraction : il a divisé \(\sqrt{\Delta}\) seul, au lieu de diviser tout \(-b+\sqrt{\Delta}\). Le test sur un cas connu a démasqué le bug.

On exécute la ligne d’Inès, x2 = -b + delta**0.5/2*a, avec \(a=1\), \(b=2\) et \(c=-3\), c’est-à-dire pour l’équation \(x^2+2x-3=0\). Quelle valeur Python donne-t-il à x2 ? une seule réponse

\(\Delta=16\) et \(\sqrt{\Delta}=4\). Python calcule d’abord \(4\div 2=2\), multiplie par \(a=1\), puis ajoute \(-b=-2\) : x2 vaut \(0\). La bonne solution est \(\dfrac{-2+4}{2}=1\) : la ligne fautive ne divise que la racine carrée.

4. Les règles de priorité

1957 : la première machine qui lit des formules.

En 1957, une équipe d’IBM dirigée par John Backus livre FORTRAN, pour FORmula TRANslation : pour la première fois, on écrit une formule presque comme en mathématiques, et la machine la traduit en calculs. Il a fallu fixer des règles pour qu’une ligne comme -b + delta**0.5/2*a n’ait qu’un sens. Python suit les mêmes :

L’ordre des calculs en Python

1. Ce qui est entre parenthèses.
2. Les puissances **.
3. Les multiplications * et divisions /, de gauche à droite.
4. Les additions + et soustractions -, de gauche à droite.

Sur le papier, la barre de fraction groupe d’elle-même le numérateur et le dénominateur. Sur une seule ligne, ce groupement doit être écrit : \(\dfrac{-b+\sqrt{\Delta}}{2a}\) devient (-b + delta**0.5) / (2*a).

Quelle ligne calcule correctement \(x_2=\dfrac{-b+\sqrt{\Delta}}{2a}\) ? une seule réponse

Il faut des parenthèses autour du numérateur et du dénominateur : (-b + delta**0.5) / (2*a). Sans les secondes, / 2*a divise par \(2\) puis multiplie par \(a\) ; sans les premières, seule la racine est divisée ; -(b + …) change le signe de la racine, et (-b + delta)**0.5 prend la racine du mauvais nombre.

5. Un bug qui se cache

Le test réussit… et pourtant le programme est faux.

Inès corrige d’abord en ajoutant une seule paire de parenthèses : x2 = (-b + delta**0.5) / 2*a. Elle relance le test sur \(x^2-5x+4\) : cette fois, le programme affiche [1.0, 4.0]. Le test est réussi.

Mais d’après la règle « de gauche à droite », / 2*a divise par \(2\), puis multiplie par \(a\). Pour \(a=1\), diviser par \(2a\) ou diviser par \(2\) puis multiplier par \(a\), c’est la même chose : le test ne pouvait pas voir l’erreur.

Et pour \(a=-1\) ?

Multiplier par \(-1\) ou diviser par \(-1\), c’est encore la même chose : changer le signe. Le calcul fautif donne donc aussi le bon résultat pour \(a=-1\). Il ne se trahit que lorsque \(a\times a\neq1\).

La ligne x2 = (-b + delta**0.5) / 2*a est encore fausse. Avec laquelle de ces équations un test la démasquerait-il ? une seule réponse

… / 2*a calcule \(\dfrac{-b+\sqrt{\Delta}}{2}\times a\) au lieu de \(\dfrac{-b+\sqrt{\Delta}}{2a}\) : les deux résultats sont égaux quand \(a\times a=1\), c’est-à-dire pour \(a=1\) et aussi pour \(a=-1\). Seule l’équation de coefficient \(a=2\) révèle l’erreur : pour tester un programme, on choisit des cas qui ne cachent rien.

6. À vous : la console Python

Corriger, vérifier, puis s’en servir.

Voici le programme d’Inès, avec ses deux lignes fautives. Ouvrez la console, puis :

1. Exécutez-le : le test affiche [3.5, 6.5].
2. Corrigez les deux lignes x1 = … et x2 = … avec les bonnes parenthèses, et exécutez à nouveau : le test doit afficher [1.0, 4.0].
3. La seconde ligne affiche alors les solutions de \(1{,}3x^2-2{,}7x-4{,}1=0\).
4. Ajoutez une ligne pour l’équation \(0{,}8x^2-1{,}1x-2{,}7=0\), et répondez à la question ci-dessous.

Si la console ne se charge pas — réseau du lycée, tablette capricieuse —, le même programme s’exécute sur https://sciency.co/console.

Lire les résultats

Avec les deux lignes corrigées, le programme affiche :

[1.0, 4.0] [-1.0187814031255893, 3.0957044800486666]

La première ligne redonne les solutions connues \(1\) et \(4\) : c’est la preuve que le programme calcule juste. La seconde donne les solutions de \(1{,}3x^2-2{,}7x-4{,}1=0\), environ \(-1{,}02\) et \(3{,}10\). Python écrit les nombres décimaux avec un point et affiche jusqu’à dix-sept chiffres : seuls les premiers comptent.

Ajoutez au programme corrigé une ligne pour l’équation \(0{,}8x^2-1{,}1x-2{,}7=0\), puis exécutez-le. Entre quels nombres se trouve sa solution positive ? une seule réponse

La ligne à ajouter est print(solutions(0.8, -1.1, -2.7)) (Python écrit les nombres décimaux avec un point). Le programme affiche [-1.274044353309402, 2.649044353309402] : la solution positive vaut environ \(2{,}649\). Sans calculatrice, il aurait fallu calculer \(\sqrt{\Delta}\) avec \(\Delta=9{,}85\) : c’est le travail que le programme fait à notre place.

7. Ce qu’il faut retenir

Le fil, d’un bout à l’autre.

Le raisonnement

La formule \(\dfrac{-b\pm\sqrt{\Delta}}{2a}\) vient de la complétion du carré. Pour la faire calculer par une machine, on l’écrit sur une ligne en rendant visibles les groupements que fait la barre de fraction :

\[\frac{-b+\sqrt{\Delta}}{2a}\;\longrightarrow\;\texttt{(-b + delta**0.5) / (2*a)}\]

Un programme se vérifie sur un cas dont on connaît la réponse, choisi pour ne rien cacher (ici \(a\neq1\) et \(a\neq-1\)) ; ensuite seulement, on lui confie les cas hors de portée du calcul à la main.

Votre parcours

Vous avez répondu juste à 0 question(s) sur 6.