web-dev-qa-db-ja.com

素数Nが与えられたら、次の素数を計算しますか?

同僚から、C#辞書コレクションはハッシュに関連する難解な理由で素数でサイズ変更されると言われました。私の直近の質問は、「次の素数がどのようにわかるのか、巨大なテーブルやその場で計算するのか、サイズ変更の原因となる挿入時の恐ろしい非決定論的ランタイム」でした。

だから私の質問は、Nが素数であるとすると、次の素数を計算する最も効率的な方法は何ですか?

67
John Shedletsky

連続する素数間のギャップ は非常に小さいことが知られており、最初の100以上のギャップは素数370261で発生します。 O(ln(p)* sqrt(p))平均。

P = 10,000の場合、O(921)操作です。これは、O(ln(p))挿入(おおよそ言えば)、これはほとんどの最新のハードウェアでミリ秒のオーダーで発生するほとんどの問題の制約内にあります。

34
marcog

約1年前、C++ 11の順序付けられていない(ハッシュ)コンテナーを実装する際に libc ++ でこの領域を操作していました。ここで自分の経験を共有すると思いました。この経験は marcogの受け入れられた答え をサポートします合理的「ブルートフォース」の定義。

つまり、ほとんどの場合、単純なブルートフォースでも十分に高速であり、平均でO(ln(p)* sqrt(p))を使用します。

size_t next_prime(size_t n)の実装をいくつか開発しました。この関数の仕様は次のとおりです。

Returns:n以上の最小の素数。

_next_prime_の各実装には、ヘルパー関数_is_prime_が付随します。 _is_prime_は、プライベートな実装の詳細と見なされる必要があります。クライアントから直接呼び出されることを意図していません。これらの実装はそれぞれ、正確性についてテストされていますが、次のパフォーマンステストでもテストされています。

_int main()
{
    typedef std::chrono::high_resolution_clock Clock;
    typedef std::chrono::duration<double, std::milli> ms;
    Clock::time_point t0 = Clock::now();

    std::size_t n = 100000000;
    std::size_t e = 100000;
    for (std::size_t i = 0; i < e; ++i)
        n = next_prime(n+1);

    Clock::time_point t1 = Clock::now();
    std::cout << e/ms(t1-t0).count() << " primes/millisecond\n";
    return n;
}
_

これはパフォーマンステストであり、一般的な使用法を反映していないことを強調する必要があります。

_// Overflow checking not shown for clarity purposes
n = next_prime(2*n + 1);
_

すべてのパフォーマンステストは以下でコンパイルされました。

_clang++ -stdlib=libc++ -O3 main.cpp
_

実装1

7つの実装があります。最初の実装を表示する目的は、sqrt(x)で因子の候補素数xのテストを停止できなかった場合、次のように分類できる実装を達成することさえできなかったことを示すことです強引な。この実装は、非常に遅いです。

_bool
is_prime(std::size_t x)
{
    if (x < 2)
        return false;
    for (std::size_t i = 2; i < x; ++i)
    {
        if (x % i == 0)
            return false;
    }
    return true;
}

std::size_t
next_prime(std::size_t x)
{
    for (; !is_prime(x); ++x)
        ;
    return x;
}
_

この実装の場合のみ、妥当な実行時間を得るために、eを100000ではなく100に設定する必要がありました。

_0.0015282 primes/millisecond
_

実装2

この実装はbrute force実装の中で最も遅く、実装1との唯一の違いは、因子がsqrt(x)を超えると素数のテストを停止することです。

_bool
is_prime(std::size_t x)
{
    if (x < 2)
        return false;
    for (std::size_t i = 2; true; ++i)
    {
        std::size_t q = x / i;
        if (q < i)
            return true;
        if (x % i == 0)
            return false;
    }
    return true;
}

std::size_t
next_prime(std::size_t x)
{
    for (; !is_prime(x); ++x)
        ;
    return x;
}
_

sqrt(x)は直接計算されませんが、_q < i_によって推測されることに注意してください。これにより、物事が数千倍に高速化されます。

