Un Crible d'Atkin bizarre

Description

A la fin de l'article Nombres premiers: Crible d'Atkin , j'avais exprimé ma déception en ce qui concerne la rapidité de l'algorithme.
Finalement, je me suis décidé à le réétudier, en me basant, comme dans le "crible des multiplications", sur des "prints" intermédiaires. Il devient alors évident qu'on peut encore faire bien des améliorations !
Ces "prints" peuvent être obtenus avec AtkinPrint.html ou, avec mx=10000, directement dans AtkinPrint_10000.txt.

Utilisez Atkin.html pour tester et mesurer l'ensemble des codes explorés.
Si le temps d'attente dépasse une ou deux minutes, diminuez le mx maximal dans la function Test():   for (var mx=10; mx<=100000000; mx*=10) {...}.
Mes propres mesures se trouvent à la fin de ce texte.

Voir aussi l'article Atkin bizarre C/C++.
 
 

Atkin3.js


Nous partons du code suivant correspondant à Atkin2.js:
function Atkin2(mx) {
  var i,n,nn,x,y,r=Math.floor(Math.sqrt(mx));
  var sv=[false,false,true,true]; // for 0,1,2,3
  for (i=4; i<=mx; ++i) sv[i]=false;
  for (x=1; x<=r; ++x) { // Boucle principale
    var xx=x*x,xx3=xx*3,xx4=xx3+xx;
    for (y=1; y<x; ++y) // Boucle A
      if ((n=xx3-y*y)<=mx && (n%12==11)) sv[n]=!sv[n];
    if (xx3<mx)
      for (y=1; y<=r; ++y) // Boucle B
        if ((n=xx3+y*y)<=mx && (n%12==7)) sv[n]=!sv[n];
    if (xx4<mx)
      for (y=1; y<=r; ++y) // Boucle C
        if ((n=xx4+y*y)<=mx && (n%12==1||n%12==5)) sv[n]=!sv[n];
  }
  for (n=5; n<=r; ++n)
    if (sv[n]) for (nn=n*n,i=nn; i<=mx; i+=nn) sv[i]=false;
  for (i=0,n=0; i<=mx; ++i)
    if (sv[i]) ++n; // i is prime
  return n;
}
Et nous introduisons quelques "prints" intermédiaires en utilisant les variables sA, sB et sC (voir AtkinPrint.html).

On constate alors que les 3 boucles tournent souvent "à vide".
Séparons les complètement pour pouvoir bien y ajuster les limites et éviter les traitements inutiles:
Boucle A:
x >= 2, 1 <= y < x, 3·x²-y² <= mx; avec y = x-1, on a 3·x²-(x-1)² <= mx ou 2·x²+2·x-(mx+1) <= 0
Nous pourrions résoudre cette équation de second degré, Mais nous limiterons simplement les x par sqrt(mx/2).
Inversons le sens de parcours de la boucle while et remarquons que le plus grand y ainsi que sa décrémentation dépendent de x%3.
Boucle B:
3·x·x + y·y <= mx ==> x <= sqrt(mx/3).
x est impair, y commence par 2 et augmente alternativement par 2 et 4.
Boucle C:
4·x·x + y·y <= mx ==> x <= sqrt(mx/4).
y commence à 1 et i augmente selon x%3 de 2 ou de 4,2 alternativement.

Finalement, nous avons réussi à éliminer, dans les boucles while, les instructions avec %12 (modulo 12) qui sont assez gourmand.
function Atkin3(mx) {
  var d,i,n,nn,r,x,xx,y,z=Math.sqrt(mx);
  var sv=[false,false,true,true]; // for 0,1,2,3
  for (i=4; i<=mx; ++i) sv[i]=false;
  r=Math.floor(z*0.7071067812); // z*0.7071067812 = sqrt(mx/2)
  for (x=2; x<r; ++x) { // Boucle A
    xx=3*x*x; y=(x%3==1)?x-3:x-1; d=(x%3)?4:2;
    while ((y>0)&&((n=xx-y*y)<=mx)) {sv[n]=!sv[n]; y-=(d=6-d);}
  }
  r=Math.floor(z*0.5773502692); // z*0.5773502692 = sqrt(mx/3)
  for (x=1; x<=r; x+=2) { // Boucle B
    xx=3*x*x; y=2; d=4;
    while ((n=xx+y*y)<=mx) {sv[n]=!sv[n]; y+=(d=6-d);}
  }
  r=Math.floor(z*0.5); // z*0.5 = sqrt(mx/4)
  for (x=1; x<=r; ++x) { // Boucle C
    xx=4*x*x; y=1; d=2;
    if (x%3) while ((n=xx+y*y)<=mx) {sv[n]=!sv[n]; y+=2;}
    else     while ((n=xx+y*y)<=mx) {sv[n]=!sv[n]; y+=(d=6-d);}
  }
  r=Math.floor(z);
  for (n=5; n<=r; ++n)
    if (sv[n]) for (nn=n*n,i=nn; i<=mx; i+=nn) sv[i]=false;
  return sv;
}
Et nous arrivons, à quelques détails près, au code (en Java) proposé dans Prime I.T. sous l'onglet Atkin, Troisième ou Quatrième implémentation, source.
 
 

