x = int(input("Entrez un nombre entier positif : "))
devine = 0
while devine ** 2 < x:
devine += 1
if devine ** 2 != x:
print(f"{x} n'est pas un carré parfait")
else:
print(f"La racine carrée de {x} est {devine}.")
x = float(input("Entrez un nombre entier positif : "))
devine = 0
pas = 0.0001
while devine ** 2 < x:
devine += pas
erreur = abs(x - devine ** 2)
print(f"La racine carrée de {x} est {devine} à {erreur} près")
Cette approche est basée sur une énumération exhaustive.
Limites :
D'autres approches existent.
valeur_recherchee = int(input("Entrez un nombre entre 0 et 1 000 000 : "))
if valeur_recherchee < 0 or valeur_recherchee > 1000000:
print("Erreur")
else:
debut = 0
fin = 1000000
milieu = round((fin + debut) / 2)
while milieu != valeur_recherchee:
if milieu > valeur_recherchee:
fin = milieu
else:
debut = milieu
milieu = round((fin + debut) / 2)
print(milieu)
x = float(input("Entrez un nombre positif : "))
debut = 0
fin = max(1, x)
milieu = (fin + debut) / 2
epsilon = 0.0001
while abs(milieu ** 2 - x) >= epsilon:
if milieu ** 2 > x:
fin = milieu
else:
debut = milieu
milieu = (fin + debut) / 2
erreur = abs(milieu ** 2 - x)
print(f"La racine carrée de {x} est {milieu} à {erreur} près")
Intuitivement, pour les 3 problèmes qui nous préoccupent :
S'il est nécessaire de tester toutes les valeurs, la dichotomie n'apporte rien.
L'instrumentation consiste à rajouter du code :
print
.Ces ajouts ne modifient pas l'algorithme instrumenté. Il s'agit d'instruments de mesure.
x = float(input("Entrez un nombre positif : "))
debut = 0
fin = max(1, x)
milieu = (fin + debut) / 2
epsilon = 0.0001
while abs(milieu ** 2 - x) >= epsilon:
print(milieu) # On affiche ici la valeur
if milieu ** 2 > x:
fin = milieu
else:
debut = milieu
milieu = (fin + debut) / 2
erreur = abs(milieu ** 2 - x)
print(f"La racine carrée de {x} est {milieu} à {erreur} près")
x = float(input("Entrez un nombre positif : "))
debut = 0
fin = max(1, x)
milieu = (fin + debut) / 2
epsilon = 0.0001
compteur = 0
while abs(milieu ** 2 - x) >= epsilon:
compteur += 1 # On incrémente le compteur d'itérations
if milieu ** 2 > x:
fin = milieu
else:
debut = milieu
milieu = (fin + debut) / 2
erreur = abs(milieu ** 2 - x)
print(f"La racine carrée de {x} est {milieu} à {erreur} près")
print(f"Nombre d'itérations : {compteur}") # on l'affiche
import time
x = float(input("Entrez un nombre positif : "))
chrono_debut = time.process_time() # démarrage du chronomètre
#
# Corps du code à chronométrer
# (voir diapositives précédentes pour les détails)
#
chrono_fin = time.process_time() # arrêt du chronomètre
temps_ecoule = chrono_fin - chrono_debut # calcul du temps écoulé
erreur = abs(milieu ** 2 - x)
print(f"La racine carrée de {x} est {milieu} à {erreur} près")
print(f"Temps d'exécution : {temps_ecoule}s") # on l'affiche
time.process_time
ne sont pas précises car elles sont impactées par les autres processus s'exécutant sur la machine.a0 = float(input("Entrez un nombre positif : "))
s = a0 / 2
epsilon = 0.0001
while abs(s ** 2 - a0) >= epsilon:
P = s ** 2 - a0
P_prime = 2 * s
s = s - P / P_prime
erreur = abs(s ** 2 - a0)
print(f"La racine carrée de {a0} est {s} à {erreur} près")
En résumé :
Vous allez voir cela en pratique dans le prochain TP.
Solution : mettre en place de bonnes pratiques de développement logiciel.
Nous aborderons quelques unes de ces bonnes pratiques dans les prochains cours.
a0 = 16
s = a0 / 2
epsilon = 0.1
while abs(s ** 2 - a0) >= epsilon:
P = s ** 2 - a0
P_prime = 2 * s
s = s - P / P_prime
print(f"sqrt({a0}) == {s}")
A gauche de l'interface se trouve le menu Run and Debug.
Dans le cadre de ce cours, vous choisirez toujours de déboguer le fichier courant.
Lorsque le programme s'exécute (non arrêté sur un point d'arrêt), la barre d'outils de débogage, qui se trouve en haut de l'éditeur, a cet aspect.
Aspect d'une ligne avant de poser un point d'arrêt.
En haut à droite de l'éditeur se trouve un bouton avec une flêche. Si on sélectionne la flêche avec un insecte, on lance l'exécution en mode débogage.
Parfois, on souhaite arrêter l'exécution uniquement lorsqu'une condition bien particulière est remplie.
Pour cela, on commence par créer un point d'arrêt classique. Ensuite, on fait un clic droit sur ce point d'arrêt pour l'éditer.
Si on souhaite arrêter l'exécution uniquement si la valeur de la variable P est inférieure à 20, il suffit de rentrer l'expression P < 20
.
Il est possible de rentrer n'importe quelle expression Booléenne valide en Python.
L'aspect d'un point d'arrêt conditionnel permet d'alerter sur la nature particulière de ce point d'arrêt.
On peut obtenir l'arborescence de toutes les valeurs de toutes les variables dans l'encadré à gauche de l'éditeur.
Un point d'arrêt Log est un losange à la place d'un cercle.
On va commencer à réfléchir aux manières d'utiliser les outils vus précédemment pour construire des programmes simples. On va également voir quelques techniques de programmation et de débogage.
On cherche simplement à retrouver le nombre rentré par l'utilisateur. On énumère toutes les possibilité. A chaque étape, on tente de deviner la valeur et on vérifie si c'est la bonne.
On énumère à nouveau toutes les possibilités. A chaque fois, on tente de deviner un diviseur possible et on vérifie le reste de la division. Au final, si on a trouvé un diviseur, c'est que le nombre n'est pas premier.
Ce nouvel algorithme est plus complexe que le précédent pour résoudre le même problème. Cela étant, cet algorithme est également plus efficace. En effet, il tire parti du fait qu'un nombre sur 2 est pair, et que les nombres pairs ne sont pas premiers. Par conséquent, cet algorithme teste 2x moins de nombres que le précédent. C'est un compromis classique : complexité d'implémentation pour meilleure vitesse d'exécution.
Cet algorithme, encore du type "devine-et-vérifie" effectue une énumération exhaustive. Cependant, elle est très limitée : on ne peut trouver que les carrés parfaits.
Cet algorithme naïf est inefficace et peu précis. En effet, il effectue un nombre très important de tests. Par ailleurs, la précision n'est pas garantie pour les grands nombres.
Tirer au sort un édudiant et le faire jouer pour voir si elle utilise naturellement une énumération exhaustive ou une dichotomie. Demandez ensuite aux autres étudiants ce qu'ils en pensent. Faire éventuellement passer un autre apprenant si la première n'a pas trouvé le juste prix.
L'intervalle est représenté par [début ; fin]. A chaque itération, on regarde si la valeur au milieu de l'intervalle est la bonne. Si la valeur recherchée est plus petite que le milieu, l'intervalle devient [début ; milieu]. Sinon, l'intervalle devient [milieu ; fin]. Par conséquent, début et fin convergent rapidement vers la valeur recherchée.
On travaille dans l'intervalle [0 ; x] si x > 1 ou dans [0 ; 1] si 0 < x < 1. A chaque itération, on regarde si la valeur au milieu de l'intervalle est proche de la valeur souhaitée, à un epsilon près. Si la valeur recherchée est plus petite que le milieu, l'intervalle devient [début ; milieu]. Sinon, l'intervalle devient [milieu ; fin]. Par conséquent, début et fin convergent rapidement vers la valeur recherchée. Cet algorithme basé sur la dichotomie converge beaucoup plus rapidement que la version avec énumération exhaustive. De plus, on contrôle mieux l'erreur grâce à un epsilon indépendant du pas d'itération.
Dans le cas des nombres premiers, il est de toutes façons nécessaires de tester toutes les valeurs.
Dans la version finale de l'algorithme, on souhaitera retirer cette instrumentation. En effet, l'utilisateur final de l'algorightme ne souhaite pas voir les étapes intermédiaires. En revanche, pour comprendre un algorithme, il est utile de l'instrumenter de cette manière.
Compter le nombre d'itérations permet de comparer 2 algorithmes ayant le même objectif. L'algorithme qui parvient au résultat en un nombre d'itérations minimum est meilleur.
En plus du nombre d'itérations, avoir le temps d'exécution permet également de comparer des algorithmes. Il est à noter que cette manière de mesurer est naïve. Il existe des approches plus robustes pour mesurer un temps d'exécution. Néanmoins, cette technique offre un moyen rapide de se faire une idée.
La démonstration de ce théorème est en-dehors de la portée de ce cours.
Il s'agit simplement de l'application directe de la formule énoncée dans les diapositives précédentes. On démarre avec s = a0 / 2. Au lieu d'utiliser s1 et s2, on réassigne s à chaque itération. C'est un algorithme simple, élégant et performant.
Une fuite mémoire survient lorsqu'un programme alloue toujours plus de mémoire, sans jamais la libérer. Le programme peut fonctionner correctement pendant plusieurs jours sans problème.
Les bugs manifestes et persistants sont les plus simples à analyser et à corriger. Les bugs cachés et intermittents peuvent se révéler très complexes à déboguer. Si ces problèmes surviennent rarement, il arrive que certaines entreprises décident malheureusement de ne pas les traiter.
La difficulté dans cet exemple est que lors d'une itération, P et P_prime dépendent de la valeur précédente de s. Nous avons choisi, sur une ligne, de donner la valeur de s ayant servi au calcul de P et P_prime lors de l'itération courante. La nouvelle valeur de s apparaît sur la ligne suivante.
Nous ne rentrerons pas dans ces détails dans le cadre de ce cours. Le cours de méthodologie et les prochains cours de programmation en JavaScript, PHP, etc. vous apporteront des outils supplémentaires.