C, 0,026119 (ngày 12 tháng 3 năm 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);
}
Điều này sử dụng phương pháp Meissel-Lehmer .
Thời gian
Trên máy của tôi, tôi nhận được khoảng 5,7 mili giây cho các trường hợp thử nghiệm kết hợp. Đây là trên Intel Core i7-3770 với RAM DDR3 ở mức 1867 MHz, chạy 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
Vì phương sai tăng quá cao , tôi đang sử dụng thời gian từ trong chương trình cho thời gian chạy không chính thức. Đây là tập lệnh tính trung bình của thời gian chạy kết hợp.
#!/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
Thời gian chính thức
Lần này là để làm các trường hợp điểm số 1000 lần.
real 0m28.006s
user 0m15.703s
sys 0m14.319s
Làm thế nào nó hoạt động
Công thức
Đặt là số nguyên dương.x
Mỗi số nguyên dương thỏa mãn chính xác một trong các điều kiện sau.n ≤ x
n = 1
viết sai rồi chia hết cho số nguyên tố trong .p[ 1 , x--√3]
n = p q , trong đó và là các số nguyên tố (không nhất thiết phải phân biệt) trong .pq( x--√3, x2--√3)
viết sai rồi là số nguyên tố vàn > x--√3
Gọi là số nguyên tố sao cho . Có các số thuộc loại thứ tư.π( y)pp ≤ yπ( x ) - π( x--√3)
Gọi biểu thị số lượng số nguyên dương là tích của số nguyên tố chính xác không nằm trong số các số nguyên tố đầu tiên . Có các số thuộc loại thứ ba.Pk( y, c )m ≤ ykcP2( x , π( x--√3) )
Cuối cùng, hãy để biểu thị số lượng số nguyên dương các số nguyên tố đầu tiên . Có các số thuộc loại thứ hai.ϕ ( y, c )k ≤ ycx - φ ( x , π( x--√3) )
Vì có số trong tất cả các loại,x
1+x−ϕ(x,π(x−−√3))+P2(x,π(x−−√3))+π(x)−π(x−−√3)=x
và do đó,
π(x)=ϕ(x,π(x−−√3))+π(x−−√3)−1−P2(x,π(x−−√3))
Các số trong danh mục thứ ba có một đại diện duy nhất nếu chúng tôi yêu cầu và, do đó . Theo cách này, sản phẩm của các số nguyên tố và nằm trong danh mục thứ ba khi và chỉ khi , do đó, có giá trị có thể có cho với giá trị cố định là và , trong đó biểu thị số nguyên tố .p≤qp≤x−−√pqx−−√3<p≤q≤xpπ(xp)−π(p)+1qpP2(x,π(x−−√3))=∑π(x√3)<k≤π(x√)(π(xpk)−π(pk)+1)pkkth
Cuối cùng, tất cả các số nguyên dương được không nguyên tố cùng nhau để là người đầu tiên số nguyên tố có thể được biểu diễn trong thời trang độc đáo như , nơi là yếu tố quan trọng nhất của . Theo cách này, và là số nguyên tố của các số nguyên tố đầu tiên .n≤ycn=pkfpknk≤cfk−1
Điều này dẫn đến công thức đệ quy . Cụ thể, tổng là trống nếu , vì vậy .ϕ(y,c)=y−∑1≤k≤cϕ(ypk,k−1)c=0ϕ(y,0)=y
Bây giờ chúng ta có một công thức cho phép chúng ta tính toán bằng cách chỉ tạo các số nguyên tố đầu tiên (hàng triệu so với hàng tỷ).π(x)π(x2−−√3)
Thuật toán
Chúng ta sẽ cần tính toán , trong đó có thể đạt mức thấp như . Mặc dù có nhiều cách khác để làm điều này (như áp dụng công thức của chúng tôi một cách đệ quy), cách nhanh nhất dường như là liệt kê tất cả các số nguyên tố lên đến , có thể được thực hiện bằng sàng Eratosthenes.π(xp)px−−√3x2−−√3
Đầu tiên, chúng tôi xác định và lưu trữ tất cả các số nguyên tố trong và tính toán và cùng một lúc. Sau đó, chúng tôi tính toán cho tất cả trong và đếm các số nguyên tố theo từng thương số kế tiếp .[1,x−−√]π(x−−√3)π(x−−√)xpkk(π(x−−√3),π(x−−√)]
Ngoài ra, có dạng đóng , trong đó cho phép chúng tôi hoàn thành việc tính toán .∑π(x√3)<k≤π(x√)(−π(pk)+1)π(x√3)−π(x√))(π(x√3)+π(x√)−12P2(x,π(x−−√3))
Điều đó làm cho tính toán của , phần đắt nhất của thuật toán. Chỉ cần sử dụng công thức đệ quy sẽ yêu cầu các hàm gọi để tính toán .ϕ2cϕ(y,c)
Trước hết, cho tất cả các giá trị của , vì vậy . Chính nó, quan sát này đã đủ để làm cho tính toán khả thi. Điều này là do bất kỳ số nào dưới nhỏ hơn sản phẩm của mười số nguyên tố khác biệt, do đó, phần lớn các triệu hồi áp đảo biến mất.ϕ(0,c)=0cϕ(y,c)=y−∑1≤k≤c,pk≤yϕ(ypk,k−1)2⋅109
Ngoài ra, bằng cách nhóm và các triệu tập đầu tiên của định nghĩa , chúng ta có được công thức thay thế . Do đó, tiền mã hóa cho một cố định và các giá trị thích hợp của lưu hầu hết các lệnh gọi hàm còn lại và các tính toán liên quan.yc′ϕϕ(y,c)=ϕ(y,c′)−∑c′<k≤c,pk≤yϕ(ypk,k−1)ϕ(y,c′)c′y
Nếu , thì , vì các số nguyên trong không chia hết cho chính xác là những phần tử tương ứng với . Ngoài ra, vì , chúng tôi có .mc=∏1≤k≤cpkϕ(mc,c)=φ(mc)[1,mc]p1,⋯,pcmcgcd(z+mc,mc)=gcd(z,mc)ϕ(y,c)=ϕ(⌊ymc⌋mc,c)+ϕ(y
Vì hàm tổng của Euler là số nhân, và chúng tôi có một cách dễ dàng để lấy được cho tất cả bằng cách tính trước các giá trị cho chỉ những trong .φ(mc)=∏1≤k≤cφ(pk)=∏1≤k≤c(pk−1)ϕ(y,c)yy[0,mc)
Ngoài ra, nếu chúng ta đặt , chúng ta sẽ có được , định nghĩa ban đầu từ bài báo của Lehmer. Điều này cho chúng ta một cách đơn giản để tiền mã hóa để tăng giá trị của .c′=c−1ϕ(y,c)=ϕ(y,c−1)−ϕ(ypc,c−1)ϕ(y,c)c
Ngoài việc tính toán trước cho một giá trị thấp nhất định của , chúng tôi cũng sẽ tính toán trước cho các giá trị thấp của , cắt ngắn đệ quy sau khi giảm xuống dưới một ngưỡng nhất định.ϕ(y,c)cy
Thực hiện
Phần trước bao gồm hầu hết các phần của mã. Một chi tiết quan trọng còn lại là cách phân chia chức năng Phi
được thực hiện.
Vì tính toán chỉ yêu cầu chia cho các số nguyên tố đầu tiên, nên chúng ta có thể sử dụng hàm thay thế. Thay vì chỉ đơn giản là chia một bởi một số nguyên tố , chúng ta nhân bởi thay vào đó và phục hồi như . Do cách nhân số nguyên được triển khai trên x64 , nên không cần chia ; 64 bit cao hơn được lưu trữ trong thanh ghi riêng của chúng.ϕπ(x−−√3)fastdiv
ypydp≈264pyp 264dpydpy264264dpy
Lưu ý rằng phương pháp này yêu cầu tính toán trước , không nhanh hơn so với tính toán trực tiếp . Tuy nhiên, vì chúng ta phải chia cho cùng một số nguyên tố lặp đi lặp lại và phép chia chậm hơn nhiều so với phép nhân, điều này dẫn đến việc tăng tốc quan trọng. Thông tin chi tiết về thuật toán này, cũng như bằng chứng chính thức, có thể được tìm thấy trong Division by Invariant Integers bằng phép nhân .ydpyp