Atkin4.js


Il semble qu'on ne puisse plus simplifier grand-chose. Pourtant, quelque chose me "démange" encore:
Les boucles intérieures (while) calculent n=xx+y*y à partir de y qui a été obtenue d'une manière "optimisée".
Question: ne pourrait-on pas directement obtenir n d'une manière "optimisée" ?
J'ai mis pas mal de temps pour faire ce travail assez "laborieux".

Adaptons notre "print" pour les valeurs de n (seconde partie de AtkinPrint_10000.txt).

Pour les trois boucles A,B,C, il "suffit" de trouver le code qui correspond à l'évolution des valeurs de n dans le "print".
function Atkin4(mx) {
  var a,b,d,e,n,r,x,z=Math.sqrt(mx);
  var sv=[false,false,true,true]; // for 0,1,2,3
  for (var i=4; i<=mx; ++i) sv[i]=false;
  r=Math.floor(z*0.7071067812);
  for (a=11,d=12,x=2; x<=r; ++x) { // Boucle A
    for (n=a,e=d,b=(x%3==0); (e>0)&&(n<=mx); e-=12,n+=(b=!b)?e:2*e) sv[n]=!sv[n];
    if (x%3==1) {a+=12; d+=12;} else a+=(x%3!=0)?d:2*d;
  }
  r=Math.floor(z*0.5773502692);
  for (x=1,d=0,a=7; x<=r; x+=2,a+=(d+=24)) { // Boucle B
    for (n=a  ,e=-12; n<=mx; n+=(e+=72)) sv[n]=!sv[n];
    for (n=a+12,e=12; n<=mx; n+=(e+=72)) sv[n]=!sv[n];
  }
  r=Math.floor(z*0.5);
  for (x=1,d=4,a=5; x<=r; ++x,a+=(d+=8)) { // Boucle C
    if (x%3) for (n=a,e=0; n<=mx; n+=(e+=8)) sv[n]=!sv[n];
    else {
      for (n=a  ,e=-24; n<=mx; n+=(e+=72)) sv[n]=!sv[n];
      for (n=a+24,e=24; n<=mx; n+=(e+=72)) sv[n]=!sv[n];
    }
  }
  r=Math.floor(z);
  for (n=5; n<=r; ++n)
    if (sv[n]) for (var nn=n*n,i=nn; i<=mx; i+=nn) sv[i]=false;
 return sv;
}
On constate que dans les boucles intérieures, les incréments de n ne sont pas constants, mais s'incrémentent eux-mêmes aussi, ce qui correspond à une sorte d'"incrémentation accélérée":
 
 

Atkin5.js


J'ai été très surpris de voir que dans Atkin4 la variable y ne figure plus nulle part !
Et la variable x ne sert plus qu'à parcourir les boucles et pour tenir compte de x%3.
On peut donc aussi "éliminer" x avec la limite a<=mx, et en "étalant" 3 fois l'intérieur des boucles qui utilisent x%3.
Pour la boucle A, cet intérieur est encore dédoublé pour éviter l'alternance introduite avec la variable booléenne b.
function Atkin5(mx) {
  var a,b,d,e,g,n;
  var sv=[false,false,true,true]; // for 0,1,2,3
  for (var i=4; i<=mx; ++i) sv[i]=false;
  for (a=11,d=12,g=24; a<=mx; a+=12,d+=12,g+=36) { // Boucle A
    for (n=a,  e=g+24; (e>=48)&&(n<=mx); n+=(e-=72)) sv[n]=!sv[n];
    a+=d;
    for (n=a-12,  e=g; (e>=48)&&(n<=mx); n+=(e-=72)) sv[n]=!sv[n];
    for (n=a,  e=g+36; (e>=48)&&(n<=mx); n+=(e-=72)) sv[n]=!sv[n];
    a+=2*d;
    for (n=a-24,e=g-12;(e>=48)&&(n<=mx); n+=(e-=72)) sv[n]=!sv[n];
    for (n=a,  e=g+24; (e>=48)&&(n<=mx); n+=(e-=72)) sv[n]=!sv[n];
    for (n=a+d-12,e=g; (e>=48)&&(n<=mx); n+=(e-=72)) sv[n]=!sv[n];
  }
  for (d=0,a=7; a<=mx; a+=(d+=24)) { // Boucle B
    for (n=a  ,e=-12; n<=mx; n+=(e+=72)) sv[n]=!sv[n];
    for (n=a+12,e=12; n<=mx; n+=(e+=72)) sv[n]=!sv[n];
  }
  for (d=4,a=5; a<=mx; a+=(d+=8)) { // Boucle C
    for (n=a,e=0; n<=mx; n+=(e+=8)) sv[n]=!sv[n];
    a+=(d+=8);
    for (n=a,e=0; n<=mx; n+=(e+=8)) sv[n]=!sv[n];
    a+=(d+=8);
    for (n=a  ,e=-24; n<=mx; n+=(e+=72)) sv[n]=!sv[n];
    for (n=a+24,e=24; n<=mx; n+=(e+=72)) sv[n]=!sv[n];
  }
  var r=Math.floor(Math.sqrt(mx));
  for (n=5; n<=r; ++n)
    if (sv[n]) for (var nn=n*n,i=nn; i<=mx; i+=nn) sv[i]=false;
  return sv;
}
Bizarre ..., ce code du crible d'Atkin qui ne comporte explicitement ni des testes sur n%12, ni des calcul de la forme n=kx²±y², ni même les variables x ou y !!!
L'algorithme est pourtant basé sur ces notions.
 
 

