Travail Dirigé 1 - Dichotomie pour Racines et Logarithmes

Le jour de l’examen, vous commencerez par avoir du temps pour réfléchir et poser vos idées sur papier avant de passer sur machine. Vous devez donc pratiquer cette approche.

Au-delà de l’aspect préparation à l’examen, il s’agit d’une pratique importante qui permet de prendre du recul par rapport à des problématiques de développement logiciel. Plutôt que de tenter d’écrire directement un programme sur machine, prendre le temps de noter ses réflexions permet souvent de trouver de meilleures solutions.

Dans le cadre de ce premier TD, vous allez donc écrire vos premiers programmes Python sur papier.

Exercice 1 - Calcul d’un racine carrée par énumération dans un espace de solutions

Ecrivez un script en Python qui calcule une racine carrée xN±εx \approx \sqrt{N} \pm \varepsilon en énumérant une suite de solutions.

Le nombre NN est obtenu via la fonction input et le résultat xx, s’il est trouvé, est affiché dans la sortie standard avec print. Sinon, le message d’erreur suivant est affiché : "Erreur : la racine carrée de {N} n'a pas été trouvée".

L’algorithme suit les étapes suivantes :

  • On lit la valeur NN rétournée par input, qui est convertie en float.
  • On initialise xx à 0.
  • On initialise ε\varepsilon à 0.01.
  • Dans une boucle :
    • Si x2Nε\left| x^2 - N \right| \leq \varepsilon, alors la boucle s’arrête et on affiche le résultat xx.
    • Si x>Nx > N, alors la boucle s’arrête et le message d’erreur est affiché.
    • Sinon, on incrémente xx de ε2\varepsilon ^ 2 et on poursuit la recherche.

Exercice 2 - Calcul d’une racine carrée en utilisant la dichotomie

Ecrivez un script qui calcule une racine carrée xN±εx \approx \sqrt{N} \pm \varepsilon en utilisant la dichotomie.

Le principe consiste à réduire progressivement un intervalle [inf;sup]\left[ \inf ; \sup \right] de telle sorte que cet intervalle converge vers [Nε;N+ε]\left[ \sqrt{N} - \varepsilon ; \sqrt{N} + \varepsilon \right]. N’importe quelle valeur dans cet intervalle est considérée comme un xx valable.

De manière intuitive, l’intervalle va commencer à [0;N][0 ; N], puis à chaque étape, on teste la valeur au milieu de cet intervalle. A chacune de ces étapes, soit on augmente la borne inférieure inf\inf, soit on diminue la borne supérieure sup\sup.

Par exemple, pour N=9N = 9 :

  • On commence sur l’intervalle [0;9][0 ; 9] et on teste 4.54.5.
  • L’intervalle devient [0;4.5][0 ; 4.5] et on teste donc 2.252.25.
  • L’intervalle devient [2.25;4.5][2.25 ; 4.5] et on teste donc 2.25+4.52=3.375\frac{2.25 + 4.5}{2} = 3.375.
  • L’intervalle devient [2.25;3.375][2.25 ; 3.375] et on teste donc 2.25+3.3752=2.8125\frac{2.25 + 3.375}{2} = 2.8125.
  • L’intervalle devient [2.8125;3.375][2.8125 ; 3.375] et on teste donc 2.8125+3.3752=3.09375\frac{2.8125 + 3.375}{2} = 3.09375.
  • L’intervalle devient [2.8125;3.09375][2.8125 ; 3.09375] et on teste donc 2.8125+3.093752=2.953125\frac{2.8125 + 3.09375}{2} = 2.953125.
  • L’intervalle devient [2.953125;3.09375][2.953125 ; 3.09375] et on teste donc 2.953125+3.093752=3.0234375\frac{2.953125 + 3.09375}{2} = 3.0234375.
  • L’intervalle devient [2.953125;3.0234375][2.953125 ; 3.0234375] et on teste donc 2.953125+3.02343752=2.98828125\frac{2.953125 + 3.0234375}{2} = 2.98828125.
  • L’intervalle devient [2.98828125;3.0234375][2.98828125 ; 3.0234375] et on teste donc 2.98828125+3.02343752=3.005859375\frac{2.98828125 + 3.0234375}{2} = 3.005859375.
  • L’intervalle devient [2.98828125;3.005859375][2.98828125 ; 3.005859375] et on teste donc 2.98828125+3.0058593752=2.997070312\frac{2.98828125 + 3.005859375}{2} = 2.997070312.
  • L’intervalle devient [2.997070312;3.005859375][2.997070312 ; 3.005859375] et on teste donc 2.997070312+3.0058593752=3.001464843\frac{2.997070312 + 3.005859375}{2} = 3.001464843.
  • Or 3.0014648432=9.0087912073.001464843^2 = 9.008791207, donc si ε=0.01\varepsilon = 0.01, alors on a notre résultat.

