web-dev-qa-db-fra.com

Étant donné le nombre premier N, calculer le prochain nombre premier?

Un collègue vient de me dire que la collection du dictionnaire C # est redimensionnée par des nombres premiers pour des raisons obscures liées au hachage. Et ma question immédiate était, "comment sait-elle quelle est la prochaine prime? Est-ce qu'ils racontent une table géante ou calculent à la volée? C'est un runtime effrayant non déterministe sur un insert provoquant un redimensionnement"

Donc ma question est, étant donné N, qui est un nombre premier, quelle est la façon la plus efficace de calculer le prochain nombre premier?

67
John Shedletsky

Le écarts entre les nombres premiers consécutifs est connu pour être assez petit, le premier écart de plus de 100 se produisant pour le nombre premier 370261. Cela signifie que même une force brute simple sera assez rapide dans la plupart des circonstances, en prenant O (ln (p) * sqrt (p)) en moyenne.

Pour p = 10 000, c'est O(921) opérations. Gardant à l'esprit que nous effectuerons cela une fois toutes les O(ln(p)) insertion (environ parlant), ceci est bien dans les contraintes de la plupart des problèmes prenant de l'ordre de la milliseconde sur la plupart des matériels modernes.

34
marcog

Il y a environ un an, je travaillais dans ce domaine pour libc ++ tout en implémentant les conteneurs (hachés) non ordonnés pour C++ 11. J'ai pensé partager mes expériences ici. Cette expérience prend en charge réponse acceptée par marcog pour une définition raisonnable de "force brute".

Cela signifie que même une force brute simple sera assez rapide dans la plupart des cas, en prenant O (ln (p) * sqrt (p)) en moyenne.

J'ai développé plusieurs implémentations de size_t next_prime(size_t n) où la spécification de cette fonction est:

Renvoie: Le plus petit nombre premier supérieur ou égal à n.

Chaque implémentation de next_prime Est accompagnée d'une fonction d'aide is_prime. is_prime Doit être considéré comme un détail d'implémentation privé; ne doit pas être appelé directement par le client. L'exactitude de chacune de ces implémentations a bien sûr été testée, mais elle a également été testée avec le test de performances suivant:

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;
}

Je dois souligner qu'il s'agit d'un test de performances et ne reflète pas une utilisation typique, qui ressemblerait davantage à:

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

Tous les tests de performance ont été compilés avec:

clang++ -stdlib=libc++ -O3 main.cpp

Implémentation 1

Il existe sept implémentations. Le but de l'affichage de la première implémentation est de démontrer que si vous n'arrêtez pas de tester le candidat prime x pour les facteurs à sqrt(x) alors vous n'avez même pas réussi à obtenir une implémentation qui pourrait être classée comme Force brute. Cette implémentation est brutalement lente .

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;
}

Pour cette implémentation seulement, j'ai dû définir e sur 100 au lieu de 100000, juste pour obtenir un temps d'exécution raisonnable:

0.0015282 primes/millisecond

Implémentation 2

Cette implémentation est la plus lente des implémentations force brute et la seule différence avec l'implémentation 1 est qu'elle arrête de tester la primauté lorsque le facteur dépasse 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;
}

Notez que sqrt(x) n'est pas directement calculé, mais déduit de q < i. Cela accélère les choses d'un facteur de milliers:

5.98576 primes/millisecond

et valide la prédiction de marcog:

... ceci est bien dans les contraintes de la plupart des problèmes prenant de l'ordre de la milliseconde sur la plupart des matériels modernes.

Implémentation 3

On peut presque doubler la vitesse (au moins sur le matériel que j'utilise) en évitant d'utiliser l'opérateur %:

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

Implémentation 4

Jusqu'à présent, je n'ai même pas utilisé la connaissance commune que 2 est le seul nombre premier pair. Cette implémentation intègre cette connaissance, doublant presque encore la vitesse:

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

La mise en œuvre 4 est probablement ce que la plupart des gens ont en tête lorsqu'ils pensent à la "force brute".

Implémentation 5

En utilisant la formule suivante, vous pouvez facilement choisir tous les nombres qui ne sont divisibles ni par 2 ni par 3:

6 * k + {1, 5}

où k> = 1. L'implémentation suivante utilise cette formule, mais implémentée avec une astuce xor mignonne:

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;
}

