C, 0.026119s (12 de marzo de 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);
}
Esto usa el método Meissel-Lehmer .
Tiempos
En mi máquina, obtengo aproximadamente 5.7 milisegundos para los casos de prueba combinados. Esto está en un Intel Core i7-3770 con RAM DDR3 a 1867 MHz, ejecutando 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
Debido a que la variación se hizo demasiado alta , estoy usando temporizaciones desde dentro del programa para los tiempos de ejecución no oficiales. Este es el script que calculó el promedio de los tiempos de ejecución combinados.
#!/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
Horarios oficiales
Esta vez es para hacer los casos de puntuación 1000 veces.
real 0m28.006s
user 0m15.703s
sys 0m14.319s
Cómo funciona
Fórmula
Deje ser un número entero positivo.X
Cada entero positivo satisface exactamente una de las siguientes condiciones.n ≤ x
n = 1
es divisible por un número primo p en [ 1 , 3 √nortepags.[ 1 , x--√3]
, donde p y q son (no necesariamente distintos) números primos en ( 3 √n = p qpagsq.( x--√3, x2--√3)
es primo yn > 3 √norten > x--√3
Supongamos que denota el número de primos p tal que p ≤ y . Hay π ( x ) - π ( 3 √π( y)pagsp ≤ ynúmeros que caen en la cuarta categoría.π( x ) - π( x--√3)
Supongamos que denota la cantidad de enteros positivos m ≤ y que son producto de exactamente k números primos que no se encuentran entre los primeros números primos c . Hay P 2 ( x , π ( 3 √PAGSk( y, C )m ≤ ykCnúmeros que caen en la tercera categoría.PAGS2( x , π( x--√3) )
Finalmente, dejemos que denote la cantidad de enteros positivos k ≤ y que son coprimos a los primeros números primos c . Hay x - ϕ ( x , π ( 3 √ϕ ( y, C )k ≤ yCnúmeros que caen en la segunda categoría.x−ϕ(x,π(x−−√3))
Como hay números en todas las categorías,x
1+x−ϕ(x,π(x−−√3))+P2(x,π(x−−√3))+π(x)−π(x−−√3)=x
y por lo tanto,
π(x)=ϕ(x,π(x−−√3))+π(x−−√3) - 1 -P2( x,π(x−-√3) )
Los números en la tercera categoría tienen una representación única si requerimos que y, por lo tanto, p ≤ √p ≤ q . De esta manera, el producto de los primospyqestá en la tercera categoría si y solo si 3 √p ≤ x--√pagsq , entonces hayπ(xX--√3< p ≤ q≤ xpagsvalores posibles paraqpara un valor fijo dep, yP2(x,π(3√π( xpags) - π( p ) + 1qpags, dondepkdenota elknúmeroprimo.PAGS2( x , π( x--√3) ) = ∑π( x√3) < k ≤ π( x√)( π( xpagsk) - π( pk) + 1 )pagskkth
Finalmente, cada entero positivo que no es coprimo a los primeros números primos c se puede expresar de manera única como n = p k f , donde p k es el factor primo más bajo de n . De esta manera, k ≤ c , yf es coprimo a los primeros números primos k - 1 .n ≤ yCn = pkFpagsknortek ≤ cFk - 1
Esto lleva a la fórmula recursiva . En particular, la suma está vacía sic=0, entoncesϕ(y,0)=y.ϕ ( y, c ) = y- ∑1 ≤ k ≤ cϕ ( ypagsk, k - 1 )c = 0ϕ ( y, 0 ) = y
Ahora tenemos una fórmula que nos permite calcular generando solo el primer π ( 3 √π( x )números primos (millones frente a miles de millones).π( x2--√3)
Algoritmo
Tendremos que calcular , dondeppuede llegar a ser tan bajo como3√π( xpags)pags . Si bien hay otras formas de hacer esto (como aplicar nuestra fórmula de forma recursiva), la forma más rápida parece ser enumerar todos los números primos de hasta3 √X--√3 , que se puede hacer con el tamiz de Eratóstenes.X2--√3
Primero, identificamos y almacenamos todos los números primos en , y calculaπ( 3 √[ 1 , x--√]yπ(√π( x--√3)al mismo tiempo. Entonces, calculamos xπ( x--√) para todos loskin(π(3√Xpagskk, y cuenta los números primos hasta cada cociente sucesivo.( π( x--√3) , π( x--√) ]
Además, tiene la forma cerradaπ( 3 √∑π( x√3) < k ≤ π( x√)( - π( pk) + 1 ) , que nos permite completar el cálculo deP2(x,π(3√π( x√3) - π( x√) ) ( π( x√3) + π( x√) - 12.PAGS2( x , π( x--√3) )
Eso deja el cálculo de , que es la parte más costosa del algoritmo. Simplemente usar la fórmula recursiva requeriría 2 llamadas de función c para calcular ϕ ( y , c ) .ϕ2Cϕ ( y, C )
En primer lugar, para todos los valores de c , entonces ϕ ( y , c ) = y - ∑ 1 ≤ k ≤ c , p k ≤ y ϕ ( yϕ ( 0 , c ) = 0C. Por sí sola, esta observación ya es suficiente para hacer factible el cálculo. Esto se debe a que cualquier número por debajo de2⋅109es más pequeño que el producto de cualquiera de los diez primos distintos, por lo que la abrumadora mayoría de los sumandos desaparece.ϕ (y, c ) =y- ∑1 ≤ k ≤ c , pk≤ yϕ ( ypagsk, k - 1 )2 ⋅ 109 9
Además, al agrupar y los primeros c ′ sumandos de la definición de ϕ , obtenemos la fórmula alternativa ϕ ( y , c ) = ϕ ( y , c ′ ) - ∑ c ′ < k ≤ c , p k ≤ y ϕ ( yyC′ϕ. Por lo tanto, la precomputaciónϕ(y,c′)para unac′fijay los valores apropiados deyguardan la mayoría de las llamadas de función restantes y los cálculos asociados.ϕ ( y, c ) = ϕ ( y, c′) - ∑C′< k ≤ c , pk≤ yϕ ( ypagsk, k - 1 )ϕ ( y, c′)c′y
Si , entoncesϕ( m c ,c)=φ( m c ), ya que los enteros en[1, m c ]que son divisibles por ninguno de p 1 ,⋯, p c son precisamente los que son coprimos para m c . Además, desdegcd(z+ m c , mmc=∏1≤k≤cpkϕ(mc,c)=φ(mc)[1,mc]p1,⋯,pcmc , tenemos que ϕ ( y , c ) = ϕ ( ⌊ ygcd(z+mc,mc)=gcd(z,mc).ϕ(y,c)=ϕ(⌊ymc⌋mc,c)+ϕ(y
Como la función totient de Euler es multiplicativa, , y tenemos una manera fácil de derivar ϕ ( y , c ) para todo y al precalcular los valores solo para aquellos y en [ 0 , m c ) .φ(mc)=∏1≤k≤cφ(pk)=∏1≤k≤c(pk- 1 )ϕ ( y, C )yy[ 0 , mC)
Además, si establecemos , obtenemos ϕ ( y , c ) = ϕ ( y , c - 1 ) - ϕ ( yC′= c - 1, la definición original del artículo de Lehmer. Esto nos da una manera simple de calcular previamenteϕ(y,c)para aumentar los valores dec.ϕ ( y, c ) = ϕ ( y, c - 1 ) - ϕ ( ypagsC, c - 1 )ϕ ( y, C )C
Además de calcular previamente para un cierto valor bajo de c , también lo calcularemos previamente para valores bajos de y , cortando la recursión poco después de caer por debajo de cierto umbral.ϕ ( y, C )Cy
Implementación
La sección anterior cubre la mayoría de las partes del código. Un detalle importante restante es cómo Phi
se realizan las divisiones en la función .
Dado que calcular solo requiere dividir por el primer π ( 3 √ϕnúmeros primos, podemos usar lafunción en su lugar. En lugar de simplemente dividir unaypor una primap, multiplicamosypordp≈ 2 64π( x--√3)fastdiv
ypagsy lugar y recuperaryrepags≈ 264pags comodpyypags . Debido a cómo se implementa la multiplicación de enteros enx64,no se requieredividir por264; los 64 bits superiores dedpyse almacenan en su propio registro.repagsy264264repagsy
Tenga en cuenta que este método requiere precomputar , que no es más rápido que calcular yrepags directamente. Sin embargo, dado que tenemos que dividir por los mismos números primos una y otra vez y la división esmucho más lentaque la multiplicación, esto resulta en una aceleración importante. Se pueden encontrar más detalles sobre este algoritmo, así como una prueba formal, enDivisión por enteros invariantes usando Multiplicación.ypags