とりあえず高速化とかほとんど考えないでnまでの素数を列挙するマクロを作ってみる
- \def\PrimeCount#1{%
- \NewArray{primes} \PushArray{primes}{2}% 配列primes生成、2をpush(=要素として加える)
- \newcount\@judnum \@judnum=3\relax% カウンタ\@judnumを生成、3を代入
- \newcount\@aryind% カウンタ\@aryindを生成 ※余計にカウンタを生成し過ぎないようループの前で生成する
- \@whilenum\@judnum<#1\relax \do{% \@judnumが引数nを超えないときループ
- \@aryind=0\relax%
- \LengthofArray[\arylen]{primes}% primesの長さ を\arylenに格納 ※\arylenはカウンタではなく制御綴。
- \@tempcntb=0\relax% \@tempcntbを、\@judnumが素数か判別するboolean変数として使用
- \@whilenum\@aryind<\arylen\relax \do{% \@judnumが素数かどうか判別するループ
- \GetArray[\ttt]{primes}{\the\@aryind}% 素数リストを順に調べていく \tttにprimesを格納
- \@tempcnta=\@judnum% 計算のため\@tempcntaに値をコピー
- \surplus\@tempcnta\ttt\relax% 以前作った\surplusで\@tempcnta % \tttを計算
- \ifnum\@tempcnta=\z@% 割り切れたなら\@tempcndb=1にする& \@aryindを増やしてloopを終わらせる
- \@tempcntb1\relax%
- \advance\@aryind\arylen%
- \fi%
- \increment\@aryind%
- }%
- \ifnum\@tempcntb=\z@ \PushArray*{primes}{\the\@judnum} \fi% ↑で\@tempcndb=1になっていなければ素数
- \advance\@judnum2% 偶数は飛ばす
- }%
- [\ShowArray{primes}]%
- }%
ここで問題になるのは以前作った\surplusがカウンタを3つも使っている上に一番激しいループの中にいることだ。
ひとまず引数をわざわざ新しいカウンタで受けている部分は激しくもったいないので引数を直接使うとして、それでも一つはカウンタが必要にになる。
- \def\surplus#1#2{%
- \newcount\t \t=#1\relax%
- \divide#1#2 \multiply#1#2 \advance\t-#1%
- #1=\t\relax%
- }
(No room for a new \count はカウンタがもう作れないというエラー)
4桁も無い素数リストなどショボいにも程が有るのでもう少し改善を試みる。本体の\PrimeCountと同様カウンタを"外で"作ってしまえばいい、つまりJavaでいうクラス変数みたいな感じにすればよろしい。Javaでもストリームリーダーとかを作りすぎてメモリを浪費しないようにクラスで宣言することがある。
流石に \t とかいう適当な名前だとスコープが広がってどっかで名前が被りそうな気がするので名前を複雑にして
- \newcount\suptmp
- \def\surplus#1#2{%
- \suptmp=#1\relax%
- \divide#1#2 \multiply#1#2 \advance\suptmp-#1%
- #1=\suptmp\relax%
- }
さて、ここからは高速化を図る。素数以外の数では割っていないのでこれでもかなり速めだが、
nの約数pは\(\sqrt n\) までに現れなければそれと対になる\(\frac{n}{p}\)も無いからそれより先を計算しても割り切れることはないはずである。
というわけで\(\sqrt n\)までで計算を止めたいが剰余すらなかった\(\mathrm\TeX\)に平方根計算などど言う高級なものがあるわけもなく、ニュートン法やら二分探索やらを使ったらできるんだろうが今回は\(p^2\)がnを超えないという条件を使う。(またpのためにカウンタを用意しなければいけないが)
\newcount\@tespow
を4行目の後、
\@tespow=\ttt \multiply\@tespow\ttt\relax
\ifnum\@tespow>\@judnum \advance\@aryind\arylen\relax \fi
を11行目の後におけばループはpが\(\sqrt n\)を超えたところでbreakされる。
結果はn=10000の時
元の関数が11秒
改良した物が1.7秒
だった(\(\mathrm\TeX\)では計時機能が分単位なのでストップウォッチで計ることになったが)
めっちゃ速くなったなびっくり
おかげでn=100000でも数十秒でできた。
24ページに渡る素数リストもできたし時間をかければいくらでも増やせそうなので今回は満足。
-2019-03-12: \(\TeX\)を\(\mathrm\TeX\)に変更
0 件のコメント:
コメントを投稿