Cela signifie effectivement que l'algorithme ne doit vérifier que 1/3 des entiers pour la primauté au lieu de la moitié des nombres et le test de performance montre la vitesse attendue de près de 50%:

32.6167 primes/millisecond

Implémentation 6

Cette implémentation est une extension logique de l'implémentation 5: elle utilise la formule suivante pour calculer tous les nombres qui ne sont pas divisibles par 2, 3 et 5:

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

Il déroule également la boucle interne dans is_prime et crée une liste de "petits nombres premiers" qui est utile pour traiter les nombres inférieurs à 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;
}

Cela va sans doute au-delà de la "force brute" et est bon pour augmenter la vitesse d'un autre 27,5% pour:

41.6026 primes/millisecond

Implémentation 7

Il est pratique de jouer au jeu ci-dessus pour une autre itération, en développant une formule pour les nombres non divisibles par 2, 3, 5 et 7:

210 * k + {1, 11, ...},

Le code source n'est pas montré ici, mais il est très similaire à l'implémentation 6. Il s'agit de l'implémentation que j'ai choisi d'utiliser réellement pour les conteneurs non ordonnés de libc ++ , et ce code source est open source (disponible sur le lien).

Cette dernière itération est bonne pour une autre augmentation de vitesse de 14,6% pour:

47.685 primes/millisecond

L'utilisation de cet algorithme garantit que les clients des tables de hachage de libc ++ peuvent choisir le nombre premier qu'ils jugent le plus avantageux pour leur situation, et les performances de cette application sont tout à fait acceptables.

74
Howard Hinnant

Juste au cas où quelqu'un serait curieux:

En utilisant le réflecteur, j'ai déterminé que .Net utilise une classe statique qui contient une liste codée en dur de ~ 72 nombres premiers allant jusqu'à 7199369, qui recherche le plus petit nombre premier qui est au moins deux fois la taille actuelle, et pour les tailles plus grandes que celle qu'il calcule amorçage suivant par division d'essai de tous les nombres impairs jusqu'au sqrt du nombre potentiel. Cette classe est immuable et sûre pour les threads (c'est-à-dire que les nombres premiers plus grands ne sont pas stockés pour une utilisation future).

43
Paul Wheeler

Une bonne astuce consiste à utiliser un tamis partiel. Par exemple, quel est le premier nombre premier qui suit le nombre N = 2534536543556?

Vérifiez le module de N par rapport à une liste de petits nombres premiers. Ainsi...

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

Nous savons que le premier premier suivant N doit être un nombre impair, et nous pouvons immédiatement éliminer tous les multiples impairs de cette liste de petits nombres premiers. Ces modules nous permettent de filtrer les multiples de ces petits nombres premiers. Si nous devions utiliser les petits nombres premiers jusqu'à 200, nous pouvons utiliser ce schéma pour éliminer immédiatement la plupart des nombres premiers potentiels supérieurs à N, à l'exception d'une petite liste.

Plus explicitement, si nous recherchons un nombre premier au-delà de 2534536543556, il ne peut pas être divisible par 2, nous devons donc considérer uniquement les nombres impairs au-delà de cette valeur. Les modules ci-dessus montrent que 2534536543556 est congru à 2 mod 3, donc 2534536543556 + 1 est congru à 0 mod 3, comme cela doit être 2534536543556 + 7, 2534536543556 + 13, etc. pour les tester pour la primauté et sans aucune division d'essai.

De même, le fait que

mod(2534536543556,7) = 3

