目次
主要な素因数分解アルゴリズム
ここでいう素因数分解アルゴリズムは完全に素因数分解をするアルゴリズムではなく因数(約数)をひとつ見つけ出すアルゴリズムなので、完全な分解が必要なら再帰的に実行したり試し割り法と組み合わせる必要がある。
素因数の性質に依存するアルゴリズム(ρ法、p-1法、楕円曲線法)
都合のよい性質を持った素因数を含んでいる場合に成功するアルゴリズム。都合のよい素因数を含んでいる場合は、汎用のアルゴリズムよりも早く素因数を見つけ出したり大きな素因数を取り出したりすることができる。
SQUFOFについて
SQUFOF(Square Form Factorization)について書かれた日本語の文章がほとんどないようなので説明を書いておく(SQUFOFの日本語名も特に存在しないみたい。平方形式分解?)。
連分数をもちいた素因数分解
SQUFOFはM. A. Morrison, J. Brillhart A Method of Factoring and the Factorization of F7(1975)で使われたアルゴリズム(連分数を利用した素因数分解アルゴリズムの一種)から派生したものなので、まずはそちらを少し説明する。
ρ法は疑似乱数列の中から「」となるものを(素因数を知らずに)見つけるという手法だった。
モリソンとブリルハートは「」となるもの見つけようとする。もし自明でないがうまく見つかるとによっての因数が得られる(これは二次ふるい法や数体ふるい法でも使われる考え方)。
それを見つけるために連分数展開を利用する。
因数分解したい数についてを連分数展開していく。
すると次の漸化式が成り立っている。
また連分数展開を途中で打ちきった近似連分数
を考えると、も漸化式で表すことができる(この漸化式はSQUFOFでは使わないので省略する。連分数についての多くの解説に書いてある)。
このとき
が成り立つ。で考えると
となり、等式の左辺が二乗の形になっている。そこでいくつかのについてを集めてそれをかけ合わせて全ての素因数の指数が偶数になるようにできれば右辺も二乗の形になり「」となるが得られる。
これがモリソンとブリルハートのアルゴリズムの概要。
シャンクスの改良
モリソンとブリルハートのアルゴリズムは次のようして単純化することができる。
- の連分数展開を続けていってが偶数のときでが完全平方になるものが現れるまで待てば、をいくつも集めずに平方となる解を得ることができる。
ただしこの場合、ある程度の長さ展開を続けていかないといけない。連分数の近似分数の分子分母は展開が進むにつれどんどん増大していくので、の増大を抑えるためにで余りを取り続けないといけない。
SQUFOFを考案したシャンクス(D. Shanks)は連分数の展開過程を二次形式の変換に対応させることで、を全く使うことなく因数を計算する方法を考案した(その理屈は省略する)。
アルゴリズムの説明
(おおよそJ. E. Gower, Samuel S. Wagstaff Jr. Square Form Factorizationの§3.1、3.3、3.5、5.2に基づく)
前提条件: 分解したい数は、偶数でない、素数でない、完全平方でないこと(またが小さいと連分数展開の周期が短いので失敗する可能性が大きくなる)。
※ [Gower&Wagstaff]では理論的な取扱いのためときはをに取り替えるという操作をしているが、この操作をしなくてもアルゴリズムは動作し多くの実装でもこの操作をしていないとある(§3.1)。
初期値と漸化式:
- 前進過程: 連分数展開の漸化式を使って展開を続けていき、が偶数でが完全平方になるものを見つける。
- ただし自明な解になってしまいそうな場合はそのは選ばず展開を続行する。次のように判定する。
- 見つけたがそれ以前の展開のどこかに現れた(が偶数なら)と等しい場合は選択せず展開に戻る(展開に出てくるはある値より小さいものだけを記憶しておけばいい。後述)。
- 展開のステップ数がある上限を越えた場合は失敗とする。また記憶した値の数がある程度増加したら(50個ぐらい?)失敗にする。
- ただし自明な解になってしまいそうな場合はそのは選ばず展開を続行する。次のように判定する。
- 後退過程: 前進過程の最後に得た項をに置き換えて、さらに展開を続ける。
- の漸化式は二つ前の値も使っているので、初期値の設定にはの関係を使う。前進過程の最後に得た項をとして次のようになる。
- 再び展開をおこない、と次の項が等しくなったところで展開を止める(後退過程での展開のステップ数は前進過程のおよそ半分ぐらい)。
- が偶数ならを見つかった因数とする。
- が奇数ならを見つかった因数とする。
- の漸化式は二つ前の値も使っているので、初期値の設定にはの関係を使う。前進過程の最後に得た項をとして次のようになる。
前進過程でを記憶する部分は次のようにする。
- を閾値とする。
- が偶数ならば、の場合にを記録する。
- が奇数ならば、の場合にを記録する。
- (小さいは非常に少ないので、まずかどうかをチェックして他の判定は後にするのが良い)
- 完全平方のが見つかったとき、がそれまでに記憶したどの値とも違っていれば後退過程に進む。
- 展開ステップの上限回数は。
例
N=63375401385616362433としてみる。L==126181
- Q0=1、P1=7960866874、Q1=65830557
- (記録) i=21127の時、Qi=223456≦2Lなので、223456/2=111728を記録。
- (完全平方) i=42462(偶数)の時、Qi=12483145984は完全平方。√Qi=111728は記録されているので展開を続ける。
- (記録) i=45890の時、Qi=114549≦Lなので、114549を記録。
- (記録) i=58124の時、Qi=1803≦Lなので、1803を記録。
- (完全平方) i=59398(偶数)の時、Qi=634082761は完全平方。√Qi=25181は記録されていないので後退過程に進む。(このときPi=7554780076)
- 展開の初期値はQ0=25181、P1=7960848882、Q1=11376179489になる。
- i=29807でPi=Pi+1=7692565499。このときQi=7692565499は奇数なので、7692565499を答えとする。
63375401385616362433 / 7692565499 = 8238526067
アルゴリズムの改良
を展開する代わりに小さな奇数を取ってを展開する: あるで探索が失敗しても別のを試すことができる。次第で少ない展開数でを見つけられるかもしれない。ただし自明な因数を除くようにアルゴリズムを少し変更する。
- 変更点1:を記録するところ: ならば、を記録する。
- 変更点2:となったところ:を見つかった因数とする。
- 性能の良いは3,5,7,11およびそれらからいくつかを選んで(全部もあり)かけ合わせたもの。
複数のでを展開していく: 例えば「何百ステップか展開するごとに別のでの展開に移る」というようにして複数のを同時展開していくと、どれかの系列で早くを見つけられてトータルで実行時間を減らせる(かもしれない)。
完全平方なの選択条件の変更: の記憶と比較の処理を上で説明したものよりも複雑なものにすると、適切なの発見までの展開数を減らすことができる(上のやり方だと適切なを飛ばす可能性がある)。ただし処理時間の点で(どれくらい)有利かは不明。
計算の省力化(アルゴリズム自体の改良ではない): 計算に時間がかかる部分はを求めるときの除算とが完全平方かを判定するところ。はとなることが多い(連分数展開しているのでになることはない)ので、はじめにかどうかを除算なしで判定するというやり方が考えられる。また完全平方かどうかはルートを取ってみないでも判定できる場合がある。
二乗したものを二進数で見ると末尾が必ずとなる。そのためまずこの形かどうかを調べると判定の効率がよくなるかもしれない。
Schemeによるプログラム例
;; Gauche以外では以下の定義を必要に応じて追加。 ;; SRFI-8 (define-syntax receive (syntax-rules () ((receive formals expression body ...) (call-with-values (lambda () expression) (lambda formals body ...))))) ;; R6RS (define (exact-integer-sqrt x) (let ((sqrt-x (inexact->exact (floor (sqrt x))))) (values sqrt-x (- x (* sqrt-x sqrt-x))))) ;; Gauche固有 (define (floor->exact x) (inexact->exact (floor x))) ;; Gauche固有 (define-syntax let1 (syntax-rules () ((_ var expr expr0 ...) (let ((var expr)) expr0 ...))))
;; 入力値のチェックや記録したQiの個数が多くなったときの処理は省略した ;; n>0 nは偶数、素数、完全平方ではないこと ;; 乗数multは小さな奇数 1, 3, 5, 7, 11, 3*5, 3*7, etc. (define (squfof n mult) (let* ((nm (* n mult)) (L (floor->exact (sqrt (* 2 (sqrt nm))))) (m*2 (* 2 mult)) (L*m*2 (* m*2 L)) (max-steps (* 4 L))) (receive (q0 nm-q0*q0) (exact-integer-sqrt nm) ;; 以上で初期パラメータ計算終了 ;; PiとQiの漸化式 (define (next-terms Qi-prev Pi Qi) (let* ((qi (quotient (+ q0 Pi) Qi)) (Pi-next (- (* qi Qi) Pi)) (Qi-next (+ Qi-prev (* qi (- Pi Pi-next))))) (values Pi-next Qi-next))) ;; Qi/gcd(Qi, 2m) <= Lの時、Qi/gcd(Qi, 2m)を記録する (define (update-saved-Qis saved-Qis Qi) (if (not (<= Qi L*m*2)) saved-Qis (let1 tmp (/ Qi (gcd Qi m*2)) (if (<= tmp L) (cons tmp saved-Qis) saved-Qis)))) ;; 前進過程 (define (forward-cycle i Qi-prev Pi Qi saved-Qis) (if (> i max-steps) #f (let1 saved-Qis (update-saved-Qis saved-Qis Qi) (receive (Pi-next Qi-next) (next-terms Qi-prev Pi Qi) (if (even? i) ; Q_{i+1}を調べるので偶奇判定が逆になる (forward-cycle (+ i 1) Qi Pi-next Qi-next saved-Qis) (receive (sqrtQ rem) (exact-integer-sqrt Qi-next) (if (or (not (zero? rem)) (memv sqrtQ saved-Qis)) (forward-cycle (+ i 1) Qi Pi-next Qi-next saved-Qis) (backward-cycle Pi-next sqrtQ)))))))) ;; 後退過程 (define (backward-cycle P-last sqrtQ) (let1 Pi-tmp (+ P-last (* sqrtQ (floor->exact (/ (- q0 P-last) sqrtQ)))) (let loop ((Qi-prev sqrtQ) (Pi Pi-tmp) (Qi (/ (- nm (* Pi-tmp Pi-tmp)) sqrtQ))) (receive (Pi-next Qi-next) (next-terms Qi-prev Pi Qi) (if (not (= Pi Pi-next)) (loop Qi Pi-next Qi-next) (/ Qi (gcd Qi m*2))))))) (forward-cycle 1 1 q0 nm-q0*q0 '()))))