はじめに
これは RSA完全理解 Advent Calendar の8日目の記事です。
さて、同カレンダーの過去の記事にて
- ユークリッド互除法
- ベズーの等式
について紹介したので、今回はユークリッド互除法をつかってベズーの等式の解を探索する方法について紹介します。
なお、このことを一般に拡張ユークリッド互除法などと呼んだりします。
拡張ユークリッド互除法とは
ユークリッド互除法は
となる
という性質を利用して効率よく gcd(a, b)
を求めるアルゴリズムのことでした。
前回の説明では素通りしましたが、じつは計算して得られる等式を整理すると、そのステップの余りを前のステップの情報をつかって表現できます!
以下はユークリッド互除法の手順と、そのステップの余りについて整理した式です。
- , 変形して , さらに余りについて
- , 変形して , さらに余りについて
- , 変形して , さらに余りについて
- 以下割り切れるまで続ける...
となり、それぞれの割られる数を前のステップの値で埋めて、余りを表現することができます。
詳細はのちほど詳しく説明しますが、このことを利用すると最終的に の形になり、解である も一緒にわかるようになります。
当然 は自身に1をかけた倍数であるので、
を0でない整数としたとき、 が解を持つ
は の倍数
を満たす解のうちの一つです。
以上のように、ユークリッドの互除法の操作を応用すればベズーの等式の解を効率的に得る事ができます。
おお〜なるほどやってみようや
具体例あると理解が進むと思うので例をだしてやってみるぞ!
, として普通にユークリッド互除法やってみる
- , あまりについて整理して
- , あまりについて整理して
- , あまりについて整理して
となる。
は割り切れたステップのひとつ前の余りであるため、そのときの についての式
を出発点として前ステップの値で展開していく
(144について前ステップの値を代入)
(↑を整理)
(↑重複整理)
これで の式が得られ、1152と504の係数が の の解の一つとなります
念の為等式が成り立つか計算してみても
となり成立しています。
詳しい説明
まず、余り についての式をいくつか並べてみて、規則を見つけて一般化を試みます
こうしてながめると 1, 2 の式以外は
というような法則があることがわかります(ユークリッド互除法は商とあまりを引き継いでステップを進めるので、当然っちゃ当然)(ちなみに1, 2の式についてはうまく同じルールで取り扱うための手法をおまけで紹介してます)
さて、この式はじつは の形で表すことができます
は とあらわせる
このとき と考えられるため、
であることが言えれば
となり は の倍数の和であらわせます。
から を表現するには直前の2つのステップの結果が必要であり、 はじめの2つの式に の倍数の和である確認ができれば再帰的に適用することで後続も成り立つとわかります。
計算すると
- なので は の倍数の和
なので は の倍数の和
となるので、後続の任意の について の倍数の和であることがわかります。
についての係数をそれぞれ とすると
と表せます
これを元の式である の と の部分に代入すると以下のようになります
これより の係数である について
といえることがわかります。
つまりユークリッド互除法において、手前2つのステップの の値と今のステップの商があれば が求められます
これをユークリッド互除法の処理が終了するまで、つまり となるまで続けると、 となっているはずで、そのときの の係数である はベズーの等式 を満たす解の一つとなっています!
ところで、ユークリッド互除法の最初の2ステップの処理は
のようになっておりそれぞれ を参照しています。
あまりが や となるステップは存在しないので、これらを参照する際には の式における
が存在しません。
そこで を守る形で と を表現するために、初期状態として以下の2項目を追加しておきます
- ()
- ()
このようにすると、はじめの2つのステップで について参照できます
これでユークリッド互除法を用いてベズーの等式 を満たす解 が探索できることがわかりました!
実装
あとは説明したことを実装するだけです!
拡張ユークリッド互除法は以下のようにかけます(わかりやすさを優先して書き方は少し冗長です。ほんとうはもうちょっとシュッとかけます)
func extGCD(a, b int64) (gcd, x, y int64) { // はじめの2つのステップで a, b を参照するために初期状態を定義 var prevx, prevy, curx, cury int64 = 1, 0, 0, 1 for b != 0 { var q int64 = a / b a, b = b, a%b // x[i] = x[i-2] - q*x[i-1] から次のxを計算して prev, cur を進める nextx := prevx - q*curx prevx = curx curx = nextx // y[i] = y[i-2] - q*y[i-1] から次のyを計算して prev, cur を進める nexty := prevy - q*cury prevy = cury cury = nexty } return a, prevx, prevy }
たとえば適当に 111
30
を入力すると、
- の解となる
のような結果が得られるはずです