前回の記事「素数にも“リズム”がある?──間隔に注目した素数のビート」では、素数同士の出現間隔(ギャップ)に注目し、その分布が一見ランダムでありながら、どこか規則性を感じさせる“素数のビート”を探求しましたね。
素数の並びの中では2
、4
、6
といった間隔が頻繁に現れましたが、その中でもひときわシンプルで美しいのが、間隔2
です。今回は、この特別な間隔で隣り合う素数のペアに、ぐっと焦点を当ててみましょう。
まるで仲良く肩を並べて歩く双子のように現れるこのペアは、数学者たちを長年魅了し続けている謎に満ちた存在です。
素数が奏でる”2拍子”のリズムの謎に迫ってみましょう。
双子素数 – “2つで1組”の特別な関係
まずはこの「2」という間隔で隣り合う素数のペアに名前をつけましょう。
差が2である2つの素数の組(p,p+2)を双子素数と呼ぶ。
具体例を見ると、イメージが湧きやすいでしょう。
- (3 , 5)
- (5 , 7)
- (11 , 13)
- (17 , 19)
- (29 , 31) など
こうして並べてみると、本当に双子のように素数が現れるのが面白いですよね。そして、この双子素数をじっと眺めていると、ある美しい性質が隠れていることに気づきます
(3 , 5)のペアを除き、双子素数の間にある数は6の倍数になる。
上の例で見てみましょう。
(5 , 7)の間は6。
(11 , 13)の間は12。
(17, 19)の間は18。
(29, 31)の間は30。
本当ですね。
これは一体なぜなのでしょうか?少しだけ数学の世界を覗いて、この秘密を解き明かしてみましょう。
では、上の性質を証明しましょう。
pを3より大の素数とします。
この時、p = 6k + 1、またはp = 6k – 1となります。
※これは前回の記事「素数にも“リズム”がある?──間隔に注目した素数のビート」で紹介しました。
この2つのパターンに分けて、相方のp+2がどうなるかを見ていきます。
■パターン1 : p = 6k + 1の場合
pの相方p + 2は、
p + 2 = 6k + 3 = 3(2k + 1)
となります。
つまり、p + 2は3の倍数となり素数ではないので、このパターンから双子素数が生まれることはありません。
■パターン2 : p = 6k – 1の場合
pの相方p + 2は、
p + 2 = 6k + 1
となります。
この場合、pとp +2どちらも素数である可能性が残ります。
もし、どちらも素数だった場合に(p,p+2)は双子素数になります。
双子素数(p,p+2)の間の数p+1はp+1 = 6kとなり6の倍数であることが分かります。
よって、「(3 , 5)のペアを除き、双子素数の間にある数は6の倍数になる」という性質が示されました。
定義から始める双子素数探索 ─ PythonとRustで実装してみよう
ここでは、「完全数を見つけるプログラムをもっと速く!メルセンヌ素数で改善してみよう」などの記事で使った素数判定の関数を再利用し、双子素数 (p,p+2)を定義に基づいて列挙するプログラムを作成してみます。
まずはPythonでプログラムを作ります。
# 素数判定を行う関数 def is_prime(n): # 2未満の数は素数ではない if n < 2: return False # (*1) 2から√nまでの数で割り切れるか調べる for i in range(2, int(n**0.5) + 1): if n % i == 0: # 割り切れたら素数ではない return False # 割り切れる数がなければ素数 return True def find_twin_primes(limit): """limit 以下の双子素数 (p, p+2) をリストアップする関数""" twin_primes = [] # pとp+2を調べるので、2からlimit-2までループ for p in range(2, limit - 1): if is_prime(p) and is_prime(p + 2): # pとp+2どちらも素数なら双子素数 twin_primes.append((p, p + 2)) return twin_primes if __name__ == '__main__': limit = 10000 twin_primes = find_twin_primes(limit) print(f"{limit} 以下の双子素数の個数 : {len(twin_primes)}") print(f"{limit} 以下の双子素数: {twin_primes}")
このプログラムを実行すると以下のようになります。
10000 以下の双子素数の個数 : 205 10000 以下の双子素数: [(3, 5), (5, 7), (11, 13), (17, 19), (29, 31), (41, 43), (59, 61), (71, 73), (101, 103), (107, 109), (137, 139), (149, 151), ・・・(省略)]
このプログラムでは、2 からlimit−2までの数pに対して、pとp+2がどちらも素数であれば双子素数と判定し、そのペアをリストに追加しています。定義そのものをコードに落とし込んだ素直な実装です。
前回の記事「素数にも“リズム”がある?──間隔に注目した素数のビート」でも紹介した primerange
を使うと、素数列を一括で生成できるため、より簡潔に双子素数を抽出できます。
from sympy import primerange def find_twin_primes(limit): """limit 以下の双子素数 (p, p+2) をリストアップする関数""" primes = list(primerange(2, limit+1)) # 2からlimitまでの素数を生成 twin_primes = [] # 双子素数のペアを保存するリスト # iとi+1の素数の差分を比較するので、リストの個数-1までループ for i in range(len(primes) - 1): if primes[i+1] - primes[i] == 2: # 差分が2だったら双子素数 twin_primes.append((primes[i], primes[i+1])) return twin_primes if __name__ == '__main__': limit = 10000 twin_primes = find_twin_primes(limit) print(f"{limit} 以下の双子素数の個数 : {len(twin_primes)}") print(f"{limit} 以下の双子素数: {twin_primes}")
次にRustで作成します。
// 素数判定を行う関数 fn is_prime(n: u32) -> bool { // 2未満の数は素数ではない if n < 2 { return false; } // 2から√nまでの数で割り切れるか調べる let sqrt_n = (n as f64).sqrt() as u32; for i in 2..=sqrt_n { if n % i == 0 { // 割り切れたら素数ではない return false; } } // 割り切れる数がなければ素数 true } // limit 以下の双子素数 (p, p+2) をリストアップする関数 fn find_twin_primes(limit: u32) -> Vec<(u32, u32)> { let mut twin_primes = Vec::new(); // pとp+2を調べるので、2からlimit-2までループ for p in 2..=limit - 2 { if is_prime(p) && is_prime(p + 2) { twin_primes.push((p, p + 2)); } } twin_primes } fn main() { let limit: u32 = 10000; let twin_primes = find_twin_primes(limit); println!("{} 以下の双子素数の個数 : {}", limit, twin_primes.len()); println!("{} 以下の双子素数: {:?}", limit, twin_primes); }
Python版とほぼ同じロジックを、Rustの構文に沿って実装したものです。is_prime
関数と find_twin_primes
関数を定義し、定義に忠実な方法で双子素数を探索しています。
双子素数の判定法 – クレメンスの双子素数判定法
クレメンスの双子素数判定法を紹介する前に、1つ数学的な記号を導入しましょう。
aをmで割った余りと、bをmで割った余りが等しい時、
a \equiv b ~ \pmod m
と書く。
このような式を合同式といい、「aとbはmを法として合同である」という。
この合同式の定義は、次のように言い換えることもできます:
a \equiv b \pmod m \iff a - b \textrm{はmの倍数}
なぜなら、
a = mx + r、b = my + rのように書ける時、
a – b = m (x – y )となり、これはmの倍数になるからです。
合同式のイメージをつかむために例をあげましょう
- 4 \equiv 1 \pmod 3
- 17 \equiv 5 \pmod 12
- 7 \equiv 1 \pmod 3
では、いよいよクレメンスの定理(Clement, 1949)を紹介します。
整数n \geq 1に対して、(n, n+2)が双子素数であるための必要十分条件は次の合同式が成り立つことです:
4((n-1)! + 1) + n \equiv 0 \mod n(n+2)
この定理の証明には、別の記事で紹介予定の「ウィルソンの定理」という素数判定に関する有名な定理が使われます。
クレメンスの定理はその美しい数式構造ゆえに注目されますが、
一方で(n-1)!の計算が非常に重くなるため、実用的な素数判定法としては非効率です。
現代のコンピュータをもってしても、大きなnに対しては高速とはいえません。
以下のコードでは、クレメンスの双子素数判定法に基づいて双子素数を判定・列挙しています。
import math def is_twin_prime_clement(n): """ クレメンスによる双子素数判定 (n, n+2) が双子素数である ⇔ 4((n-1)! + 1) + n ≡ 0 mod n(n+2) """ #if n < 3 or n % 2 == 0: #return False # nは3以上の奇数である必要がある factorial_part = math.factorial(n-1) # (n-1)!を計算 lhs = 4 * (factorial_part + 1) + n # クレメンスの判定法の左辺を計算 modulus = n * (n + 2) # 割る数を計算 return lhs % modulus == 0 def find_twin_primes(limit): """limit 未満の双子素数 (p, p+2) をリストアップする関数""" twin_primes = [] # 1は素数ではないので、2以上からlimit未満まで調べる for p in range(2, limit): if is_twin_prime_clement(p): # クレメンスの判定法を使用して、双子素数であればリストに追加 twin_primes.append((p, p + 2)) return twin_primes if __name__ == '__main__': limit = 10000 twin_primes = find_twin_primes(limit) print(f"{limit} 以下の双子素数の個数 : {len(twin_primes)}") print(f"{limit} 以下の双子素数: {twin_primes}")
このコードでは、is_twin_prime_clement
関数がクレメンスの合同式を計算して、
その結果が合同(\equiv)を満たすかどうかを判定しています。math.factorial
関数は Python 標準ライブラリ math
モジュールに含まれており、引数として与えた整数の階乗を返します。
■実行時間の比較
- 定義ベースの双子素数判定:約 0.016 秒
- クレメンスの双子素数判定法による判定:約 8.19 秒
この差は、階乗計算にかかるコストの大きさによるものです。
クレメンスの定理は数学的には非常に美しく、かつ必要十分条件を与える貴重な定理ですが、高速な判定を目的とするには不向きです。
クレメンスの定理を通して、「理論的な美しさ」と「計算上の実用性」が一致するとは限らないことが実感できます。
こうしたギャップこそが、数学を単なる道具以上のものにしてくれる面白さでもあります。
「双子素数予想」 – 数学最大の謎の1つ –
素数が無限に存在することは、古代ギリシャの偉大な数学者ユークリッドによって証明された、美しくも揺るぎない事実です。(詳しくは「素数っていくつあるの?」にて)
では、その特別なペアである「双子素数」も、同じように無限に存在するのでしょうか?
このシンプルな問いこそ、数学の歴史上、最も深く、そして難解な謎の一つです。
驚くべきことに、その答えは——
「まだ、誰も知らない」のです。
世界中の数学者が「双子素数は無限に存在するはずだ」と固く信じています。しかし、2000年以上の時を経た今も、その証明は誰一人として成し遂げていません。
これこそが、数学における最も有名な未解決問題の一つ、「双子素数予想」です。
なぜ、「双子素数予想」の証明はこれほど難しいのでしょう?
ユークリッドの「素数が無限にある」ということの証明は、既存の素数から新しい素数を作り出すという、比較的シンプルなものでした。
しかし、その方法では新しく見つかった素数が「どこに」出現するかまでは分かりません。
すぐ隣に別の素数がいる保証は全くありません。
数が大きくなるほど、素数の出現密度は低くなるため、間隔が「2」という奇跡的なペアが無限に続くことを示すのは、全く次元が違う難しさです。
21世紀の衝撃! 張益唐 (Yitang Zhang)による歴史的ブレイクスルー
古くから知られていたとされる「双子素数予想」ですが、長い間まったく進展がありませんでした。
多くの数学者が、「これはもしかすると、証明不可能かもしれない」とさえ感じていたほどです。
そんな閉塞感の中で、2013年に一人の数学者が世界を震撼させます。
その名は──張益唐(Yitang Zhang)。
彼が発表した論文の中で示した結果は、こうです:
「間隔が7000万以下の素数ペアは無限に存在する」
これは、双子素数(間隔が2の素数ペア)が無限に存在することを直接証明したわけではありません。
ですので、「えっ、結局、双子素数予想は解決してないの?」と思う人もいるかもしれません。
しかし、この結果が持つ意味は非常に深く、数学界にとって歴史的なブレイクスルーでした。
それまでの数学者たちは、ゴールが見えないマラソンを走っているような状態でした。
「間隔2の素数ペアが無限にある」というゴールを目指してはいるものの、はるか手前にありそうな「間隔100の素数ペアが無限にある」や「間隔10000の素数ペアが無限にある」さえ誰も証明できていなかったのです。
そもそも、ゴールが存在するのかすら分からない。
「数が大きくなると素数はどんどん離れてしまい、もう二度と近づかないのでは…」という不安さえありました。
そこに突如現れたのが、張益唐博士の結果です。
「どんなに大きな数の世界に行っても、”7000万”より狭い間隔の素数ペアは絶対に無限に存在する!」
このインパクトは計り知れません。
マラソンにたとえれば、こうです:
「ゴールテープは確かにある!今までは見えなかったけど、ついにその存在を誰かが示した!」
つまり、それまで闇雲に走っていた状態から、
「よし、まずは7000万の位置にあるゴールテープを見つけた。
あとは皆で、それを“2”の位置(双子素数)まで少しずつ引き寄せていこう!」という流れに変わったのです。
これまで数学者たちは、暗闇の中を手探りで進んでいました。
そこに突然、確かな道筋を照らす光が差し込んだのです。
だからこそ、この論文は数学界を揺るがす大ニュースとなりました。
張益唐博士の結果は、双子素数予想の解決に向けた第一歩。
それはまさに、21世紀の数学が踏み出した歴史的な一歩だったのです。
「双子素数予想」に関するその後の進展
張益唐博士の成果は、数学界に大きな希望をもたらしました。
その流れを受けて、テレンス・タオ(Terence Tao)をはじめとする世界的な数学者たちが協力を呼びかけ、
「素数の間隔をどこまで小さくできるか?」
をテーマにした共同研究プロジェクトが立ち上がります。
それが、ポリマス・プロジェクト(Polymath Project)です。
このプロジェクトは、オンライン上で世界中の数学者が議論・検証・証明を分担しながら進めるという、これまでにないスタイルの協働型数学研究でした。
その成果は驚くべきもので、張博士が示した「7000万」という間隔は、
わずか1年ほどで「246」まで縮められるという快挙が成し遂げられました。
また、その後の研究では「エリオット・ハルバーシュタム予想(Elliott–Halberstam conjecture)」というものが正しいと仮定すれば、
「間隔が12以下の素数ペアが無限に存在する」
ことを示すことも可能であるとされています。
つまり、未解決とはいえ「双子素数予想」(間隔2の素数ペアが無限にあるか)に、
理論的にはぐっと近づいたのです。
まだ「2」という目標には到達していませんが、
長年停滞していたこの難問は、張益唐という1人の数学者の登場をきっかけに、
世界中の数学者の協力によって、劇的に前進しはじめました。
「1人のひらめき」と「多くの知恵」が結びついたこの現代的な進展こそ、
21世紀の数学の新しい可能性を示しているといえるでしょう。
まとめ
本記事では、以下のような数学の魅力的な旅路をたどりました:
- 双子素数の定義と性質:差が 2 の素数ペアとは何か。「6の倍数 ±1」型という構造から、その中の謎を探求しました。
- 合同式とClementの双子素数判定法:双子素数を構造的に判定できる美しい数学的定理を紹介し、Python で実装しました。
- プログラムによる比較:定義ベースと階乗を使った判定法の実行速度の違いから、数学的美しさと実用性のギャップを体験しました。
- 現代の飛躍的進展:張益唐博士による「間隔 \leq70,000,000 の素数ペアは無限に存在する」という衝撃的発見から始まり、Polymath プロジェクトによる 「246」への大幅な改善までを追いました。