Le nombre NN est obtenu via la fonction input et le résultat xx est affiché dans la sortie standard.

L’algorithme suit les étapes suivantes :

  • On lit la valeur NN rétournée par input.
  • On initialise ε\varepsilon à 0.01.
  • On initialise inf\inf à 0.
  • On initialise sup\sup à {N,siN>11,sinon\begin{cases}N, si N > 1 \\ 1, sinon\end{cases}.
  • On initialise xx à inf+sup2\frac{\inf + \sup}{2} : on prend le milieu de l’intervalle.
  • Dans une boucle :
    • Si x2Nε\left| x^2 - N \right| \leq \varepsilon, alors la boucle s’arrête et on affiche le résultat xx.
    • Sinon :
      • Si x2<Nx^2 < N, alors la borne inf\inf prend la valeur xx. On est donc dans l’intervalle [x;sup][x ; \sup].
      • Sinon, la borne sup\sup prend la valeur xx. On est donc dans l’intervalle [inf;x][\inf ; x].
      • On met à jour xx en calculant à nouveau inf+sup2\frac{\inf + \sup}{2} : on prend le milieu du nouvel intervalle réduit.

Exercice 3 - Comparaison des approches

Selon vous, quelle approche de calcul de racine carrée fonctionne le mieux entre :

  • une énumération dans un espace de solutions,
  • l’utilisation de la dichotomie.

Pourquoi ? Veuillez justifier votre réponse avec des exemples.

Exercice 4 - Nombre négatif

Que se passe-t-il si l’utilisateur rentre N=9N = -9 ?

Que faut-il faire pour éviter cela ?

Exercice 5 - Calcul d’un logarithme

Ecrivez un script qui calcule un logarithme xlog2(N)±εx \approx \log_2(N) \pm \varepsilon en utilisant la dichotomie.

Pour rappel, un logarithme en base 2 est tel que 2x=N2^x = N. Il s’agit donc, algorithmiquement parlant, d’un problème similaire à celui du calcul d’une racine carrée (pour lequel on avait x2=Nx^2 = N).

Par exemple, pour N=8N = 8 :

  • On commence sur l’intervalle [0;8][0 ; 8] et on teste 44.
  • L’intervalle devient [0;4][0 ; 4] et on teste donc 22.
  • L’intervalle devient [2;4][2 ; 4] et on teste donc 33.
  • Bingo, 23=82^3 = 8 et on a trouvé le résultat exact.

Exercice 6 - Accélération du calcul de logarithme

Dans l’exercice précédent, nous avons utilisé l’intervalle de départ [0;N][0 ; N] pour commencer la dichotomie. En pratique, il est possible de trouver très rapidement un intervalle de recherche beaucoup plus petit. Plus l’intervalle est petit, plus on diminue le nombre d’itérations nécessaires pour trouver un résultat, et plus l’exécution est rapide.

Intuitivement, la raison pour laquelle on peut trouver un meilleur intervalle de départ est la vitesse à laquelle la fonction puissance évolue :

xx2x2^x
01
12
24
38
416
532
664
7128
8256
9512
101024
112048
124096
138192
1416384
1532768
1665536
17131072
18262144
19524288
201048576

On peut donc commencer par rechercher le plus grand nombre entier borneborne tel que 2borne<N2^{borne} < N.

On pourra ainsi commencer la dichotomie avec l’intervalle [borne1;borne+1][borne - 1 ; borne + 1] à la place de l’intervalle [0;N][0 ; N].

Par exemple, si on recherche x=log2(N)x = \log_2(N) pour N=80000N = 80000, on peut voir dans le tableau ci-dessus que 216=655362^{16} = 65536.

On pourra donc commencer la dichotomie avec l’intervalle [15;17][15 ; 17] à la place de l’intervalle [0;80000][0 ; 80000].

Cette technique porte le nom d’approximations successives : on trouve d’abord l’intervalle [15;17][15 ; 17] puis on recherche la valeur xx par dichotomie, soit environ 16.287712316.2877123.

Réécrivez votre script de calcul de logarithme pour y intégrer l’optimisation initiale sur les bornes.