[分享] 計算π到小數點下一億位
看板C_and_CPP (C/C++)作者Schottky (Schottky)時間11年前 (2013/09/18 18:18)推噓4(4推 0噓 0→)留言4則, 4人參與討論串1/2 (看更多)
其實上一篇「計算 e 到一億位」是為這一篇鋪路,
用的演算法大致相同, 只是公式較複雜。
原始碼: http://codepad.org/vGYg1YaD
Compile 這段程式需要 gnu mp 及 gnu mpfr 兩個程式庫,
Linux 可以直接安裝各 distribution 提供的程式開發套件, 這兩項都是標準配備,
Dev C++ 搭配 MinGW32 的版本可以下載 MinGW 官方網站的套件:
MinGW GMP: http://sourceforge.net/projects/mingw/files/MinGW/Base/gmp/
MinGW MPFR: http://sourceforge.net/projects/mingw/files/MinGW/Base/mpfr/
在 64-bit Linux, 2.4GHz CPU 上計算一億位大約需要 2GB RAM, 時間約六分鐘。
這次採用 GMP 的整數運算和 MPFR 的浮點運算,
MPFR 比起 GMP 的 mpf 浮點運算有幾個優點:
1. 精確度 (precision) 定義明確, 使用 32-bit 和 64-bit library 不會在尾數
有差異, 而且可以從 MPFR_PREC_MAX 知道 precision 最大值, 不必用猜的。
2. 尾數的取捨有明確定義, 是要無條件捨去還是四捨五入都要在運算時指明。
3. 轉換成小數字串時, GMP 會自動省略尾數的 0, MPFR 不會。當計算π到 50 位
時, 最後一位剛好就是 0, 少一位會讓人懷疑是不是偷工減料。
4. 豐富完整的數學運算函數, 如 log, 非整數次方, 三角函數等等。
MPFR 的缺點則是:
1. MPFR 效能比 GMP 慢 10% 左右, 但目前這個程式大部份時間並非耗在浮點運算,
而是在遞迴函數內的整數乘法, 所以影響不大。
2. 多一個 library 要 link, 多幾行安裝說明要寫, 有點不方便。 (還是寫了)
3. 最大精確度只有 GMP 的一半, 但沒有人有這麼大的 RAM 所以也沒差。
整體來說我還是比較喜歡 MPFR, 因為設計得很細心, 對極限細微操作的掌握較好。
以這篇來說, 不會有人跑來問我輸入 50 位為什麼只出現 49 位小數...
http://en.wikipedia.org/wiki/Pi#Rapidly_convergent_series
用來計算π的是 Chudnovsky 公式, 維基百科寫每多計算一項增加 14 位有效小數,
我推導出來是當 N 值越大, 每項增加的位數趨近 3*log10(53360) = 14.1816474627
所以可以直接用除法計算數列共需計算幾項, 才能到達使用者要求的精確度。
R(a,b)*P(a,b)
定義 S(a,b) = -------------
Q(a,b)
(6b)! (6a)!
R(a,b) = ----- ÷ ----- (第 a+1 項到第 b 項提出來的乘數)
(3b)! (3a)!
b!
Q(a,b) = (----)^3*(640320)^(3*(b-a)) (第 a+1 項到第 b 項的分母)
a!
P(a,b) = 第 a+1 項到第 b 項的 (13591409+545140134*k) 分子部份通分後的和
剩下的就和上一篇計算 e 的級數一樣, 使用遞迴函數做 Divide and Conquer 。
為什麼是第 a+1 項到第 b 項? 這有點難解釋, 寫過一遍會比較容易理解...
這樣定義後在 merge 時可以省掉一個小乘法運算, 銜接處比較簡潔。
這次使用一模一樣的進度條。進度條的作用並不是逗貓或讓無聊的使用者打發時間,
而是讓我自己知道程式有沒有在跑? 大概還要跑多久? 藉此判斷是不是該喊卡了。
所以只要程式執行時間會超過一秒, 大概都會需要個進度條或里程表。
進度條並不難做, 短短 20 行左右的一個小函式就行了, 而且很容易重複使用。
另一個計算π的公式是 Gauss-Legendre 法, 這個數列無法使用 Divide & Conquer,
只能依序計算。它的收斂能力非常高, 23 項就有一千萬位, 27 項就有一億位。
每多算一項, 就會產生兩倍的有效數字, 而且使用 MPFR 來寫, 程式可以很簡潔。
我對高斯法的細節還沒有深入研究, 各位如果有興趣可以 implement 看看 :)
// URL: http://codepad.org/vGYg1YaD
// -------- >8 -------- >8 -------- >8 -------- >8 -------- >8 --------
#include <stdio.h>
#include <stdlib.h>
#include <gmp.h>
#include <mpfr.h>
#include <math.h>
#define FILENAME "pi.txt"
unsigned long long int count, total, old_p;
void progress(void)
{
int p, g, c;
p = (int)floorf(1000.0f*count/total+0.5f);
if (p > old_p) {
old_p = p;
g = (int)floorf(72.0f*count/total+0.5f);
for (c=0; c<72; c++) {
if (c<g) printf("H");
else printf("-");
}
printf(" %5.1f%%\r", p/10.0);
fflush(stdout);
}
if (count == total) {
printf("\n");
}
}
void write_pi(mpfr_t pi, int digits)
{
char *pi_str;
mp_exp_t exp;
int c;
FILE *fp;
pi_str = malloc(digits+1);
fp = fopen(FILENAME, "w+");
mpfr_get_str(pi_str, &exp, 10, digits, pi, MPFR_RNDZ);
fprintf(fp, "pi=");
for (c=0; c<digits; c++) {
if (c!=1) {
if (c%50 == 1) {
fprintf(fp, " ");
}
}
fprintf(fp, "%c", pi_str[c]);
if (c==0) {
fprintf(fp, ".");
} else if (c%1000 == 0) {
fprintf(fp, " << %d\n", c);
} else if (c%500 == 0) {
fprintf(fp, " <\n");
} else if (c%50 == 0) {
fprintf(fp, "\n");
} else if (c%5 == 0) {
fprintf(fp, " ");
}
}
if (c%50 != 1) {
fprintf(fp, "\n");
}
fclose(fp);
free(pi_str);
}
void s(mpz_t a, mpz_t b, mpz_t max, mpz_t p, mpz_t q, mpz_t r)
{
mpz_t m, p1, q1, r1;
mpz_inits(m, p1, q1, r1, NULL);
mpz_sub(m, b, a);
if (mpz_cmp_ui(m, 0) <= 0) {
gmp_printf("Error! s(%Zd, %Zd).\n", a, b);
} else if (mpz_cmp_ui(m, 1) == 0) {
if (mpz_cmp_ui(a, 0) == 0) {
mpz_ui_pow_ui(q, 640320, 3);
mpz_set_ui(r, 120); // 6!
mpz_mul_ui(p, q, 13591409);
mpz_submul_ui(p, r, 13591409+545140134);
} else {
mpz_set_ui(r, 8);
mpz_mul_ui(m, a, 6);
mpz_add_ui(m, m, 1);
mpz_mul(r, r, m); // r *= 6a+1;
mpz_add_ui(m, m, 2);
mpz_mul(r, r, m); // r *= 6a+3;
mpz_add_ui(m, m, 2);
mpz_mul(r, r, m); // r *= 6a+5;
mpz_mul_ui(q, b, 640320);
mpz_pow_ui(q, q, 3);
mpz_set_ui(p, 13591409);
mpz_addmul_ui(p, b, 545140134);
mpz_mul(p, p, r);
if (mpz_tstbit(b, 0) == 1) { mpz_neg(p, p); }
}
} else {
mpz_add(m, a, b);
mpz_fdiv_q_ui(m, m, 2);
s(a, m, max, p, q, r);
s(m, b, max, p1, q1, r1);
// Merge
mpz_mul(p, p, q1);
mpz_addmul(p, p1, r);
mpz_mul(q, q, q1);
if (mpz_cmp(b, max) == 0) {
mpz_realloc2(r, 1);
} else {
mpz_mul(r, r, r1);
}
}
// gmp_printf("s(%Zd, %Zd) = %Zd / %Zd (%Zd)\n", a, b, p, q, r);
count++;
progress();
mpz_clears(m, p1, q1, r1, NULL);
}
void chudnovsky(unsigned long int digits)
{
unsigned long int d, n, precision;
mpz_t max, p, q, r, z;
mpfr_t pf, qf;
d = digits+1;
n = ceill(d*logl(10)/(3*logl(53360)));
precision = ceill(d*logl(10.0)/logl(2.0));
count = old_p = 0;
total = n*2-1;
printf("Digits = %lu, N = %lu, Precision = %lu\n",
d, n, precision);
mpz_inits(max, p, q, r, z, NULL);
mpfr_inits2(precision, pf, qf, NULL);
mpz_set_ui(max, n);
mpz_set_ui(z, 0);
s(z, max, max, p, q, r);
mpz_mul_ui(q, q, 426880);
mpfr_set_z(pf, p, MPFR_RNDN);
mpfr_set_z(qf, q, MPFR_RNDN);
mpfr_div(pf, qf, pf, MPFR_RNDN);
mpfr_sqrt_ui(qf, 10005, MPFR_RNDN);
mpfr_mul(pf, pf, qf, MPFR_RNDN);
mpz_clears(max, p, q, r, z, NULL);
printf("Calculation done. Writing result to file <%s>...\n", FILENAME);
write_pi(pf, d);
mpfr_clears(pf, qf, NULL);
}
int main(int argc, char *argv[])
{
mpfr_t digits, max_digit;
mpfr_inits2(70, digits, max_digit, NULL);
// max_gitit = MPFR_PREC_MAX*log10(2)
mpfr_set_ui(max_digit, 2, MPFR_RNDN);
mpfr_log10(max_digit, max_digit, MPFR_RNDN);
mpfr_mul_ui(max_digit, max_digit, MPFR_PREC_MAX, MPFR_RNDN);
mpfr_sub_ui(max_digit, max_digit, 1, MPFR_RNDN);
mpfr_floor(max_digit, max_digit);
if (argc == 2) {
mpfr_set_str(digits, argv[1], 10, MPFR_RNDZ);
mpfr_floor(digits, digits);
if (mpfr_cmp_ui(digits, 1) < 0 || mpfr_cmp(digits, max_digit) > 0) {
mpfr_fprintf(stderr, "Invalid parameter, "
"digits must range from 1 to %.0Rf\n", max_digit);
return -1;
}
} else if (argc > 2) {
fprintf(stderr, "Usage: %s #digits\n", argv[0]);
return -1;
} else {
mpfr_set_ui(digits, 100000000, MPFR_RNDN);
}
chudnovsky(mpfr_get_ui(digits, MPFR_RNDZ));
mpfr_clears(digits, max_digit, NULL);
return 0;
}
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 118.167.24.180
推
09/18 20:56, , 1F
09/18 20:56, 1F
推
09/18 20:56, , 2F
09/18 20:56, 2F
推
09/18 21:22, , 3F
09/18 21:22, 3F
推
09/18 21:46, , 4F
09/18 21:46, 4F
討論串 (同標題文章)
C_and_CPP 近期熱門文章
11
46
PTT數位生活區 即時熱門文章