5

今日、Rabin-Miller Strong Pseudoprime Test を実装しようとしています。

Wolfram Mathworldを参照として使用しました。3行目から 5 行目までが私のコードの要約です。

ただし、プログラムを実行すると、素数 (5、7、11 などの低い値であっても) は素数ではないと (時々) 表示されます。私は非常に長い間コードを調べてきましたが、何が間違っているのかわかりません。

助けを求めて、私はこのサイトと他の多くのサイトを見てきましたが、ほとんどが別の定義を使用しています (おそらく同じですが、私はこの種の数学に慣れていないため、同じ明白な関係はわかりません)。

私のコード:

import random

def RabinMiller(n, k):

    # obviously not prime
    if n < 2 or n % 2 == 0:
        return False

    # special case        
    if n == 2:
        return True

    s = 0
    r = n - 1

    # factor n - 1 as 2^(r)*s
    while r % 2 == 0:
        s = s + 1
        r = r // 2  # floor

    # k = accuracy
    for i in range(k):
        a = random.randrange(1, n)

        # a^(s) mod n = 1?
        if pow(a, s, n) == 1:
            return True

        # a^(2^(j) * s) mod n = -1 mod n?
        for j in range(r):
            if pow(a, 2**j*s, n) == -1 % n:
                return True

    return False

print(RabinMiller(7, 5))

これは、Mathworld で与えられた定義とどう違うのでしょうか?

4

5 に答える 5

6

1. コードに関するコメント

以下で説明するポイントの多くは他の回答に記載されていますが、それらをすべてまとめておくと便利です。

  1. セクションでは

    s = 0
    r = n - 1
    
    # factor n - 1 as 2^(r)*s
    while r % 2 == 0:
        s = s + 1
        r = r // 2  # floor
    

    rs の役割が入れ替わっています。実際にはn − 1 を 2 s rとして因数分解しています。MathWorld 表記法に固執したい場合は、コードのこのセクションでrandを交換する必要があります。s

    # factor n - 1 as 2^(r)*s, where s is odd.
    r, s = 0, n - 1
    while s % 2 == 0:
        r += 1
        s //= 2
    
  2. ラインで

    for i in range(k):
    

    変数iは使用されていません: そのような変数に名前を付けるのは慣例です_

  3. 1 からn − 1 までのランダムなベースを選択します。

    a = random.randrange(1, n)
    

    これは MathWorld の記事で述べられていることですが、その記事は数学者の視点から書かれています。実際、1 s = 1 (mod n ) であるため、基数 1 を選択しても意味がありません。試行を無駄にすることになります。同様に、基数n − 1 を選ぶのは無意味です。なぜなら、sは奇数であり、したがって ( n − 1) s = −1 (mod n ) だからです。数学者は無駄な試行を心配する必要はありませんが、プログラマーは心配するので、代わりに次のように記述します。

    a = random.randrange(2, n - 1)
    

    (この最適化が機能するには、 nは少なくとも 4 である必要がありますが、 n = 2 の場合と同様に、 n = 3Trueの場合は関数の先頭に戻ることで簡単に調整できます。)

  4. 他の返信で指摘されているように、あなたは MathWorld の記事を誤解しています。" nがテストに合格する" というのは、" nが基数aのテストに合格する" という意味です。素数に関する際立った事実は、素数がすべての基数のテストに合格することです。したがって、a s = 1 (mod n ) であることがわかったら、ループを一周して、テストする次のベースを選択する必要があります。

    # a^(s) = 1 (mod n)?
    x = pow(a, s, n)
    if x == 1:
        continue
    
  5. ここに最適化の機会があります。計算したばかりの値x2 0 s (mod n ) です。したがって、すぐにテストして、ループの繰り返しを 1 回節約できます。

    # a^(s) = ±1 (mod n)?
    x = pow(a, s, n)
    if x == 1 or x == n - 1:
        continue
    
  6. a 2 j s (mod n )を計算するセクションでは、これらの各数値は前の数値の 2 乗 (モジュロn ) です。前の値を 2 乗するだけで済むのに、それぞれをゼロから計算するのはもったいないです。したがって、このループを次のように記述する必要があります。

    # a^(2^(j) * s) = -1 (mod n)?
    for _ in range(r - 1):
        x = pow(x, 2, n)
        if x == n - 1:
            break
    else:
        return False
    
  7. Miller–Rabin を試す前に、小さな素数で割り切れるかどうかをテストすることをお勧めします。たとえば、Rabin の 1977 年の論文で、彼は次のように述べています。

    アルゴリズムを実装する際に、いくつかの省力化手順を組み込みます。まず、任意の素数p < Nで割り切れるかどうかをテストします。ここで、N = 1000 とします。

