C, 0.026119s (12 mar 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);
}
Questo utilizza il metodo Meissel-Lehmer .
Tempi
Sulla mia macchina, sto ottenendo circa 5,7 millisecondi per i casi di test combinati. Questo è su un Intel Core i7-3770 con DDR3 RAM a 1867 MHz, con 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
Poiché la varianza è diventata troppo elevata , sto usando i tempi all'interno del programma per i tempi di esecuzione non ufficiali. Questo è lo script che ha calcolato la media dei tempi di esecuzione combinati.
#!/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
Tempi ufficiali
Questa volta è per fare i casi punteggio 1000 volte.
real 0m28.006s
user 0m15.703s
sys 0m14.319s
Come funziona
Formula
Sia un numero intero positivo.X
Ogni intero positivo soddisfa esattamente una delle seguenti condizioni.n ≤ x
n = 1
p [ 1 , 3 √n è divisibile per un numero primo in .p[ 1 , x--√3]
p q ( 3 √n = p q , dove e sono numeri primi (non necessariamente distinti) in .pq( x--√3, x2--√3)
n > 3 √n è prime en > x--√3
Let denota il numero di numeri primi tali che . Ci sono numeri che rientrano nella quarta categoria.p p ≤ y π ( x ) - π ( 3 √π( y)pp ≤ yπ( x ) - π( x--√3)
Sia la quantità di numeri interi positivi che sono un prodotto di esattamente numeri primi non tra i primi numeri primi. Esistono numeri che rientrano nella terza categoria.m ≤ y k c P 2 ( x , π ( 3 √PK( y, c )m ≤ yKcP2( x , π( x--√3) )
Infine, lascia che denoti la quantità di numeri interi positivi che sono coprimi ai primi numeri primi . Esistono numeri che rientrano nella seconda categoria.k ≤ y c x - ϕ ( x , π ( 3 √ϕ ( y, c )k ≤ ycx - ϕ ( x , π( x--√3) )
Poiché ci sono numeri in tutte le categorie,X
1 + x - ϕ ( x , π( x--√3) ) + P2( x , π( x--√3) ) + π( x ) - π( x--√3) = x
e quindi,
π( x ) = ϕ ( x , π( x--√3) ) + π( x--√3) - 1 - P2( x ,π( x--√3) )
I numeri nella terza categoria hanno una rappresentazione univoca se si richiede che e, quindi, . In questo modo, il prodotto dei numeri primi e è nella terza categoria se e solo se , quindi ci sono possibili valori per per un valore fisso di e , dove indica il numero primo .p ≤ √p ≤ q pq 3 √p ≤ x--√pq π(xX--√3< p ≤q≤ xpq p P 2 ( x , π ( 3 √π( xp) -π( p ) + 1qpp k k thP2( x ,π( x--√3) ) = ∑π( x√3) < k ≤π( x√)(π( xpK) -π( pK)+1)pkkth
Infine, ogni numero intero positivo che non è coprimi ai primi numeri primi può essere espresso in modo univoco come , dove è il fattore primo più basso di . In questo modo, ed sono coprimi ai primi numeri primi .c n = p k f p kn≤ycn=pkfpkk ≤ c f k - 1nk≤cfk−1
Questo porta alla formula ricorsiva . In particolare, la somma è vuota se , quindi .c = 0 ϕ ( y , 0 ) = yϕ(y,c)=y−∑1≤k≤cϕ(ypk,k−1)c=0ϕ(y,0)=y
Ora abbiamo una formula che ci consente di calcolare generando solo i primi numeri primi (milioni vs miliardi).π ( 3 √π(x)π(x2−−√3)
Algoritmo
Dovremo calcolare , dove può arrivare a un minimo di . Mentre ci sono altri modi per farlo (come applicare la nostra formula in modo ricorsivo), il modo più veloce sembra essere quello di enumerare tutti i numeri primi fino a , che può essere fatto con il setaccio di Eratostene.p3√π(xp)p 3 √x−−√3x2−−√3
Innanzitutto, identifichiamo e memorizziamo tutti i numeri primi in e calcoliamo e contemporaneamente. Quindi, calcoliamo per tutti i in e contiamo i numeri primi fino a ogni quoziente successivo .π( 3 √[1,x−−√]π(√π(x−−√3) xπ(x−−√)xpk( π ( 3 √k(π(x−−√3),π(x−−√)]
Inoltre, ha la forma chiusa , che ci permette di completare il calcolo di .∑π(x√3)<k≤π(x√)(−π(pk) + 1 ) P2(xπ(x√3)−π( x√) )(π( x√3)+π( x√) -12P2( x,π(x−-√3) )
Ciò lascia il calcolo di , che è la parte più costosa dell'algoritmo. Il semplice utilizzo della formula ricorsiva richiederebbe chiamate di funzione per calcolare .2 c ϕ (φ2cϕ (y, c )
Prima di tutto, per tutti i valori di , quindi . Di per sé, questa osservazione è già sufficiente per rendere fattibile il calcolo. Questo perché qualsiasi numero inferiore a è più piccolo del prodotto di dieci numeri primi distinti, quindi la stragrande maggioranza delle somme svanisce.c ϕ ( y , c ) = y - ∑ 1 ≤ k ≤ c , p k ≤ y ϕ ( yϕ ( 0 , c ) = 0c2⋅109ϕ ( y, c ) = y- ∑1 ≤ k ≤ c , pK≤yϕ(ypk,k−1)2⋅109
Inoltre, raggruppando e le prime sintesi della definizione di , otteniamo la formula alternativa . Pertanto, pre-calcolare per una fissa e i valori appropriati di salva la maggior parte delle chiamate di funzione rimanenti e i calcoli associati.c ′ ϕ ϕ ( y , c ) = ϕ ( y , c ′ ) - ∑ c ′ < k ≤ c , p k ≤ y ϕ ( yyc'φϕ(y,c′)c′ϕ (y, c ) = ϕ (y, c')−∑c′<k≤c,pk≤yϕ(ypk,k−1)ϕ(y,c′)c'y
Se , allora , poiché gli interi in che sono divisibili per nessuno di sono precisamente quelli che sono coprime di . Inoltre, poiché , abbiamo che . ϕ ( m c , c )mc= ∏1≤k≤cpk[ 1 , m c ] p 1 , ⋯ , p c m c gcd ( z + m c , m c ) = mcd ( z , m c ) = ϕϕ(mc,c)=φ(mc)[1,mc]p1,⋯,pcmcϕ ( y ,gcd(z+mc,mc)=gcd(z,mc)ϕ(y,c)=ϕ(⌊ymc⌋mc,c)+ϕ(y
Dato che la funzione di totaggio di Eulero è moltiplicativa, , e abbiamo un modo semplice per derivare per tutti precompilando i valori solo per quelli in .φ ( mc) = ∏1 ≤ k ≤ cφ ( pK) = ∏1 ≤ k ≤ c( pK- 1 )y y [ 0 , m c )ϕ (y, c )yy[0,mc)
Inoltre, se impostiamo , otteniamo , il definizione originale dall'articolo di Lehmer. Questo ci dà un modo semplice per pre-calcolare per aumentare i valori di .ϕ ( y , c ) = ϕ ( y , c - 1 )c′=c−1ϕ(y,cϕ(y,c)=ϕ(y,c−1)−ϕ(ypc,c−1)cϕ(y,c)c
Inoltre per il pre-calcolo per un certo valore basso di , lo pre-calcoleremo anche per valori bassi di , tagliando la ricorsione poco dopo essere scesa sotto una certa soglia.cϕ(y,c)cy
Implementazione
La sezione precedente copre la maggior parte delle parti del codice. Un dettaglio rimanente e importante è come Phi
vengono eseguite le divisioni nella funzione .
Poiché computing richiede solo la divisione per i primi numeri primi , possiamo invece usare la funzione. Invece di dividere semplicemente una per una primaria , moltiplichiamo per e recuperiamo come . A causa di come viene implementata la moltiplicazione dei numeri interi su x64 , non è richiesta la divisione per ; i 64 bit più alti di sono memorizzati nel proprio registro.ϕypydpπ(x−−√3)fastdiv
ypydp≈264pyp 264dpy264264dpy
Si noti che questo metodo richiede il pre calcolo di , che non è più veloce del computing direttamente. Tuttavia, poiché dobbiamo dividere gli stessi numeri primi più e più volte e la divisione è molto più lenta della moltiplicazione, ciò si traduce in un importante accelerazione. Maggiori dettagli su questo algoritmo, nonché una dimostrazione formale, possono essere trovati in Division da Invariant Integers usando la moltiplicazione .dpyp