_5.98576 primes/millisecond
_

marcogの予測を検証します。

...これは、ほとんどの最新ハードウェアで1ミリ秒のオーダーで発生するほとんどの問題の制約内にあります。

実装3

_%_演算子の使用を避けることで、(少なくとも使用しているハードウェアで)速度をほぼ2倍にできます。

_bool
is_prime(std::size_t x)
{
    if (x < 2)
        return false;
    for (std::size_t i = 2; true; ++i)
    {
        std::size_t q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
    }
    return true;
}

std::size_t
next_prime(std::size_t x)
{
    for (; !is_prime(x); ++x)
        ;
    return x;
}

11.0512 primes/millisecond
_

実装4

これまでのところ、2が唯一の素数であるという一般的な知識すら使用していません。この実装にはその知識が組み込まれており、速度はほぼ2倍になります。

_bool
is_prime(std::size_t x)
{
    for (std::size_t i = 3; true; i += 2)
    {
        std::size_t q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
    }
    return true;
}

std::size_t
next_prime(std::size_t x)
{
    if (x <= 2)
        return 2;
    if (!(x & 1))
        ++x;
    for (; !is_prime(x); x += 2)
        ;
    return x;
}

21.9846 primes/millisecond
_

実装4は、ほとんどの人が「ブルートフォース」と考えるときに念頭に置いているものです。

実装5

次の式を使用すると、2または3で割り切れないすべての数値を簡単に選択できます。

_6 * k + {1, 5}
_

ここで、k> = 1です。次の実装ではこの式を使用しますが、かわいいxorトリックを使用して実装します。

_bool
is_prime(std::size_t x)
{
    std::size_t o = 4;
    for (std::size_t i = 5; true; i += o)
    {
        std::size_t q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
        o ^= 6;
    }
    return true;
}

std::size_t
next_prime(std::size_t x)
{
    switch (x)
    {
    case 0:
    case 1:
    case 2:
        return 2;
    case 3:
        return 3;
    case 4:
    case 5:
        return 5;
    }
    std::size_t k = x / 6;
    std::size_t i = x - 6 * k;
    std::size_t o = i < 2 ? 1 : 5;
    x = 6 * k + o;
    for (i = (3 + o) / 2; !is_prime(x); x += i)
        i ^= 6;
    return x;
}
_

これは事実上、アルゴリズムが数値の1/2ではなく整数の1/3のみをチェックする必要があることを意味し、パフォーマンステストでは約50%の予想されるスピードアップが示されます。

_32.6167 primes/millisecond
_

実装6

この実装は、実装5の論理的な拡張です。次の式を使用して、2、3、および5で割り切れないすべての数値を計算します。

_30 * k + {1, 7, 11, 13, 17, 19, 23, 29}
_

また、is_prime内の内側のループを展開し、30未満の数を処理するのに役立つ「小さな素数」のリストを作成します。

_static const std::size_t small_primes[] =
{
    2,
    3,
    5,
    7,
    11,
    13,
    17,
    19,
    23,
    29
};

static const std::size_t indices[] =
{
    1,
    7,
    11,
    13,
    17,
    19,
    23,
    29
};

bool
is_prime(std::size_t x)
{
    const size_t N = sizeof(small_primes) / sizeof(small_primes[0]);
    for (std::size_t i = 3; i < N; ++i)
    {
        const std::size_t p = small_primes[i];
        const std::size_t q = x / p;
        if (q < p)
            return true;
        if (x == q * p)
            return false;
    }
    for (std::size_t i = 31; true;)
    {
        std::size_t q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
        i += 6;

        q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
        i += 4;

        q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
        i += 2;

        q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
        i += 4;

        q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
        i += 2;

        q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
        i += 4;

        q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
        i += 6;

        q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
        i += 2;
    }
    return true;
}

