C, 0.026119s (12. März 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);
}
Dies erfolgt nach der Meissel-Lehmer-Methode .
Timings
Auf meinem Rechner erhalte ich für die kombinierten Testfälle ungefähr 5,7 Millisekunden . Dies ist auf einem Intel Core i7-3770 mit DDR3-RAM bei 1867 MHz, auf dem openSUSE 13.2 ausgeführt wird.
$ ./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
Da die Varianz zu hoch wurde , verwende ich programminterne Timings für die inoffiziellen Laufzeiten. Dies ist das Skript, das den Durchschnitt der kombinierten Laufzeiten berechnet hat.
#!/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
Offizielle Zeiten
In dieser Zeit werden die Score-Fälle 1000-mal ausgeführt.
real 0m28.006s
user 0m15.703s
sys 0m14.319s
Wie es funktioniert
Formel
Sei eine positive ganze Zahl.x
Jede positive ganze Zahl erfüllt genau eine der folgenden Bedingungen.n≤x
n=1
n ist teilbar durch eine Primzahl in [ 1 , 3 √p.[1,x−−√3]
, wobei p und q (nicht notwendigerweise verschiedene) Primzahlen in ( 3 √ sindn=pqpq.(x−−√3,x2−−√3)
ist prim und n > 3 √nn>x−−√3
Sei die Anzahl der Primzahlen p, so dass p ≤ y ist . Es gibt π ( x ) - π ( 3 √π(y)pp≤yZahlen, die in die vierte Kategorie fallen.π(x)−π(x−−√3)
Sei die Menge der positiven ganzen Zahlen m ≤ y , die ein Produkt von genau k Primzahlen sind, die nicht zu den ersten c Primzahlen gehören. Es gibt P 2 ( x , π ( 3 √Pk(y,c)m≤ykcZahlen, die in die dritte Kategorie fallen.P2(x,π(x−−√3))
Schließlich sei die Anzahl der positiven ganzen Zahlen k ≤ y , die zu den ersten c Primzahlen koprime sind. Es gibt x - ϕ ( x , π ( 3 √ϕ(y,c)k≤ycZahlen, die in die zweite Kategorie fallen.x−ϕ(x,π(x−−√3))
Da es in allen Kategorien Zahlen gibt ,x
1+x−ϕ(x,π(x−−√3))+P2(x,π(x−−√3))+π(x)−π(x−−√3)=x
und deshalb,
π(x)=ϕ(x,π(x−−√3))+π(x−−√3)−1−P2(x,π(x−−√3))
Die Zahlen in der dritten Kategorie haben eine eindeutige Darstellung, wenn wir und daher p ≤ √ benötigenp≤q . Auf diese Weise fällt das Produkt der Primzahlenpundqgenau dann in die dritte Kategorie, wenn 3 √p≤x−−√pq , also gibt esπ(xx−−√3<p≤q≤xpmögliche Werte fürqfür einen festen Wert vonpundP2(x,π(3√)π(xp)−π(p)+1qp, wobeipkdiek-tePrimzahl bezeichnet.P2(x,π(x−−√3))=∑π(x√3)<k≤π(x√)(π(xpk)−π(pk)+1)pkkth
Schließlich jede positive ganze Zahl das ist nicht auf die ersten Koprimzahlen c Primzahlen kann in einzigartiger Art und Weise ausgedrückt werden als n = p k f , wobei p k die niedrigste Primfaktor ist n . Auf diese Weise ist k ≤ c und f gleichzeitig mit den ersten k - 1 Primzahlen.n≤ycn=pkfpknk≤cfk−1
Dies führt zu der rekursiven Formel . Insbesondere ist die Summe leer, wennc=0 ist, alsoϕ(y,0)=y.ϕ(y,c)=y−∑1≤k≤cϕ(ypk,k−1)c=0ϕ(y,0)=y
Wir haben jetzt eine Formel, mit der wir berechnen können, indem wir nur das erste π ( 3 √ ) erzeugenπ(x)Primzahlen (Millionen gegen Milliarden).π(x2−−√3)
Algorithmus
Wir müssen π ( x berechnen, wobeipnur3√ betragen kannπ(xp)p . Während es andere Möglichkeiten gibt, dies zu tun (wie das rekursive Anwenden unserer Formel), scheint der schnellste Weg darin zu bestehen, alle Primzahlen bis zu3 √ aufzulistenx−−√3 , was mit dem Sieb des Eratosthenes gemacht werden kann.x2−−√3
Zunächst identifizieren und speichern wir alle Primzahlen in und berechneπ( 3 √[1,x−−√]undπ(√π(x−−√3)gleichzeitig. Dann berechnen wir xπ(x−−√) für allekin(π(3√xpkkund zähle die Primzahlen bis zu jedem nachfolgenden Quotienten.(π(x−−√3),π(x−−√)]
Auch hat die geschlossene Formπ( 3 √∑π(x√3)<k≤π(x√)(−π(pk)+1) , wodurch wir die Berechnung vonP2(x,π(3√)vervollständigen könnenπ(x√3)−π(x√))(π(x√3)+π(x√)−12.P2(x,π(x−−√3))
Damit bleibt die Berechnung von , dem teuersten Teil des Algorithmus. Die einfache Verwendung der rekursiven Formel würde 2 c- Funktionsaufrufe erfordern , um ϕ ( y , c ) zu berechnen .ϕ2cϕ(y,c)
Zu allererst für alle Werte von c , so φ ( y , c ) = y - Σ 1 ≤ k ≤ c , p k ≤ y φ ( yϕ(0,c)=0c. Diese Beobachtung allein reicht bereits aus, um die Berechnung durchführbar zu machen. Dies liegt daran, dass jede Zahl unter2⋅109kleiner ist als das Produkt von zehn verschiedenen Primzahlen, sodass die überwiegende Mehrheit der Summanden verschwindet.ϕ(y,c)=y−∑1≤k≤c,pk≤yϕ(ypk,k−1)2⋅109
Durch Gruppieren von und den ersten c' - Summanden der Definition von ϕ erhalten wir auch die alternative Formel ϕ ( y , c ) = ϕ ( y , c ' ) - ∑ c ' < k ≤ c , p k ≤ y ϕ ( yyc′ϕ. Somit werden durch Vorberechnen vonϕ(y,c')für ein festesc'und geeignete Werte vony diemeisten verbleibenden Funktionsaufrufe und die zugehörigen Berechnungen gespart.ϕ(y,c)=ϕ(y,c′)−∑c′<k≤c,pk≤yϕ(ypk,k−1)ϕ(y,c′)c′y
Wenn , dann ist ϕ ( m c , c ) = φ ( m c ) , da die ganzen Zahlen in [ 1 , m c ] durch keines von p 1 , ⋯ , p teilbar sind c sind genau diejenigen, die zu m c coprime sind . Auch da gcd ( z + m c , mmc=∏1≤k≤cpkϕ(mc,c)=φ(mc)[1,mc]p1,⋯,pcmc , wir haben das ϕ ( y , c ) = ϕ ( ⌊ ygcd(z+mc,mc)=gcd(z,mc).ϕ(y,c)=ϕ(⌊ymc⌋mc,c)+ϕ(y
Da die Euler'sche Totientenfunktion multiplikativ ist, ist , und wir haben einen einfachen Weg, ϕ ( y , c ) für alle y durch Vorausberechnung der Werte nur für die y in [ 0 , m c ) .φ(mc)=∏1≤k≤cφ(pk)=∏1≤k≤c(pk−1)ϕ(y,c)yy[0,mc)
Wenn wir , erhalten wir auch ϕ ( y , c ) = ϕ ( y , c - 1 ) - ϕ ( yc′=c−1, die ursprüngliche Definition aus Lehmers Arbeit. Dies gibt uns eine einfache Möglichkeit,ϕ(y,c)vorab zu berechnen,umdie Werte vonczu erhöhen.ϕ(y,c)=ϕ(y,c−1)−ϕ(ypc,c−1)ϕ(y,c)c
Zusätzlich zum Vorberechnen von für einen bestimmten, niedrigen Wert von c berechnen wir ihn auch für niedrige Werte von y vor und kürzen die Rekursion, nachdem ein bestimmter Schwellenwert unterschritten wurde.ϕ(y,c)cy
Implementierung
Der vorherige Abschnitt behandelt die meisten Teile des Codes. Ein verbleibendes wichtiges Detail ist, wie die Unterteilungen in der Funktion Phi
durchgeführt werden.
Da für die Berechnung von nur das erste π ( 3 √) dividiert werden mussϕPrimzahlen, wir könnenstattdessendieFunktion verwenden. Anstatt einfach einydurch eine Primzahlp zudividieren, multiplizieren wirymitdp≈ 2 64π(x−−√3)fastdiv
ypy stattdessen und erholenydp≈264p alsdpyyp . Aufgrund der Implementierung der ganzzahligen Multiplikation inx64ist das Teilen durch264nicht erforderlich. die höheren 64 Bits vondpywerden in ihrem eigenen Register gespeichert.dpy264264dpy
Beachten Sie, dass für diese Methode die Vorberechnung von erforderlich ist , was nicht schneller ist als die Berechnung von ydp direkt. Da wir uns jedoch immer wieder durch dieselben Primzahlen teilen müssen und die Teilungso viel langsamerals die Multiplikation ist, führt dies zu einer bedeutenden Beschleunigung. Weitere Details zu diesem Algorithmus sowie einen formalen Beweis finden Sie inDivision durch invariante ganze Zahlen mit Multiplikation.yp