C, 0.026119s (12 mars 2016)
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define cache_size 16384
#define Phi_prec_max (47 * a)
#define bit(k) (1ULL << ((k) & 63))
#define word(k) sieve[(k) >> 6]
#define sbit(k) ((word(k >> 1) >> (k >> 1)) & 1)
#define ones(k) (~0ULL >> (64 - (k)))
#define m2(k) ((k + 1) / 2)
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
#define ns(t) (1000000000 * t.tv_sec + t.tv_nsec)
#define popcnt __builtin_popcountll
#define mask_build(i, p, o, m) mask |= m << i, i += o, i -= p * (i >= p)
#define Phi_prec_bytes ((m2(Phi_prec_max) + 1) * sizeof(int16_t))
#define Phi_prec(i, j) Phi_prec_pointer[(j) * (m2(Phi_prec_max) + 1) + (i)]
#define Phi_6_next ((i / 1155) * 480 + Phi_5[i % 1155] - Phi_5[(i + 6) / 13])
#define Phi_6_upd_1() t = Phi_6_next, i += 1, *(l++) = t
#define Phi_6_upd_2() t = Phi_6_next, i += 2, *(l++) = t, *(l++) = t
#define Phi_6_upd_3() t = Phi_6_next, i += 3, *(l++) = t, *(l++) = t, *(l++) = t
typedef unsigned __int128 uint128_t;
struct timespec then, now;
uint64_t a, primes[4648] = { 2, 3, 5, 7, 11, 13, 17, 19 }, *primes_fastdiv;
uint16_t *Phi_6, *Phi_prec_pointer;
inline uint64_t Phi_6_mod(uint64_t y)
{
if (y < 30030)
return Phi_6[m2(y)];
else
return (y / 30030) * 5760 + Phi_6[m2(y % 30030)];
}
inline uint64_t fastdiv(uint64_t dividend, uint64_t fast_divisor)
{
return ((uint128_t) dividend * fast_divisor) >> 64;
}
uint64_t Phi(uint64_t y, uint64_t c)
{
uint64_t *d = primes_fastdiv, i = 0, r = Phi_6_mod(y), t = y / 17;
r -= Phi_6_mod(t), t = y / 19;
while (i < c && t > Phi_prec_max) r -= Phi(t, i++), t = fastdiv(y, *(d++));
while (i < c && t) r -= Phi_prec(m2(t), i++), t = fastdiv(y, *(d++));
return r;
}
uint64_t Phi_small(uint64_t y, uint64_t c)
{
if (!c--) return y;
return Phi_small(y, c) - Phi_small(y / primes[c], c);
}
uint64_t pi_small(uint64_t y)
{
uint64_t i, r = 0;
for (i = 0; i < 8; i++) r += (primes[i] <= y);
for (i = 21; i <= y; i += 2)
r += i % 3 && i % 5 && i % 7 && i % 11 && i % 13 && i % 17 && i % 19;
return r;
}
int output(int result)
{
clock_gettime(CLOCK_REALTIME, &now);
printf("pi(x) = %9d real time:%9ld ns\n", result , ns(now) - ns(then));
return 0;
}
int main(int argc, char *argv[])
{
uint64_t b, i, j, k, limit, mask, P2, *p, start, t = 8, x = atoi(argv[1]);
uint64_t root2 = sqrt(x), root3 = pow(x, 1./3), top = x / root3 + 1;
uint64_t halftop = m2(top), *sieve, sieve_length = (halftop + 63) / 64;
uint64_t i3 = 1, i5 = 2, i7 = 3, i11 = 5, i13 = 6, i17 = 8, i19 = 9;
uint16_t Phi_3[] = { 0, 1, 1, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 7, 7, 8 };
uint16_t *l, *m, Phi_4[106], Phi_5[1156];
clock_gettime(CLOCK_REALTIME, &then);
sieve = malloc(sieve_length * sizeof(int64_t));
if (x < 529) return output(pi_small(x));
for (i = 0; i < sieve_length; i++)
{
mask = 0;
mask_build( i3, 3, 2, 0x9249249249249249ULL);
mask_build( i5, 5, 1, 0x1084210842108421ULL);
mask_build( i7, 7, 6, 0x8102040810204081ULL);
mask_build(i11, 11, 2, 0x0080100200400801ULL);
mask_build(i13, 13, 1, 0x0010008004002001ULL);
mask_build(i17, 17, 4, 0x0008000400020001ULL);
mask_build(i19, 19, 12, 0x0200004000080001ULL);
sieve[i] = ~mask;
}
limit = min(halftop, 8 * cache_size);
for (i = 21; i < root3; i += 2)
if (sbit(i))
for (primes[t++] = i, j = i * i / 2; j < limit; j += i)
word(j) &= ~bit(j);
a = t;
for (i = root3 | 1; i < root2 + 1; i += 2)
if (sbit(i)) primes[t++] = i;
b = t;
while (limit < halftop)
{
start = 2 * limit + 1, limit = min(halftop, limit + 8 * cache_size);
for (p = &primes[8]; p < &primes[a]; p++)
for (j = max(start / *p | 1, *p) * *p / 2; j < limit; j += *p)
word(j) &= ~bit(j);
}
P2 = (a - b) * (a + b - 1) / 2;
for (i = m2(root2); b --> a; P2 += t, i = limit)
{
limit = m2(x / primes[b]), j = limit & ~63;
if (i < j)
{
t += popcnt((word(i)) >> (i & 63)), i = (i | 63) + 1;
while (i < j) t += popcnt(word(i)), i += 64;
if (i < limit) t += popcnt(word(i) & ones(limit - i));
}
else if (i < limit) t += popcnt((word(i) >> (i & 63)) & ones(limit - i));
}
if (a < 7) return output(Phi_small(x, a) + a - 1 - P2);
a -= 7, Phi_6 = malloc(a * Phi_prec_bytes + 15016 * sizeof(int16_t));
Phi_prec_pointer = &Phi_6[15016];
for (i = 0; i <= 105; i++)
Phi_4[i] = (i / 15) * 8 + Phi_3[i % 15] - Phi_3[(i + 3) / 7];
for (i = 0; i <= 1155; i++)
Phi_5[i] = (i / 105) * 48 + Phi_4[i % 105] - Phi_4[(i + 5) / 11];
for (i = 1, l = Phi_6, *l++ = 0; i <= 15015; )
{
Phi_6_upd_3(); Phi_6_upd_2(); Phi_6_upd_1(); Phi_6_upd_2();
Phi_6_upd_1(); Phi_6_upd_2(); Phi_6_upd_3(); Phi_6_upd_1();
}
for (i = 0; i <= m2(Phi_prec_max); i++)
Phi_prec(i, 0) = Phi_6[i] - Phi_6[(i + 8) / 17];
for (j = 1, p = &primes[7]; j < a; j++, p++)
{
i = 1, memcpy(&Phi_prec(0, j), &Phi_prec(0, j - 1), Phi_prec_bytes);
l = &Phi_prec(*p / 2 + 1, j), m = &Phi_prec(m2(Phi_prec_max), j) - *p;
while (l <= m)
for (k = 0, t = Phi_prec(i++, j - 1); k < *p; k++) *(l++) -= t;
t = Phi_prec(i++, j - 1);
while (l <= m + *p) *(l++) -= t;
}
primes_fastdiv = malloc(a * sizeof(int64_t));
for (i = 0, p = &primes[8]; i < a; i++, p++)
{
t = 96 - __builtin_clzll(*p);
primes_fastdiv[i] = (bit(t) / *p + 1) << (64 - t);
}
return output(Phi(x, a) + a + 6 - P2);
}
Ceci utilise la méthode de Meissel-Lehmer .
Les horaires
Sur ma machine, il me faut environ 5,7 millisecondes pour les cas de test combinés. Ceci est sur un Intel Core i7-3770 avec DDR3 RAM à 1867 MHz, exécutant openSUSE 13.2.
$ ./timepi '-march=native -O3' pi 1000
pi(x) = 93875448 real time: 2774958 ns
pi(x) = 66990613 real time: 2158491 ns
pi(x) = 62366021 real time: 2023441 ns
pi(x) = 34286170 real time: 1233158 ns
pi(x) = 5751639 real time: 384284 ns
pi(x) = 2465109 real time: 239783 ns
pi(x) = 1557132 real time: 196248 ns
pi(x) = 4339 real time: 60597 ns
0.00572879 s
Comme la variance est devenue trop importante , j'utilise des timings du programme pour les temps d'exécution non officiels. C'est le script qui calcule la moyenne des temps d'exécution combinés.
#!/bin/bash
all() { for j in ${a[@]}; do ./$1 $j; done; }
gcc -Wall $1 -lm -o $2 $2.c
a=(1907000000 1337000000 1240000000 660000000 99820000 40550000 24850000 41500)
all $2
r=$(seq 1 $3)
for i in $r; do all $2; done > times
awk -v it=$3 '{ sum += $6 } END { print "\n" sum / (1e9 * it) " s" }' times
rm times
Temps officiels
Ce temps est pour faire les cas de partition 1000 fois.
real 0m28.006s
user 0m15.703s
sys 0m14.319s
Comment ça fonctionne
Formule
Soit un entier positif.x
Chaque entier positif vérifie exactement l'une des conditions suivantes.n≤x
n=1
est divisible par un nombre premier p dans [ 1 , 3 √np.[1,x−−√3]
, où p et q sont des nombres premiers (non nécessairement distincts) dans ( 3 √n=pqpq.(x−−√3,x2−−√3)
est premier et n > 3 √nn>x−−√3
Soit le nombre de nombres premiers p tels que p ≤ y . Il y a π ( x ) - π ( 3 √π(y)pp≤ynombres qui entrent dans la quatrième catégorie.π(x)−π(x−−√3)
Soit la quantité d'entiers positifs m ≤ y qui est un produit de k nombres premiers exactement et non parmi les c premiers nombres premiers. Il y a P 2 ( x , π ( 3 √Pk(y,c)m≤ykcP2(x,π(x−−√3)) numéros qui appartiennent à la troisième catégorie.
Enfin, soit désigner la quantité d'entiers positifs k ≤ y qui sont premiers entre eux les premier c nombres premiers. Il y a x - ϕ ( x , π ( 3 √ϕ(y,c)k≤ycx−ϕ(x,π(x−−√3)) numéros entrant dans la deuxième catégorie.
Puisqu'il y a nombres dans toutes les catégories,x
1+x−ϕ(x,π(x−−√3))+P2(x,π(x−−√3))+π(x)−π(x−−√3)=x
et donc,
π(x)=ϕ(x,π(x−−√3))+π(x−−√3)−1−P2(x,π(x−−√3))
Les nombres de la troisième catégorie ont une représentation unique si nous exigeons que et donc p ≤ √p≤q . De cette façon, le produit des nombres premierspetqest dans la troisième catégorie si et seulement si 3 √p≤x−−√pq , donc il y aπ(xx−−√3<p≤q≤xpvaleurs possibles pourqpour une valeur fixe dep, etP2(x,π(3√π(xp)−π(p)+1qp, oùpkdésigne lekthP2(x,π(x−−√3))=∑π(x√3)<k≤π(x√)(π(xpk)−π(pk)+1)pkkth nombre premier.
Enfin, tout entier positif qui ne correspond pas aux c premiers nombres premiers peut être exprimé de manière unique par n = p k f , où p k est le facteur premier le plus bas de n . De cette façon, k ≤ c et f est coprime des k - 1 premiers nombres premiers.n≤ycn=pkfpknk≤cfk−1
Ceci conduit à la formule récursive . En particulier, la somme est vide sic=0, doncϕ(y,0)=y.ϕ(y,c)=y−∑1≤k≤cϕ(ypk,k−1)c=0ϕ(y,0)=y
Nous avons maintenant une formule qui nous permet de calculer en ne générant que le premier π ( 3 √π(x)π(x2−−√3) nombres premiers (millions vs milliards).
Algorithme
Nous aurons besoin de calculer , oùppeut être aussi bas que3√π(xp)p . Bien qu’il existe d’autres moyens de le faire (comme l’application récursive de notre formule), le moyen le plus rapide semble être d’énumérer tous les nombres premiers jusqu’à3 √.x−−√3x2−−√3 , ce qui peut être fait avec le tamis d'Eratosthenes.
Premièrement, nous identifions et stockons tous les nombres premiers dans et calculerπ( 3 √[1,x−−√]etπ(√π(x−−√3)en même temps. Ensuite, on calcule xπ(x−−√) pour toutkin(π(3√xpkk, et comptez les nombres premiers jusqu'à chaque quotient successif.(π(x−−√3),π(x−−√)]
Aussi, a la forme ferméeπ( 3 √∑π(x√3)<k≤π(x√)(−π(pk)+1) , ce qui nous permet de compléter le calcul deP2(x,π(3√π(x√3)−π(x√))(π(x√3)+π(x√)−12P2(x,π(x−−√3)) .
Cela laisse le calcul de , qui est la partie la plus chère de l’algorithme. Simplement en utilisant la formule récursive , il faudrait deux c des appels de fonction pour calculer φ ( y , c )ϕ2cϕ(y,c) .
Tout d' abord, pour toutes les valeurs de c , de sorte que φ ( y , c ) = y - Σ 1 ≤ k ≤ c , p k ≤ y φ ( yϕ(0,c)=0c. En soi, cette observation est déjà suffisante pour rendre le calcul possible. En effet, tout nombre inférieur à2×109est plus petit que le produit de dix nombres premiers distincts, de sorte que la très grande majorité des sommands disparaissent.φ ( y, c ) = y- Σ1 ≤ k ≤ c , pk≤ yφ ( ypk, k - 1 )2 ⋅ 109
De plus, en regroupant les et les premiers c ' summands de la définition de φ , on obtient la solution formule φ ( y , c ) = φ ( y , c ' ) - Σ c ' < k ≤ c , p k ≤ y φ ( yyc′ϕ. Ainsi, précalculerφ(y,c')pour une durée déterminéec'etvaleurs appropriées deyϕ(y,c)=ϕ(y,c′)−∑c′<k≤c,pk≤yϕ(ypk,k−1)ϕ(y,c′)c′y enregistre la plupart des appels de fonctions restantes et les calculs associés.
Si , puis φ ( m c , c ) = φ ( m c ) , étant donnéles nombres entiers [ 1 , m c ] qui sont divisibles par aucun de p 1 , ⋯ , p c sont précisément ceux qui sont coprime à m c . Aussi, depuis gcd ( z + m c , mmc=∏1≤k≤cpkϕ(mc,c)=φ(mc)[1,mc]p1,⋯,pcmc , on a que φ ( y , c ) = φ ( ⌊ ygcd(z+mc,mc)=gcd(z,mc)ϕ(y,c)=ϕ(⌊ymc⌋mc,c)+ϕ(y .
Depuis la fonction indicatrice d'Euler est multiplicatif, , et nous avons un moyen facile de calculer φ ( y , c ) pour tout y en précalculer les valeurs que ceux y dans [ 0 , m c )φ(mc)=∏1≤k≤cφ(pk)=∏1≤k≤c(pk−1)φ (y, C )yy[ 0 , mc) .
En outre, si l' on pose , on obtient φ ( y , c ) = φ ( y , c - 1 ) - φ ( yc′= c - 1, la définition originale de l'article de Lehmer. Cela nous donne un moyen simple de pré-calculerϕ(y,c)pour des valeurs croissantes dec.φ (y, c ) = ϕ (y, C - 1 ) - φ ( ypc, c - 1 )φ ( y, C )c
En plus du précalcul pour une certaine valeur faible de c , nous le précalculons également pour des valeurs faibles de yϕ(y,c)cy , coupant la récursivité après avoir atteint un certain seuil.
la mise en oeuvre
La section précédente couvre la plupart des parties du code. Un détail important restant est la manière dont les divisions dans la fonctionPhi
sont effectuées.
Etant donné que le calcul du ne nécessite en divisant par le premier π ( 3 √ϕnombres premiers, nous pouvons utiliser lafonction à la place. Plutôt que de simplement diviser unypar un nombre premierp, on multiplieypardp≈ 2 64π(x−−√3)fastdiv
ypy place et récupèreydp≈264p commedpyyp . En raison de la manière dont la multiplication d’entiers est implémentée surx64, iln’est pas nécessaire dediviser par264; les 64 bits les plus élevés dedpydpy264264dpy sont stockées dans leur propre registre.
Notez que cette méthode nécessite un calcul préalable , qui n’est pas plus rapide que le calcul ydp directement. Cependant, étant donné que nous devons diviser encore et encore par les mêmes nombres premiers et que la division esttellement plus lenteque la multiplication, il en résulte une accélération importante. On peut trouver plus de détails sur cet algorithme, ainsi qu'une preuve formelle, dansDivision par entiers invariants à l'aide de la multiplication.yp