Étant donné un exposant $e$, on souhaite calculer le plus efficacement possible $x^e, \forall x$, c'est-à-dire en minimisant le nombre de multiplications, pour gagner en temps d'exécution.
En effet, pour calculer $X^8$, on peut :
L'écart se creuse très rapidement, entre la méthode naïve et les méthodes plus évoluées. Ainsi, pour calculer $X^{1024}, il faut
Quand on sait que pour chiffrer, dans la méthode RSA, il faut élever notre message (un nombre) à la puissance "un grand nombre premier" (comprendre : un exposant de plusieurs centaines de chiffres), on comprend l'intérêt de racourcir le temps de calcul d'une puissance.
L'exponentiation rapide est une technique classique, utilisée en pratique, pour obtenir la puissance d'un nombre donné.
Cette exponentiation rapide itère, pour le calcul de $X^e$, les opérations suivantes :
dans un ordre dépendant de l'écriture en base 2 de $e$.
Plus précisément...
15 s'écrit 1111 en base 2.
L'algorithme précédent aboutit au mot $SXSXSX$. Les étapes du calcul sont alors :
res = $X$ | $X$ | |
S | res = res * res | $X^2$ |
X | res = res * $X$ | $X^3$ |
S | res = res * res | $X^6$ |
X | res = res * $X$ | $X^7$ |
S | res = res * res | $X^{14}$ |
X | res = res * $X$ | $X^{15}$ |
Le dernier résultat (res) contient donc la puissance recherchée, en seulement 6 opérations (contre 14, dans la méthode naïve).
La liste d'exposants est ici : 1, 2, 3, 6, 7, 14, 15.
19 s'écrit 10011 en base 2, ce qui fournit le mot $SSSXSX$.
Après travail, la liste d'exposants est ici : 1, 2, 4, 8, 9, 18, 19.
On pourrait donc calculer $X^{19}$ en 6 multiplications.
Seulement, l'exponentiation rapide ne donne pas la méthode la plus rapide systématiquement. Par exemple, pour calculer $X^{15}$, on aurait pu procéder ainsi :
$a_1 = X$ | $X$ |
$a_2 = a_1*a_1$ | $X^2$ |
$a_3 = a_2*a_1$ | $X^3$ |
$a_4 = a_3*a_3$ | $X^6$ |
$a_5 = a_4*a_3$ | $X^9$ |
$a_6 = a_4*a_4$ | $X^{15}$ |
Soit 5 multiplications pour $X^{15}$, au lieu de 6 par la méthode dite exponentiation rapide. Une meilleure liste d'exposants pour $X^{15}$ est donc : 1, 2, 3, 6, 9, 15.
On rappelle que, dans la méthode d'exponentiation rapide de $X^e$, le nouveau terme est égal au terme précédent multiplié :
On a donc, du point de vue des exposants :
$1 = a_1 < a_2 < ... < a_n = e$
avec
Par exemple, pour $X^{15}$ par exponentiation rapide, les exposants 1, 2, 3, 6, 7, 14, 15 sont bien de cette forme (pour passer d'un terme au successeur, soit on double, soit on rajoute 1).
On impose, par cette méthode, trop de restrictions aux listes d'exposants. Il nous suffit en effet d'avoir une liste d'exposants :
On veut que cette liste soit la plus petite possible, et évidemment qu'elle soit strictement croissante.
Faire intervenir le dernier exposant calculé pour obtenir le suivant semble alors légitime, vu que l'on souhaite atteindre $e$ rapidement.
On cherche donc des listes d'exposants de la forme
$1 = a_1 < a_2 < ... < a_n = e$
où
$a_{i+1} = a_i + a_j, j \leqslant i$
Par contre, imposer $j=i$ ou $j=1$ est une restriction inutile. La lever conduit à définir de nouveaux algorithmes d'exponentiation rapide, tels que les chaînes d'addition.
Faire une fonction qui teste si une liste $L$ d'exposants est correcte : chaque terme $a_{i+1}$ de la suite doit pouvoir s'écrire comme la somme de son prédécesseur, et d'un second terme apparu précédemment dans la liste. En d'autres termes, la fonction doit vérifier si l'on a
$\forall i \leqslant len(L)-1, \exists j \leqslant i-1, a_{i+1} = a_i + a_j, j \leqslant i$
Créez une fonction qui calcule $X^e$ à partir d'une liste d'exposants fournis en argument. On stockera, dans un dictionnaire, les termes intermédiaires obtenus avec leurs exposants associés : on en aura besoin pour les termes à venir.
La première idée qui peut nous venir à l'esprit, pour calculer $X^e$, est de tester toutes les possibilités. On peut, à ce niveau, choisir un premier angle d'attaque :
Ou procéder en s'inspirant d'une technique de type backtracking :
Une métaheuristique consiste à utiliser le hasard pour la génération de nouveaux algorithmes (Monte-Carlo, etc.) Une telle méthode de conception peut s'avérer fructueuse ; on va donc chercher à réaliser un algorithme d'exponentiation rapide basée sur l'utilisation du hasard.
Réalisons une fonction de génération aléatoire de la liste d'exposants. On trouvera en argument (de cette fonction) l'exposant $e$ à atteindre. On pourra tester les deux voies suivantes :
Une fois ces fonctions réalisées... Que pensez-vous de cette métaheuristique dans le cas qui nous intéresse ici ? Est-elle efficace ?
Vérifier expérimentalement que cette méthode n'est pas idéale :
Conclusion ? Il faut chercher une methode plus efficace...
Une chaîne d'addition (de Brauer) pour un exposant entier $e$ est une liste d'entiers $1 = a_1 < a_2 < ... < a_r = e$ où $a_i = a_{i-1} + a_k, k<i$
Une chaîne d'addition pour l'exposant entier 15 peut être
D'où la chaîne : 1, 2, 4, 8, 12, 14, 15.
Les listes d'entiers obtenues par la méthode d'exponentiation rapide sont, on l'a vu, de la forme : $1 = a_1 < a_2 < ... < a_r = e$ avec $a_i = a_{i-1} + a_k, k \in {1;i-1}$.
C'est donc un cas particulier de chaînes d'addition.
Dans le cas de l'exponentiation rapide, la longueur $l_n$ de la chaîne pour l'exposant $n$, se calcule en fonction du nombre $\nu_n$ de 1 dans l'écriture binaire de $n$, par la formule :
$l_n = \lfloor log_2(n) \rfloor + \nu_n - 1$
Par exemple, $15 = 1111_2$, donc $\nu_{15} = 4$, et $l_{15} = \lfloor log_2(15) \rfloor + 4 - 1 = 3+4-1 = 6$
De même, pour $19 = 10011_2$, $l_{19} = 4+3-1 = 6$.
On a vu que l'exponentiation rapide ne donne pas toujours la meilleure chaîne d'addition. Plus précisément, les meilleurs chaînes d'addition sont, dans le cas de l'exponentiation rapide, obtenues pour les entiers de la forme $2^n$, et les pires dans le cas $2^{n+1}-1$.
En effet, pour $n$ entier dans [$2^k, 2^{k+1} -1$], $log_2(n) = k$ reste fixe. Seul change donc, dans $l_n$, le nombre $\nu_n$ de 1, qui vaut 1 pour $2^n = 1000...0_2$, et $n+1$ pour $2^{n+1}-1 = 111...1_2$.
De manière plus générale, trouver la plus courte chaîne d'addition est un problème compliqué.
Les résultats restent à établir, quant à la longueur d'une chaîne minimale pour l'entier $n$. Par exemple, une conjecture de Sholz-Brauer, énoncée en 1937, et qui affirme que $l_{2^n-1} \leqslant n-1+l_n$, n'a toujours pas été démontrée.
Dans la pratique, puisqu'aucun résultat général ne peut fournir systématiquement la meilleure chaîne, on peut utiliser des techniques d'optimisation pour s'approcher des chaînes minimales. Nous donnons, dans ce qui suit, un exemple d'un tel algorithme...
On suppose posséder une fonction générant aléatoirement une chaîne d'addition de $x$ à $e$. Par exemple, le pseudo-code suivant peut convenir :
trouve = faux tant que trouve = faux L = [1] n = 1 tant que L[n] < e choisir k dans [1, n] ajouter L[n] + L[k] à la liste L incrémenter n si L[n] = e, alors trouve = vrai retourner L
(D'autres algorithmes peuvent être trouvés.)
L'algorithme génétique est issu d'une analogie des processus d'évolution de Darwin.
On sélectionne aléatoirement $n$ individus, puis on boucle :
jusqu'à obtenir un résultat satisfaisant.
Une des applications possibles de cette métaheuristique à notre problème est :
La mutation d'une chaîne $1 = a_1 < a_2 < a_3 < ... < a_n = e$ consiste à :
Programmez la recherche de chaînes d'addition par algorithme génétique.
On regardera l'évolution de la taille de la plus courte chaîne d'addition en fonction de différents paramètres de cet algorithme :
Une exploration des résultats, sous forme de graphes et de synthèse, est souhaitée. Une comparaison avec la technique d'exponentiation rapide est la bienvenue.
Enfin, cet algorithme peut être amélioré de différentes manières. Le faire est le bienvenu.