ほぼ雑記的メモ
3n+1型の素数には三乗根があります。それは原始根をeとすれば 1, w=e^(p-1)/3 および w^2と求まるのですが、求めるだけならば原始根は知らなくても問題ありません。フェルマーの小定理からFpの任意の元aについて
a^(p-1) = 1
なのですから a^(p-1)/3 はあと3乗すれば1になる数、すなわちそれは1かwかw^2のいずれかです。よって適当に数を選んで(p-1)/3乗してみれば2/3の確率で求まります。原始根を求めるのは大変なので大きな素数pに対してはこちらのほうが簡単でしょう。
同じ理論が 素数の羃乗根(たとえば4乗根とか)にも使えます。
4n+1型の素数は4乗根があります。さきほど同様原始根をeとすれば 1, w=(p-1)/4、w^2, w^3が4乗根です。しかしw^2は2乗根であり、それは-1で自明な根です。自明でない4乗根(すなわち原始4乗根はwと w^3になります)よって、ランダムに選んだ数を(p-1)/4乗してみれば、1/2の確率で原始4乗根が得られることになります。
例1)
F13を考えます。12 = 3 * 2^2ですからF13は4乗根をもちます。実際
2^3 = 8
で、8は2乗根ではないから(原始)4乗根です。
例2)
F97は 97-1 = 3 * 32ですから32 乗根を持ちます。
よって、F97の任意の元を選び3乗すれば32乗根のうちのどれかに属します。その数をrとします。
これくらい大きくなるとそれが原始的かどうか(つまり32乗して始めて1になるのか)チェックするのは結構大変に思えます。
普通に考えれば rが原始32乗根であるには
r≠1
r^2≠1
r^4≠1
r^8≠1
r^16≠1
であることを確認すればよいのですが。しかしよく注視してみると
r^16≠1
だけチェックすればよいことがわかります。なぜなら r^16≠1ならば r^8≠1 だからです。
よって
r^16 = (a^3)^16 = a^48 ≠ 1
だけのチェックをすればよく、このチェックを通過した数は F97における1の原始32乗根です。
なおこのチェックは2の羃に関しては平方剰余かどうかをチェックしているのと同じというのがまた味わい深いですね。
これで計算してみると
2^48=1
3^48=1
4^48=1
5^48=96
よって、5^3 = 28がF97の原始32乗根ということになります。
例3)
F433は
p-1 = 432 = 16 * 3^3
よって F433は 27乗根をもちます。よって任意にaを選び16乗すればそれは27乗根の1つです。
原始27乗根であるかどうかチェックするには
r≠1
r^3≠1
r^9≠1
をチェックすればよいのですが、同様の理論により
r^9≠1だけをチェックすればよいことがわかります。
実際計算してみると
(2^16)^9 = 1
(3^16)^9 = 198
であり、3^16 = 26が F433の 原始27乗根として求まります
確率的なアルゴリズムではありますが「あたり」を引く確率は高いので、総じて計算は早いといえましょう。
3n+1型の素数pについて x^2 + 3y^2 = p となる整数を求めるアルゴリズムのメモ。解があることの証明は結構面倒なんで略します。
そもそも x^2 + 3y^2 <=p なる数は有限なのだから、全ての数について調べていけば解は見つかるわけですが、pが巨大な数になると探索は非常に困難になります。そこで、このエントリではpが巨大な数でも比較的少ない計算量で求める方法を紹介します。
基本的な手法としては4n+1型の素数についての平方和 x^2 + y^2 = p の解を求めるのと同じです。
x^2 + 3y^2 = N p --(1)
となるNを探します。そこからより小さいNを探し最終的にNを1にすることで求めます。(1)式は x, yの範囲として
-p/2 < x < p/2
-p/2 < y < p/2
と出来ることに注意します。(もし一つでも解が見つかれば x = x % p, y = y % pとすればよいのです。
x^2 + 3y^2 < (p/2)^2 + 3(p/2)^2 < p^2 より
N p < p^2
すなわち
N < p
となるようにNを選べることに注意します。
ここで次の事実を用います。
Nもまた u^2 + 3v^2 の形に書くことができる。
証明は結構面倒なので略します。
よって(1)式は
x^2 + 3y^2 = (u^2 + 3v^2) p --(2)
と書き直せます。このような u, vを見つけることができれば(2)式は
p = (x^2 + 3y^2) / (u^2 + 3v^2)
= (x + √-3y)(x - √-3y) / (u + √-3v)(u - √-3v)
と書き直されます。(いきなり平方根とか虚数とか出てくるけど気にしない)。ここで次の事実を用います。
(x + √-3y) / (u + √-3v)
または
(x + √-3y) / (u - √-3v)
のどちらかは割りきれる。
証明は結構面倒なので略します。
ただこのやり方はpを分解するのに、Nが現れ、しかもそのNを分解するというのは本末転倒のように思えますが、実際にはNの素因数は3n+1型の素数または平方数しか現れません。さらに後述するように最初のx, yを選べば平方数を含まないようにすることもできます。また x, yの選び方から N < p となるので、Nの素因数分解が容易に出来るのであればpに比べて小さい数の分解が出来ればよく、結構高速に求めることができます。各素因子の分解についても同じように考えていけばよいのです。
ただ、pが大きな数の場合、Nも大きくなる傾向があり、探索が困難になることが考えられまs.そこで、素因数分解をしなくてもよいアルゴリズムを紹介します。
次のような数を探します
t^2 + 3s^2 = M N --(3)
このような数はもうわかっていて、(1)のx, yがまさにそれです。ここで(1)同様 t, sは範囲として -N/2 から N/2の間に収めることが出来ることに注意します。(すなわち t = x % N, s = y % N とすればよいのです)
(1)式の両辺に(3)式の両辺 を掛ければ
(x^2 + 3y^2)(t^2 + 3s^2) = M N^2 p
左辺 = (x + √-3y)(x - √-3y) (t + √-3s)(t - √-3s)
= (x + √-3y)(t - √-3s)(x - √-3y)(t + √-3s)
= {xt + 3ys + √-3(-xs + yt)}{xt + 3ys -√-3(-xs + yt)}
= (xt + 3ys)^2 + 3(-xs + yt)^2
ところで -xs + yt = 0 (mod N)です。故に (-xs + yt)はNで割りきれます。右辺はN^2で割りきれるから等式が成立するには (xt + 3ys)^2もNで割りきれないといけません。つまり、
x' = (xt + 3ys)/N
y' = (-xs + yt)/N
はどちらも整数です。よって
x'^2 + 3y'^2 = M p
となります。t, sの値の取り方から M<Nとなりますからより小さいMを見つけることが出来ました。以下これを繰り返せばMは最終的に1にすることができます。
最後に(1)式をみたすx, yの探し方を紹介します。これには1の三乗根がw必要になります。これは原始根が分かっていれば簡単にわかります。
pの原始根をeとすれば pは 3n+1型だから体Kpにおいて、
w = e^n
とすればwは1の三乗根です。これより
(w - w^2)^2 = -3 (mod P)
となりますから(w^3=1に注意して計算してみるとよい)
x = (w - w^2)
y = 1
とすれば
x^2 + 3y^2 = 0 (mod P)
を満たすから
x^2 + 3y^2 = N P
なるNが見つかったことになります。
しかし三乗根を見つけるのに原始根を探すというのはいささか面倒です。これを解決するもっとシンプルな解法があります。適当な数をランダムに選びn乗すれば2/3の確率で1の三乗根になります。10個もトライすれば見つかるでしょう。
#!/usr/bin/env ruby
def inv(t, n)
a = n
b = t
x = 1
y = 0
while b != 1
shift = 0
while (b & (1 << shift)) == 0
if((x & 1) == 1)
x = x + n
end
x /= 2
shift += 1
end
b = b >> shift
shift = 0
while (a & (1 << shift)) == 0
if((y & 1) == 1)
y = y + n
end
y /= 2
shift += 1
end
a = a >> shift
if b > a
x = x + y
b = b - a
else
y = x + y
a = a - b
end
end
x
end
n = 3801307799
(1..n).each do |t|
s = inv(t, n)
puts("#{t} * #{s} = #{t * s % n} (mod #{n})")
end
一つ前のエントリ
の手法は途中の計算結果を保存しておかないといけないという問題がありました。そこでそれを改良したアルゴリズムを紹介します。このアルゴリズムもユークリッドの互除法を使うという点では同じです。違うのは
ax + by = 1
ではなく
ax + by = N
を考えている点がです。この式は a=N, b=n とすると x=1, y=0 という自明な解を持っています。
そこで a_1 = N, b_1 = n, x_1 = 1, y_1 = 0 として前回と同じように
a_{n+1} = b_n\\ b_{n+1} = m_n\\ x_{n+1} = d_n x_n + y_n\\ y_{n+1} = x_n と変形していきます。 a_n x_n + b_n y_n = N が常に成り立っているので、
a_n x_n = N - b_n y_n が成立します。 a_n = b_{n-1}, y_n = x_{n-1} ですから b_{n-1} x_n = N - b_n x_{n-1} と書き直すことができます。nを2からnまで変化させた等式
b_1 x_2 = N - b_2 x_1\\ b_2 x_3 = N - b_3 x_2\\ \dots\\ b_{n-1} x_n = N - b_n x_{n-1}
について左辺どうし、右辺どうしを掛け合わせると
左辺 = b_1 b_2 \dots b_{n-1} x_2 x_3 \dots x_n 右辺 = N(なんちゃら) \pm b_2 b_3 \dots b_{n} x_1 x_2 \dots x_{n-1}
となります。 \pmod Nで考えれば N(なんちゃら)は0とみなせるので結局
b_1 x_n = \pm b_n x_1 \pmod N
が常に成立していることになります。符号はnの偶奇で決まります。なお b_1, b_2, ... および x_1, x_2, ...がゼロでないという証明が別途必要ですがそれは最後に記します。
ところで、b_nはいずれ1になります。x_1 = 1, b_1 = nでしたから、その時点で
n x_n = \pm 1
となり x_nが求める解になります。このアルゴリズムは途中の計算結果を保存しておかなくてよいという利点があり一つ前のエントリより優れていると言えましょう。
#!/usr/bin/env ruby
n = a = 916132831
b0 = b = 9973
x = 1
y = 0
sign = 1
while b != 1
d = a / b
m = a % b
a = b
b = m
tmp = d * x + y
y = x
x = tmp
sign = -sign
end
puts("#{sign * x} * #{b0} = #{b0 * x * sign % n} (mod #{n})")
さて、このアルゴリズムでも十分実用になるのですが、Nが奇数の場合はさらに効率よく求めることができます。 そのアルゴリズムを次のエントリで紹介しようと思います。
b_1, b_2, ... および x_1, x_2, ...がゼロでないという証明
b_nについては単調に減少し1になったところでアルゴリズムは止まるので0でないことは明らか。 x_nについては次の補題を利用します。
\hbox{gcd}(a_{n+1}, b_{n+1}) = \hbox{gcd}(a_n, b_n)\\ \hbox{gcd}(x_{n+1}, y_{n+1}) = \hbox{gcd}(x_n, y_n)\\ が成立する
この補題より\hbox{gcd}(x_n, y_n) = \hbox{gcd}(x_1, y_1) = 1であり、したがって x_n=0ならばy_n=1でならねばならず それはb_n=0でなければならないので矛盾
と書くと何やら難しそうですが、簡単にいうならばnという整数が与えられたとき
nx = 1 \pmod N
となる数xを見つけようということです。このような逆数はnとNが互いに素なら必ずあります。法Nにおける逆数は整数の剰余の計算においてとても重要であり暗号の分野で頻繁に使われています。
(多くの)プログラム言語的に言い換えると
(n * x) % N = 1
となる数ということになるでしょう。ここで 細かいことをいうとnが負数のときどうなるん?という問題があります。例えばperlやrubyだと nが負の場合は(例えば -1 % 7)は 6を返しますが、CやNode.jsは-1を帰すようです。 ちなみに、数学的にはどちらでも問題なしです。何故どちらでもいいかを語り始めると長いので細かいことは有限体の本でも読んでもらうとして、結論から言うと
-1 = 6 \pmod 7
と思っておけば問題ないです。7を法とした世界では-1も6も同じものを指します。表現の仕方が違うだけです。
さてこのアプローチには大きく2種類あるようです。 一つはユークリッドの互除法を使うもの。もう一つはopensslの内部て使われているものです。後者のアルゴリズムに名前があるのかどうかはしりません。
最初にユークリッドの互除法を使う手法を紹介します。
ユークリッドの互除法とは数a, bが与えられたときにその最大公約数を求める手法ですが、その解を求める課程を利用して
ax + by = 1
を解くことができるのです。実際ここで a=N, b=nとして両辺をNで割ったあまりをみれば
by = 1 \pmod N
ですからyが求めるものであることがわかります。ちなみに上の式はa, bが互いに素なら(つまりGCD(a,b)=1ならば必ず解を持つことが知られています。
さてそのアルゴリズムですが、このアルゴリズムをを一般式で書くと煩雑になるので具体的に a = (N = )916132831, b = (n = ) 9973 として説明します。 まずaをbで割った余りと商を利用して以下のように変形します。
916132831x + 9973y\\ = (91861\times 9973 + 3078)x + 9973y\\ = 3078x + 9973(91861x + y)
ここで x' = 91861x + y\\ y' = x とすれば 9973x' + 3078y' という式が得られます。商と余りを利用してより小さい係数の式を得ることができました。
a_1, b_1を正の整数でa_1 > b_1としa_n, b_n, x_n, y_nを以下のように定義する a_{n+1} = b_n\\ b_{n+1} = m_n\\ x_{n+1} = d_n x_n + y_n\\ y_{n+1} = x_n ただしd_nはm_nは a_n = d_n b_n + m_n (0 \le m_n < b_n) を満たす整数とするとa_n > b_nであり a_1 x_1 + b_1 y_1 = a_n x_n + b_n y_n が成立する。
このように書くと何やら複雑ですがプログラム的には
a[n+1] = b[n]
b[n+1] = a[n] % b[n]
ということです。このようにa_n, b_nだけを計算していくといつかはa_nは1に、b_nは0になります。そのとき
x_n = 1\\ y_n = 0 とすると、 a_n x_n + b_n y_n = 1 を満たしますから、 これから逆に、x_{n-1}, y_{n-1}が求まってゆき、最終的にy_1が求まります。 これが求める答えになります。 例として、a_1=916132831とb_1=9973で計算していくと以下のようになります。
# a b d m 1 916132831 9973 91861 3078 2 9973 3078 3 739 3 3078 739 4 122 4 739 122 6 7 5 122 7 17 3 6 7 3 2 1 7 3 1 3 0 8 1 0
これからx_8=1, y_8=0が求まります。 これからx_7, y_7, x_6, y_6, ...と計算していくと
# x y 7 0 1 6 1 -2 5 -2 35 4 35 -212 3 -212 883 2 883 -2861 1 -2861 262815204
となり、y_1 = 262815204が求まります。
実際 9973 \times 262815204 = 1 \pmod Nです。
#!/usr/bin/env ruby
a = []
b = []
d = []
m = []
x = []
y = []
n = a[1] = 916132831
b[1] = 9973
i = 1
while b[i] != 0
d[i] = a[i] / b[i]
m[i] = a[i] % b[i]
a[i + 1] = b[i]
b[i + 1] = m[i]
i = i + 1
end
x[i] = 1
y[i] = 0
j = i
while j > 1
j = j - 1
x[j] = y[j + 1]
y[j] = x[j + 1] - d[j] * x[j]
end
puts("#{y[1]} * #{b[1]} = #{b[1] * y[1] % n} (mod #{n})")
ユークリッドの互除法はとても早く1まで係数が降下するので効率もよいのですがa_n, b_nの途中の計算結果を保存しておかないといけないという問題があります。また、d, mを求めるのに商計算をしなくてはなりません。 一般的に商の計算はとても遅く、特にNが数1000bitともなるとその計算がオーバヘッドとなるので、出来ることなら使いたくないというのが本音です。そこでより効率のよいアルゴリズムがopensslでは使われています。それを次回に紹介したいと思います。
三角関数使って表記すれば半径1の円に内接する正N角形の一辺の長さは
2\sin(\pi/N)
なんですが、この値は具体的に加減乗除と累乗根を使って表すことができます。 それを計算してみようという試み。
2\sin(\pi/3) = \sqrt{3}
2\sin(\pi/4) = \sqrt{2}
2\sin(\pi/6) = 1
ここまでは図を書けば殆ど自明です。正8角形、正12角形は半角の公式を使うと比較的簡単に求まります。半角の公式とは
\sin^2(\alpha/2) = \frac{1−\cos\alpha}{2}
です。
\begin{eqnarray*} 2\sin(\pi/8) = 2\sqrt{\frac{1-\cos(\pi/4)}{2}}\\ = \sqrt{2-\sqrt{2}} \end{eqnarray*}
\begin{eqnarray*} 2\sin(\pi/12) = 2\sqrt{\frac{1-\cos(\pi/6)}{2}}\\ = \sqrt{2-\sqrt{3}} \end{eqnarray*}
根号の中に根号になりますね。この先、16, 32, 64または24, 48, ..と増えていっても、半角の公式でどんどん数が求まっていきます。もちろん増える度に根号は深くなります、こんな風に辺の数が増えていくと、根号がどんどん深くなると予想されるのですが、正10角形は比較的シンプルな値になります。
2\sin(\pi/10) = \frac{-1+\sqrt{5}}{2}
となります。正5角形よりシンプル。 実は正10角形は正5角形の作図がどうやって行われるかが理解できていると簡単に求めることができます。
一般にx^n-1の根を複素平面にプロットすると単位円に内接する正n角形の頂点となります。正10角形をプロットすると図のようになり、 一辺の長さは図のP_1Qになりますが、 これがP_1+P_4と原点Oからの距離(|P_1+P_4|)に等しいことが見て取れると思います。
P_1, P_2, P_3, P_4は正5角形の頂点、すなわちx^5-1の根です。これには1という自明な根があるので因数分解ができて、
x^5-1 = (x-1)(x^4+x^3+x^2+x+1)
この第2項
\begin{eqnarray} x^4+x^3+x^2+x+1 \hbox{ (★)} \end{eqnarray}
の根ということになります。その根の1つを\zetaとすれば、すべての根は
\zeta, \zeta^2, \zeta^3, \zeta^4
となります。また\zetaは★の根ですから\zeta+\zeta^2+\zeta^3+\zeta^4 = -1になります。 ここではP_1=\zetaとします。 今
\begin{eqnarray*} X = \zeta + \zeta^4\\ Y = \zeta^2 + \zeta^3 \end{eqnarray*}
とすれば
\begin{eqnarray*} X+Y = \zeta+\zeta^2+\zeta^3+\zeta^4 = -1\\ XY = \zeta+\zeta^2+\zeta^3+\zeta^4 = -1 \end{eqnarray*}
ですから(\zeta^5 = 1に注意)X, Yは
x^2 + x - 1
の根。よって
X = \frac{-1 + \sqrt{5}}{2}
となります。XがP_1+P_4なのは言わずもがなです。これから次の有名な問題を証明できます。
2\pi > 10 \times \frac{-1 + \sqrt{5}}{2} = 6.180... > 6.1
模範解答では、正8角形や正12角形を使うものが多いわけですが、こちらの方がより根号の数が少なく計算が簡単ですね。\sqrt{5}=2.236...というのを知っていればいいわけで。
なお余談ながら
1: |P_2+P_3| |P_1+P_4|:1
はいわゆる黄金比(1:1.618....)です。こんな計算のやりかたを知らなくても正10角形には黄金比が隠れていることを知っていれば3.05より大きいのは直ぐにわかるでしょう。いきなり正10角形の辺の長さは黄金比1.618:1だから・・・と書き始めたら正解になるかどうかはわかりませんが。
Texの文章そのままコピペでいいのは楽ですね。コミケのときに書いた原稿をそのままコピペ。
\alpha_{0} = \left(-1 + \sqrt{17}\right)/2 \alpha_{1} = \left(-1 - \sqrt{17}\right)/2 \beta_{0} = \left(\alpha_{0} + \sqrt{\alpha_{0}^2 +4}\right)/2 \beta_{1} = \left(\alpha_{1} + \sqrt{\alpha_{1}^2 +4}\right)/2 \gamma_{0} = \left(\beta_{0} + \sqrt{\beta_{0}^2 - 4\beta_{1}}\right)/2 \delta_{0} = \left(\gamma_{0} + \sqrt{\gamma_{0}^2 - 4}\right)/2 \delta_{0}^{17} = 1
ちなみに、ためしに正65537角形の式を載せたらブラウザ固まりました(式長すぎたか?)
Tex形式で数式を書けばJavaScriptが勝手に数式をほげほげして見やすい形式に変更してくれるというシステム"MathJax" 数式をWebに書きたいという要求は凄いな。 一瞬Texのソースが見えるのがアレですけど。
ただこれちょっとシステムが複雑すぎませんかね?複雑・多機能なシステムは更新が大変。最低限要求されていることはもっとシンプルな気がする。
\int_a^b f(x) dx=F(b)-F(a)
\mathbf{V}_1 \times \mathbf{V}_2 = \begin{vmatrix} \mathbf{i} & \mathbf{j} & \mathbf{k} \\ \frac{\partial X}{\partial u} & \frac{\partial Y}{\partial u} & 0 \\ \frac{\partial X}{\partial v} & \frac{\partial Y}{\partial v} & 0 \end{vmatrix}
参考までに、同じものをmimetexで書くとこんな感じになります。 見た目は圧倒的にMathJaxのほうがよいですね。
Powered by Red Leaf ( Rev. 4d249636d ), © Issei Numata, 2007-2025