std::size_t
next_prime(std::size_t n)
{
    const size_t L = 30;
    const size_t N = sizeof(small_primes) / sizeof(small_primes[0]);
    // If n is small enough, search in small_primes
    if (n <= small_primes[N-1])
        return *std::lower_bound(small_primes, small_primes + N, n);
    // Else n > largest small_primes
    // Start searching list of potential primes: L * k0 + indices[in]
    const size_t M = sizeof(indices) / sizeof(indices[0]);
    // Select first potential prime >= n
    //   Known a-priori n >= L
    size_t k0 = n / L;
    size_t in = std::lower_bound(indices, indices + M, n - k0 * L) - indices;
    n = L * k0 + indices[in];
    while (!is_prime(n))
    {
        if (++in == M)
        {
            ++k0;
            in = 0;
        }
        n = L * k0 + indices[in];
    }
    return n;
}
_

これはほぼ間違いなく「ブルートフォース」を超えており、速度をさらに27.5%向上させて次のことを行うのに適しています。

_41.6026 primes/millisecond
_

実装7

上記のゲームをもう1回繰り返して、2、3、5、7で割り切れない数値の式を作成するのが実用的です。

_210 * k + {1, 11, ...},
_

ソースコードはここには示されていませんが、実装6と非常によく似ています。これは、 libc ++ の順序付けられていないコンテナに実際に使用することを選択した実装です。リンク)。

この最後の反復は、次の14.6%の速度向上に適しています。

_47.685 primes/millisecond
_

このアルゴリズムを使用することで、 libc ++ のハッシュテーブルのクライアントは、状況に最も有益であると判断した任意の素数を選択でき、このアプリケーションのパフォーマンスは十分に許容できます。

74
Howard Hinnant

誰かが好奇心がある場合に備えて:

リフレクターを使用して、.Netは、最大7199369までのハードコードされた〜72プライムのリストを含む静的クラスを使用することを決定しました。潜在的な数のsqrtまでのすべての奇数の試行除算による次の素数。このクラスは不変であり、スレッドセーフです(つまり、より大きな素数は将来の使用のために保存されません)。

43
Paul Wheeler

素敵なトリックは、部分的なふるいを使用することです。たとえば、数値N = 2534536543556に続く次の素数は何ですか?

小さい素数のリストに関してNのモジュラスを確認します。したがって...

mod(2534536543556,[3 5 7 11 13 17 19 23 29 31 37])
ans =
     2     1     3     6     4     1     3     4    22    16    25

Nに続く次の素数は奇数でなければならないことがわかっており、この小さな素数のリストのすべての奇数倍数をすぐに破棄できます。これらのモジュライにより、これらの小さな素数の倍数をふるいにかけることができます。 200までの小さな素数を使用する場合、このスキームを使用して、小さなリストを除き、Nより大きい潜在的な素数のほとんどをすぐに破棄できます。

より明確に、2534536543556を超える素数を探している場合、2で割り切れないため、その値を超える奇数のみを考慮する必要があります。上記の法則は、2534536543556が2 mod 3に一致することを示しています。したがって、2534536543556 + 1は2534536543556 + 7、2534536543556 + 13などでなければならないため、2534536543556 + 1は0 mod 3に一致します。原始性をテストするために、そして試験分割なしで。

同様に、

mod(2534536543556,7) = 3

2534536543556 + 4は0 mod 7に一致することを示しています。もちろん、その数は偶数であるため、無視できます。ただし、2534536543556 + 11は2534536543556 + 25などのように、7で割り切れる奇数です。この場合も、(7で割り切れるので)これらの数字を素数ではなく明確に複合として除外できます。

最大37個までの素数の小さなリストのみを使用して、開始点2534536543556のすぐ後に続くほとんどの数を除外できます。

{2534536543573 , 2534536543579 , 2534536543597}

それらの数のうち、素数ですか?

2534536543573 = 1430239 * 1772107
2534536543579 = 99833 * 25387763

リストの最初の2つの数値の素因数分解を提供する努力をしました。それらが複合であることがわかりますが、素因数は大きいです。もちろん、これは理にかなっています。なぜなら、残っている数が小さな素因数を持たないことを既に保証しているからです。短いリストの3番目(2534536543597)は、実際にはNを超える最初の素数です。ここで説明したふるい分けスキームは、素数であるか、一般に大きな素因数で構成される数になる傾向があります。そのため、次の素数を見つける前に、数個の数だけに素数の明示的なテストを実際に適用する必要がありました。

