C, 0,026119s (12 березня 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);
}
Для цього використовується метод Мейселя-Лемера .
Хронометраж
На моїй машині я отримую приблизно 5,7 мілісекунд для комбінованих тестових випадків. Це на Intel Core i7-3770 з оперативною пам’яттю DDR3 на 1867 МГц, працює на 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
Оскільки дисперсія стала надто великою , я використовую таймінги в рамках програми для неофіційних часів виконання. Це сценарій, який обчислював середнє значення комбінованого часу виконання.
#!/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
Офіційні часи
Це час для того, щоб робити рахунки в 1000 разів.
real 0m28.006s
user 0m15.703s
sys 0m14.319s
Як це працює
Формула
Нехай - натуральне число.х
Кожне додатне ціле число задовольняє рівно одній з наступних умов.n ≤ x
n = 1
н ділиться на просте число в .p[ 1 , х--√3]
n = p q , де і є (не обов'язково різними) простими числами у .pq( х--√3, х2--√3)
н є простим іn>x−−√3
Нехай позначає кількість простих чисел таким, що . Є числа які належать до четвертої категорії.π(y)pp≤yπ(x)−π(x−−√3)
Нехай позначає величину натуральних чисел що є добутком точно простих чисел, не серед перших простих чисел. Є номери які потрапляють у третю категорію.Pk(y,c)m≤ykcP2(x,π(x−−√3))
Нарешті, нехай позначає кількість натуральних чисел які є одночасними до перших простих чисел. Існують числа які належать до другої категорії.ϕ(y,c)k≤ycx−ϕ(x,π(x−−√3))
Оскільки у всіх категоріях є числа,x
1+x−ϕ(x,π(x−−√3))+P2(x,π(x−−√3))+π(x)−π(x−−√3)=x
і тому,
π(x)=ϕ(x,π(x−−√3))+π(x−−√3)−1−P2(x,π(x−−√3))
Числа в третій категорії мають унікальне подання, якщо нам потрібен і, отже, . Таким чином, добуток праймерів і належить до третьої категорії тоді і лише тоді, коли , тому є можливих значень для фіксованого значення , а , де позначає просте число .p≤qp≤x−−√pqx−−√3<p≤q≤xpπ(xp)−π(p)+1qpP2(x,π(x−−√3))=∑π(x√3)<k≤π(x√)(π(xpk)−π(pk)+1)pkkth
Нарешті, кожне додатне ціле число яке не є копернім першим простим числам, може бути виражене унікальним чином як , де - найнижчий простий коефіцієнт . Таким чином, , і є співвідношенням до перших простих чисел.n≤ycn=pkfpknk≤cfk−1
Це призводить до рекурсивної формули . Зокрема, сума порожня, якщо , тому .ϕ(y,c)=y−∑1≤k≤cϕ(ypk,k−1)c=0ϕ(y,0)=y
Тепер у нас є формула, яка дозволяє обчислити , генеруючи лише перші прості числа (мільйони проти мільярдів).π(x)π(x2−−√3)
Алгоритм
Нам знадобиться обчислити , де може бути таким же низьким, як . Хоча є й інші способи зробити це (наприклад, застосовувати нашу формулу рекурсивно), як видається, найшвидший спосіб перерахувати всі прайми до , що можна зробити за допомогою сита Ератосфена.π(xp)px−−√3x2−−√3
Спочатку ми ідентифікуємо і зберігаємо всі прості числа в і обчислюємо і одночасно. Потім ми обчислюємо для всіх in , і підраховуємо праймери до кожного наступного коефіцієнта .[1,x−−√]π(x−−√3)π(x−−√)xpkk(π(x−−√3),π(x−−√)]
Також має закриту форму , який дозволяє нам виконати обчислення .∑π(x√3)<k≤π(x√)(−π(pk)+1)π(x√3)−π(x√))(π(x√3)+π(x√)−12P2(x,π(x−−√3))
Це залишає обчислення , що є найдорожчою частиною алгоритму. Просто використання рекурсивної формули вимагатиме викликів функції для обчислення .ϕ2cϕ(y,c)
Перш за все, для всіх значень , тому . Саме по собі цього спостереження вже достатньо, щоб зробити обчислення здійсненним. Це тому, що будь-яке число нижче менше, ніж добуток будь-яких десяти різних прайменів, тому переважна більшість сум не зникає.ϕ(0,c)=0cϕ(y,c)=y−∑1≤k≤c,pk≤yϕ(ypk,k−1)2⋅109
Також, групуючи та перші підсумки визначення , ми отримуємо альтернативну формулу . Таким чином, попереднє обчислення для фіксованого та відповідних значень зберігає більшість залишків викликів функцій та пов'язаних з ними обчислень.yc′ϕϕ(y,c)=ϕ(y,c′)−∑c′<k≤c,pk≤yϕ(ypk,k−1)ϕ(y,c′)c′y
Якщо , то , оскільки цілі числа в які не поділяються на жодне з це саме ті, що відносяться до . Також, оскільки , маємо, що .mc=∏1≤k≤cpkϕ(mc,c)=φ(mc)[1,mc]p1,⋯,pcmcgcd(z+mc,mc)=gcd(z,mc)ϕ(y,c)=ϕ(⌊ymc⌋mc,c)+ϕ(y
Оскільки тотативна функція Ейлера мультипликативна, , і ми маємо простий спосіб отримати для всіх значення лише для тих, хто в .φ(mc)=∏1≤k≤cφ(pk)=∏1≤k≤c(pk−1)ϕ(y,c)yy[0,mc)
Також, якщо встановити , отримаємо , оригінальне визначення з статті Лемера. Це дає нам простий спосіб скласти обчислення для збільшення значень .c′=c−1ϕ(y,c)=ϕ(y,c−1)−ϕ(ypc,c−1)ϕ(y,c)c
Окрім попереднього обчислення для певного низького значення , ми також попередньо обчислимо його для низьких значень , скорочуючи рекурсію короткою після падіння нижче певного порогу.ϕ(y,c)cy
Впровадження
Попередній розділ охоплює більшість частин коду. Ще одна важлива деталь - це те, як Phi
виконуються підрозділи у функції .
Оскільки для обчислення потрібне ділення лише на перші прості числа, ми можемо замість цього використовувати функцію. Замість того, щоб просто ділити на простий , ми помножимо на замість цього і відновимо як . Через те, як реалізовано ціле множення на x64 , ділення на не потрібно; вищі 64 біти зберігаються у власному реєстрі.ϕπ(x−−√3)fastdiv
ypydp≈264pyp 264дпуdpy264264dpy
Зауважте, що цей метод вимагає попереднього , що не швидше, ніж обчислення безпосередньо. Однак, оскільки нам доводиться ділитися тими ж праймерами знову і знову, і ділення відбувається набагато повільніше, ніж множення, це призводить до важливого прискорення. Більш детальну інформацію про цей алгоритм, а також про офіційне підтвердження можна знайти в розділі Invariant Integers за допомогою множення .уdpyp