nous dit que 2534536543556 + 4 est congru à 0 mod 7. Bien sûr, ce nombre est pair, donc nous pouvons l'ignorer. Mais 2534536543556 + 11 est un nombre impair divisible par 7, tout comme 2534536543556 + 25, etc. Encore une fois, nous pouvons exclure ces nombres comme étant clairement composés (car ils sont divisibles par 7) et donc pas premiers.

En utilisant uniquement la petite liste de nombres premiers jusqu'à 37, nous pouvons exclure la plupart des nombres qui suivent immédiatement notre point de départ de 2534536543556, à l'exception de quelques-uns:

{2534536543573 , 2534536543579 , 2534536543597}

De ces nombres, sont-ils premiers?

2534536543573 = 1430239 * 1772107
2534536543579 = 99833 * 25387763

J'ai fait l'effort de fournir les factorisations premières des deux premiers nombres de la liste. Voyez qu'ils sont composites, mais les facteurs premiers sont importants. Bien sûr, cela a du sens, car nous avons déjà veillé à ce qu'aucun nombre restant ne puisse avoir de petits facteurs premiers. Le troisième de notre liste restreinte (2534536543597) est en fait le tout premier nombre premier après N. Le schéma de tamisage que j'ai décrit aura tendance à donner des nombres premiers ou composés de facteurs premiers généralement grands. Nous devions donc appliquer un test explicite de primalité à seulement quelques nombres avant de trouver le premier premier.

Un schéma similaire produit rapidement le prochain nombre premier supérieur à N = 1000000000000000000000000000, comme 1000000000000000000000000103.

12
user85109

Juste quelques expériences avec la distance inter-nombres premiers.


Ceci est un complément pour visualiser d'autres réponses.

J'ai obtenu les nombres premiers du 100 000e (= 1 299 709) au 200 000e (= 2 750 159)

Certaines données:

Maximum interprime distance = 148

Mean interprime distance = 15  

Diagramme de fréquence de distance interprime:

alt text

Distance interprime vs nombre premier

alt text

Juste pour voir que c'est "aléatoire". Cependant ...

12
Dr. belisarius

Il n'y a pas de fonction f(n) pour calculer le prochain nombre premier. Au lieu de cela, un nombre doit être testé pour la primauté.

Il est également très utile, lors de la recherche du nième nombre premier, de connaître déjà tous les nombres premiers du 1er au (n-1) ème, car ce sont les seuls nombres qui doivent être testés en tant que facteurs.

En raison de ces raisons, je ne serais pas surpris s'il existe un ensemble précalculé de grands nombres premiers. Cela n'a pas vraiment de sens pour moi si certains nombres premiers devaient être recalculés à plusieurs reprises.

5
Kirk Broadhurst

Comme d'autres l'ont déjà noté, un moyen de trouver le prochain nombre premier étant donné le nombre premier actuel n'a pas encore été trouvé. Par conséquent, la plupart des algorithmes se concentrent davantage sur l'utilisation d'un moyen rapide de vérification primalité puisque vous devez vérifier n/2 des nombres entre votre nombre premier connu et le suivant.

Selon l'application, vous pouvez également vous en sortir en codant en dur une table de recherche, comme indiqué par Paul Wheeler .

3
rjzii

Pour la pure nouveauté, il y a toujours cette approche:

#!/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);
}

ce qui produit bien sûr

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

Mis à part tous les jeux et divertissements, il est bien connu que la taille optimale de la table de hachage est rigoureusement prouvée un nombre premier de la forme 4N−1. Il ne suffit donc pas de trouver le premier nombre premier. Vous devez également faire l'autre vérification.

3
tchrist

Pour autant que je me souvienne, il utilise un nombre premier à côté du double de la taille actuelle. Il ne calcule pas ce nombre premier - il y a une table avec des nombres préchargés jusqu'à une grande valeur (pas exactement, quelque chose autour de 10 000 000). Lorsque ce nombre est atteint, il utilise un algorithme naïf pour obtenir le numéro suivant (comme curNum = curNum + 1) et le valide en utilisant certaines de ces méthodes: http://en.wikipedia.org/wiki/Prime_number# Verifying_primality

0
Stas