同様のスキームは、N = 1000000000000000000000000000を超える次の素数を1000000000000000000000000103として迅速に生成します。

12
user85109

素数間距離でのほんの数回の実験。


これは、他の回答を視覚化するための補足です。

100.000番目(= 1,299,709)から200.000番目(= 2,750,159)までの素数を取得しました

一部のデータ:

Maximum interprime distance = 148

Mean interprime distance = 15  

素数距離周波数プロット:

alt text

素数間距離と素数

alt text

ただそれが「ランダム」であることがわかります。 ただし...

12
Dr. belisarius

次の素数を計算する関数f(n)はありません。代わりに、数の素数をテストする必要があります。

また、n番目の素数を見つけるときに、1番目から(n-1)番目までのすべての素数を知ることは非常に便利です。これらは素数としてテストする必要がある唯一の数だからです。

これらの理由の結果、事前に計算された大きな素数のセットが存在する場合、私は驚かないでしょう。特定の素数を繰り返し再計算する必要がある場合、それは本当に意味がありません。

5
Kirk Broadhurst

他の人がすでに指摘したように、現在の素数を与えられた次の素数を見つける手段はまだ見つかっていません。したがって、ほとんどのアルゴリズムは、既知の素数と次の素数の間のn/2の数をチェックする必要があるため、 primality をチェックする高速手段の使用に重点を置いています。

Paul Wheeler で示されているように、アプリケーションによっては、ルックアップテーブルをハードコーディングするだけで済みます。

3
rjzii

斬新さのために、常にこのアプローチがあります:

#!/usr/bin/Perl
for $p ( 2 .. 200  ) {
      next if (1x$p) =~ /^(11+)\1+$/;
      for ($n=1x(1+$p); $n =~ /^(11+)\1+$/; $n.=1) { }
      printf "next prime after %d is %d\n", $p, length($n);
}

もちろんどれが生産する

next prime after 2 is 3
next prime after 3 is 5
next prime after 5 is 7
next prime after 7 is 11
next prime after 11 is 13
next prime after 13 is 17
next prime after 17 is 19
next prime after 19 is 23
next prime after 23 is 29
next prime after 29 is 31
next prime after 31 is 37
next prime after 37 is 41
next prime after 41 is 43
next prime after 43 is 47
next prime after 47 is 53
next prime after 53 is 59
next prime after 59 is 61
next prime after 61 is 67
next prime after 67 is 71
next prime after 71 is 73
next prime after 73 is 79
next prime after 79 is 83
next prime after 83 is 89
next prime after 89 is 97
next prime after 97 is 101
next prime after 101 is 103
next prime after 103 is 107
next prime after 107 is 109
next prime after 109 is 113
next prime after 113 is 127
next prime after 127 is 131
next prime after 131 is 137
next prime after 137 is 139
next prime after 139 is 149
next prime after 149 is 151
next prime after 151 is 157
next prime after 157 is 163
next prime after 163 is 167
next prime after 167 is 173
next prime after 173 is 179
next prime after 179 is 181
next prime after 181 is 191
next prime after 191 is 193
next prime after 193 is 197
next prime after 197 is 199
next prime after 199 is 211

すべての楽しみとゲームは別として、最適なハッシュテーブルサイズは厳密に証明可能の形式の素数であることはよく知られています4N−1。したがって、次の素数を見つけるだけでは不十分です。他のチェックも行う必要があります。

3
tchrist

私が覚えている限りでは、現在のサイズの2倍の隣の素数を使用します。それはその素数を計算しません-いくつかの大きな値までプリロードされた数のテーブルがあります(正確には、約10,000,000程度)。その数に達すると、単純なアルゴリズムを使用して次の番号(curNum = curNum + 1など)を取得し、これらのメソッドの一部を使用して検証します。 http://en.wikipedia.org/wiki/Prime_number# Verifying_primality

0
Stas