Mesures en ms


Veuillez patienter "une minute" lorsque vous lancez Atkin.html qui permet d'afficher vos tests mesures correspondant à:
Atkin0: mx=10, n=4, time=0 ms 
Atkin0: mx=100, n=25, time=0 ms 
Atkin0: mx=1000, n=168, time=0 ms 
Atkin0: mx=10000, n=1229, time=1 ms 
Atkin0: mx=100000, n=9592, time=4 ms 
Atkin0: mx=1000000, n=78498, time=44 ms 
Atkin0: mx=10000000, n=664579, time=399 ms 
Atkin0: mx=100000000, n=5761455, time=5265 ms 

Atkin1: mx=10, n=4, time=0 ms 
Atkin1: mx=100, n=25, time=0 ms 
Atkin1: mx=1000, n=168, time=0 ms 
Atkin1: mx=10000, n=1229, time=1 ms 
Atkin1: mx=100000, n=9592, time=4 ms 
Atkin1: mx=1000000, n=78498, time=42 ms 
Atkin1: mx=10000000, n=664579, time=478 ms 
Atkin1: mx=100000000, n=5761455, time=5232 ms 

Atkin2: mx=10, n=4, time=0 ms 
Atkin2: mx=100, n=25, time=0 ms 
Atkin2: mx=1000, n=168, time=1 ms 
Atkin2: mx=10000, n=1229, time=1 ms 
Atkin2: mx=100000, n=9592, time=4 ms 
Atkin2: mx=1000000, n=78498, time=42 ms 
Atkin2: mx=10000000, n=664579, time=373 ms 
Atkin2: mx=100000000, n=5761455, time=4029 ms 

Atkin3: mx=10, n=4, time=0 ms 
Atkin3: mx=100, n=25, time=0 ms 
Atkin3: mx=1000, n=168, time=0 ms 
Atkin3: mx=10000, n=1229, time=1 ms 
Atkin3: mx=100000, n=9592, time=2 ms 
Atkin3: mx=1000000, n=78498, time=31 ms 
Atkin3: mx=10000000, n=664579, time=297 ms 
Atkin3: mx=100000000, n=5761455, time=2905 ms 

Atkin4: mx=10, n=4, time=0 ms 
Atkin4: mx=100, n=25, time=0 ms 
Atkin4: mx=1000, n=168, time=0 ms 
Atkin4: mx=10000, n=1229, time=0 ms 
Atkin4: mx=100000, n=9592, time=1 ms 
Atkin4: mx=1000000, n=78498, time=19 ms 
Atkin4: mx=10000000, n=664579, time=270 ms 
Atkin4: mx=100000000, n=5761455, time=2799 ms 

Atkin5: mx=10, n=4, time=0 ms 
Atkin5: mx=100, n=25, time=0 ms 
Atkin5: mx=1000, n=168, time=0 ms 
Atkin5: mx=10000, n=1229, time=0 ms 
Atkin5: mx=100000, n=9592, time=4 ms 
Atkin5: mx=1000000, n=78498, time=20 ms 
Atkin5: mx=10000000, n=664579, time=270 ms 
Atkin5: mx=100000000, n=5761455, time=2786 ms
Le tableau ci-dessous n'a qu'une valeur comparative.
Les mesures dépendent de l'ordinateur, du navigateur et de leur charge.
Sur le même ordinateur, j'ai chaque fois effectué une série de tests et indiqué (en ms) le meilleur résultat.

Une liste actualisée d'articles sur les nombres premiers, complétée de mesures des temps d'exécution, peut être obtenue ici: Nombres premiers.
 
 
Testé avec:
Firefox            23.0     OK
Google Chrome      28.0     OK
Internet Explorer  11       OK
Opera              12.16    OK
Safari              5.1.7   OK

Si ça "bloque", n'allez pas jusqu'à mx=100000000 (dans Atkin.html: function Test() ).

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.