Potęgowanie modularne

Potęgowanie modularne. Naszym celem jest policzenie

\( a^b\ {\sf mod} \ n . \)

Oczywiście, możemy założyć, że \( 0\leq a < n \), bo inaczej najpierw można policzyć resztę \( a' \) z dzielenia \( a \) przez \( n \), a dopiero potem \( (a')^b \ {\sf mod} \ n \), jako że \( (a')^b \ {\sf mod}\ n =a^b \ {\sf mod} \ n \). Zauważmy, że w przeciwieństwie do zwykłego potęgowania wynik nigdy nie przekracza \( n \), czyli nie rośnie wykładniczo w stosunku do \( a \). Pozwala to żywić nadzieję na szybsze algorytmy potęgujące. Dla rozgrzewki przeanalizujmy najpierw naiwny sposób liczenia potęgi modulo:

  • Naiwne potęgowanie modulo. Wymnóż \( a \) przez siebie \( b \)-krotnie, po każdym mnożeniu weź resztę modulo \( n \). Branie reszty po każdym mnożeniu, utrzymuje tymczasowy iloczyn w zbiorze \( {\{ {0,\ldots,n-1} \} } \). Zatem wykonywanych jest \( b \) mnożeń liczb \( O(\log{n}) \)-bitowych, czyli wykonywanych jest \( O(b\log^2{n}) \) operacji bitowych. Jest to zatem algorytm działający w czasie wykładniczym względem rozmiaru wejścia \( O(\log{b}+\log{n}) \).
  • Szybkie potęgowanie modulo. Niech \( b=(b_{k-1}\ldots b_0)_2 \) będzie binarnym zapisem liczby \( b \). Zauważmy, że \( a^b \ {\sf mod} \ n =\prod_{i=0}^{k-1}a^{b_i2^i} \ {\sf mod} \ n \).

Policzmy zatem w następujacy sposób kolejne potęgi \( a \) występujące w iloczynie:

\( \begin{align*} a \cdot a \ {\sf mod} \ n & \equiv_n & a^2 \ {\sf mod} \ n , \\ (a^2 \ {\sf mod} \ n )\cdot(a^2 \ {\sf mod}\ n ) & \equiv_n & a^4 \ {\sf mod} \ n , \\ & \ldots & \\ (a^{2^{k-2}} \ {\sf mod} \ n )\cdot(a^{2^{k-2}} \ {\sf mod} \ n ) & \equiv_n & a^{2^{k-1}} \ {\sf mod} \ n . \end{align*} \)

Tym sposobem wykonanych zostanie \( k-2 \) mnożeń i dzieleń liczb \( O(\log{n}) \)-bitowych, czyli \( O(k\log^2{n}) \) operacji bitowych. Aby otrzymać wynik musimy jeszcze wymnożyć przez siebie te potęgi, ktorym odpowiada bit \( 1 \) w liczbie \( b \). Wykonanych zostanie więc jeszcze co najwyżej \( k \) mnożeń (które przeplatamy braniem reszty modulo \( n \)) liczb \( O(\log{n}) \)-bitowych.

W sumie wykonanych zostanie \( O(k\log^2{n})=O(\log{b}\cdot\log^2{n}) \) operacji bitowych, a więc znacznie mniej niż sposobem naiwnym. Przedstawiony algorytm jest efektywny (wielomianowy względem długości wejścia).

Przykład

\( 7^{12} \ {\sf mod} \ 10 =? \)

  • \( 12=(1100)_2 \),
  • wyznaczamy wybrane potęgi \( 7 \) modulo \( 10 \):
    • \( 7^2=49\equiv_{10} 9 \),
    • \( 7^4\equiv_{10}9\cdot9=81\equiv_{10}1 \),
    • \( 7^8\equiv_{10}1\cdot1=1 \),
  • \( 7^{12}=7^8\cdot7^4\equiv_{10}1\cdot1=1 \).
  • wykonane zostały tylko \( 4 \) mnożenia!

Przykład

\( 3^{51} \ {\sf mod} \ 13 =? \)

  • \( 51=(110011)_2 \),
  • wyznaczamy wybrane potęgi \( 3 \) modulo \( 13 \):
    • \( 3^2=9 \),
    • \( 3^4=9\cdot9=81\equiv_{13}3 \),
    • \( 3^8\equiv_{13}3\cdot3=9 \),
    • \( 3^{16}\equiv_{13}9\cdot9=81\equiv_{13}3 \),
    • \( 3^{32}\equiv_{13}3\cdot3=9 \).
  • \( 3^{51}=3^{32}\cdot3^{16}\cdot3^2\cdot3^1\equiv_{13}9\cdot3\cdot9\cdot3=729\equiv_{13}1 \).