2. 改訂されたコード

これらすべてをまとめると、次のようになります。

from random import randrange

small_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31] # etc.

def probably_prime(n, k):
    """Return True if n passes k rounds of the Miller-Rabin primality
    test (and is probably prime). Return False if n is proved to be
    composite.

    """
    if n < 2: return False
    for p in small_primes:
        if n < p * p: return True
        if n % p == 0: return False
    r, s = 0, n - 1
    while s % 2 == 0:
        r += 1
        s //= 2
    for _ in range(k):
        a = randrange(2, n - 1)
        x = pow(a, s, n)
        if x == 1 or x == n - 1:
            continue
        for _ in range(r - 1):
            x = pow(x, 2, n)
            if x == n - 1:
                break
        else:
            return False
    return True
于 2013-01-31T01:21:31.590 に答える
4

Omri Barel が言ったことに加えて、 for ループにも問題があります。テストに合格したtrueものを見つけたら、戻ってきます。aただし、可能性が高い素数になるには、すべてaがテストに合格する必要があります。n

于 2013-01-30T21:19:29.880 に答える
2

これが私のバージョンです:

# miller-rabin pseudoprimality checker

from random import randrange

def isStrongPseudoprime(n, a):
    d, s = n-1, 0
    while d % 2 == 0:
        d, s = d/2, s+1
    t = pow(a, d, n)
    if t == 1:
        return True
    while s > 0:
        if t == n - 1:
            return True
        t, s = pow(t, 2, n), s - 1
    return False

def isPrime(n, k):
    if n % 2 == 0:
        return n == 2
    for i in range(1, k):
        a = randrange(2, n)
        if not isStrongPseudoprime(n, a):
            return False
    return True

素数を使ったプログラミングについてもっと知りたい場合は、私のブログでこのエッセイを控えめに勧めます。

于 2013-01-30T21:52:50.427 に答える
2

私はこのコードについて疑問に思っています:

# factor n - 1 as 2^(r)*s
while r % 2 == 0:
    s = s + 1
    r = r // 2  # floor

取りましょうn = 7。だからn - 1 = 6。と表現できn - 1ます2^1 * 3。この場合r = 1s = 3.

しかし、上記のコードは別のものを見つけます。で始まるr = 6ので、r % 2 == 0. 最初は、s = 01回の反復の後、s = 1と が得られr = 3ます。しかし今r % 2 != 0、ループは終了します。

最終的にs = 1andになりますが、r = 3これは明らかに正しくありません: 2^r * s = 8.

sループ内で更新しないでください。代わりに、2 で割れる回数を数えてください (これは になりますr)。除算後の結果は になりますs。、 の例ではn = 7n - 1 = 6それを 1 回 (so r = 1) 分割でき、分割後は 3 (so s = 3) になります。

于 2013-01-30T21:05:39.863 に答える
1

ウィキペディアも参照してください。ここでは、既知の「ランダムな」シーケンスが特定の素数まで保証された答えを提供します。

  • n < 1,373,653 の場合、a = 2 と 3 をテストするだけで十分です。
  • n < 9,080,191 の場合、a = 31 と 73 をテストするだけで十分です。
  • n < 4,759,123,141 の場合、a = 2、7、および 61 をテストするだけで十分です。
  • n < 2,152,302,898,747 の場合、a = 2、3、5、7、および 11 をテストするだけで十分です。
  • n < 3,474,749,660,383 の場合、a = 2、3、5、7、11、および 13 をテストするだけで十分です。
  • n < 341,550,071,728,321 の場合、a = 2、3、5、7、11、13、および 17 をテストするだけで十分です。
于 2013-01-31T20:48:55.707 に答える