$a^k \pmod{p}$ のような、大きな数のべき乗を法 $p$ で計算する場合、単純に $a$ を $k$ 回掛けてから剰余を求めると、計算途中の数値が非常に大きくなり、計算時間やメモリの点で非効率です。
繰り返し二乗法 (Exponentiation by Squaring)、またはバイナリ法 (Binary Method) は、この計算を効率的に行うためのアルゴリズムです。RSA暗号などで巨大な数のべき乗剰余を計算する際に不可欠な技術です。
このアルゴリズムの基本は、指数 $k$ を2進数表現で捉え、計算量を $O(\log k)$ に削減することにあります。
具体例
例えば、$5^{21} \pmod{p}$ を計算する場合を考えます。
まず、指数 $21$ を2進数で表現します。 $21 = 16 + 4 + 1 = 1 \cdot 2^4 + 0 \cdot 2^3 + 1 \cdot 2^2 + 0 \cdot 2^1 + 1 \cdot 2^0$ したがって、$21$ の2進数表記は $(10101)_2$ です。
これを利用して、$5^{21}$ を以下のように分解します。 $$ 5^{21} = 5^{16+4+1} = 5^{16} \cdot 5^4 \cdot 5^1 $$ $5^1, 5^2, 5^4, 5^8, 5^{16}, \dots$ といった、$a^{2^i}$ の形のべき乗を順次計算(前の結果を2乗していくだけで)しておけば、あとは2進数表記でビットが立っている(1である)項だけを掛け合わせることで、最終的な結果を得ることができます。これにより、掛け算の回数を大幅に削減できます。
アルゴリズムの実装
繰り返し二乗法には、指数のビットを右から見るか(Right-to-Left)、左から見るか(Left-to-Right)で少し異なる実装がありますが、どちらも計算量は同じです。ここでは、よりシンプルに実装できるRight-to-Left方式を紹介します。
Pythonプログラム
| |
プログラムの解説
res = 1:結果を格納する変数を1で初期化します。while exp > 0::指数expが0になるまでループします。if exp % 2 == 1::指数expの最下位ビット(2で割った余り)が1であるかをチェックします。res = (res * base) % mod:最下位ビットが1であれば、現在のbaseの値を結果resに掛け合わせます。base = (base * base) % mod:baseを2乗します。これにより、$a, a^2, a^4, a^8, \dots$ といった形で計算が進んでいきます。exp //= 2:指数expを2で割り(整数除算)、1ビット右にシフトします。これにより、次のループでは1つ上のビットを評価できるようになります。- ループが終了すると、
resに最終的な計算結果が格納されています。
この方法では、計算の各ステップで剰余を取るため、扱う数値が法 mod より大きくなることがなく、巨大な数のべき乗計算でもオーバーフローを防ぎつつ高速に処理できます。