指定位數的圓周率
December 3, 2021圓周率後的小數位數是無止境的,使用電腦來計算無止境的小數,是數學家與程式設計師感興趣的課題,這邊介紹的公式配合〈大數運算〉,可以計算指定位數的圓周率。
解法思路
- 首先介紹 J.Marchin 的圓周率公式:
-
PI = [16/5 - 16/(3*5³) + 16/(5*5⁵) - 16/(7*5⁷) + ……] - [4/239 - 4/(3*239³) + 4/(5*239⁵) - 4/(7*239⁷) + ……]
- 可以將這個公式整理為:
-
PI = [16/5 - 4/239] - [16/5³ - 4/239³]/3 + [16/5⁵ - 4/239⁵]/5 + ……
- 也就是說第 n 項,若 n 為奇數則為正數,為偶數則為負數,而項數表示方式為:
-
[16/5²ⁿ⁻¹ - 4/239²ⁿ⁻¹] / (2*n-1)
- 如果要計算圓周率至 10 的負 L 次方,由於 [16/5²ⁿ⁻¹ - 4/239²ⁿ⁻¹] 中 16/5²ⁿ⁻¹ 比 4/239²ⁿ⁻¹ 來得大,具有決定性,表示至少必須計算至第 n 項:
-
[16 /5²ⁿ⁻¹] / (2*n-1) = 10⁻ᶫ
- 將上面的等式取 log 後化簡,可以求得:
-
n = L/(2log5) = L/1.39794
- 若要求精確度至小數後 L 位數,則只要求至公式的第 n 項,其中 n 等於:
-
n = [L / 1.39794] + 1
- 在上式中 [] 為高斯符號,也就是取至整數(不大於 L/1.39794 的整數);為了計算方便,可以在程式中使用下面這個公式來計算第 n 項:
-
[Wₙ₋₁/5²ⁿ - Vₙ₋₁/239²ⁿ] / (2*n-1)
其中 n 從 1 開始,而 W ₀ 為 16 * 5,V₀ 為 4 * 239,這個公式的演算法配合大數運算函式的演算法為:
div(w, 25, w);
divide(v, 57121, v); // 239 * 239 = 57121
sub(w, v, q);
div(q, 2 * k - 1, q);
最後的 PI 就是由單獨求得的第 n 項加總而得,因而大數運算後第一個數字代表整數,第二個位數之後是代表小數。為了計算精確度至小數後 L 位數,大整數運算必須有 L + 1 個位數。如果具備處理浮點數精確度的 API,亦可直接使用,但要注意預留位數與四捨五入問題,因為在除法過程中,可能有循環小數無法表示的問題。
程式實作:
#include <stdio.h>
#include <stdlib.h>
#define L 1000
#define N L / 4 + 1
// L 為位數,N是array長度
// 只處理正數的大整數加、減、除
void add(int*, int*, int*);
void subtract(int*, int*, int*);
void divide(int*, int, int*);
int main(void) {
int s[N] = {0};
int w[N] = {0};
int v[N] = {0};
int q[N] = {0};
int n = (int) (L / 1.39793 + 1);
w[0] = 16 * 5;
v[0] = 4 * 239;
int k;
for(k = 1; k <= n; k++) {
// 套用公式
divide(w, 25, w);
divide(v, 57121, v); // 239 * 239 = 57121
subtract(w, v, q);
divide(q, 2 * k - 1, q);
if(k % 2) {// 奇數項
add(s, q, s);
} else { // 偶數項
subtract(s, q, s);
}
}
printf("%d.", s[0]);
for(k = 1; k < N; k++) {
printf("%04d", s[k]);
}
return 0;
}
void add(int* a, int* b, int* c) {
int i, carry = 0;
for(i = N - 1; i >= 0; i--) {
c[i] = a[i] + b[i] + carry;
if(c[i] < 10000) {
carry = 0;
} else { // 進位
c[i] = c[i] - 10000;
carry = 1;
}
}
}
void subtract(int* a, int* b, int* c) {
int i, borrow = 0;
for(i = N - 1; i >= 0; i--) {
c[i] = a[i] - b[i] - borrow;
if(c[i] >= 0) {
borrow = 0;
} else { // 借位
c[i] = c[i] + 10000;
borrow = 1;
}
}
}
void divide(int* a, int b, int *c) { // b 為除數
int i, tmp, remain = 0;
for(i = 0; i < N; i++) {
tmp = a[i] + remain;
c[i] = tmp / b;
remain = (tmp % b) * 10000;
}
}
import java.math.BigInteger;
public class LongPI {
private final BigInteger PI;
public LongPI(int L) {
int n = (int) (L / 1.39793 + 1);
BigInteger b25 = BigInteger.valueOf(25);
BigInteger b57121 = BigInteger.valueOf(57121);
BigInteger scale = BigInteger.valueOf(10).pow(L);
BigInteger w = BigInteger.valueOf(16 * 5).multiply(scale);
BigInteger v = BigInteger.valueOf(4 * 239).multiply(scale);
BigInteger s = BigInteger.valueOf(0);
BigInteger q = null;
for(int k = 1; k <= n; k++) {
w = w.divide(b25);
v = v.divide(b57121);
q = w.subtract(v).divide(BigInteger.valueOf(2 * k - 1));
s = k % 2 == 1 ? s.add(q) : s.subtract(q);
}
PI = s;
}
public String toString() {
String str = PI.toString();
return String.format("%c.%s", str.charAt(0), str.substring(1));
}
public static void main(String[] args) {
System.out.println(new LongPI(1000));
}
}
class LongPI:
def __init__(self, L):
n = int(L / 1.39793 + 1)
scale = 10 ** L
def pi(k, w, v):
if k == n + 1:
return 0
else:
wk = w // 25
vk = v // 57121
qk = (wk - vk) // (2 * k - 1)
return (qk if k % 2 == 1 else -qk) + pi(k + 1, wk, vk)
self.PI = pi(1, 16 * 5 * scale, 4 * 239 * scale)
def __str__(self):
s = str(self.PI)
return "%c.%s" % (s[0], s[1:])
print(LongPI(1000))
import scala.math.BigInt
class LongPI private (pi: BigInt) {
override def toString = {
val str = pi.toString
"%c.%s".format(str(0), str.tail)
}
}
object LongPI {
def apply(l: Int) = {
val n = (l / 1.39793 + 1).toInt
val b25 = BigInt(25)
val b57121 = BigInt(57121)
val scale = BigInt(10).pow(l)
def pi(k: Int, w: BigInt, v: BigInt): BigInt = {
if(k == n + 1) BigInt(0)
else {
val wk = w / b25
val vk = v / b57121
val qk = (wk - vk) / BigInt(2 * k - 1)
(if(k % 2 == 1) qk else -qk) + pi(k + 1, wk, vk)
}
}
new LongPI(pi(1, BigInt(16 * 5) * scale, BigInt(4 * 239) * scale))
}
}
print(LongPI(1000))
class LongPI
def initialize(l)
n = (l / 1.39793 + 1).to_i
scale = 10 ** l
p = ->(k, w, v) {
if k == n + 1
0
else
wk = w / 25
vk = v / 57121
qk = (wk - vk) / (2 * k - 1)
(if k % 2 == 1; qk else -qk end) + p.call(k + 1, wk, vk)
end
}
@pi = p.call(1, 16 * 5 * scale, 4 * 239 * scale)
end
def to_s
str = @pi.to_s
sprintf("%c.%s", str[0], str[1, str.size])
end
end
puts(LongPI.new(1000))
// 用到大數運算中的 BigNumber
var LongPI = function() {
function apply(L) {
var n = parseInt(L / 1.39793 + 1);
var b25 = BigNumber('25');
var b57121 = BigNumber('57121');
var scale = BigNumber('1' + new Array(L + 1).join('0'));
var w = BigNumber('80').multiply(scale);
var v = BigNumber('956').multiply(scale);
var s = BigNumber('0');
for(var k = 1; k <= n; k++) {
w = w.divide(b25);
v = v.divide(b57121);
q = w.subtract(v).divide(BigNumber((2 * k - 1) + ''));
s = k % 2 === 1 ? s.add(q) : s.subtract(q);
}
return new LongPI(s);
}
function LongPI(PI) {
this.PI = PI;
}
LongPI.prototype.toString = function() {
var str = this.PI.toString();
return str[0] + '.' + str.substring(1);
};
return apply;
}();
print(LongPI(50));
data LongPI = LongPI Integer
instance Show LongPI where
show (LongPI value) = (head s) : '.' : (tail s)
where s = show value
longPi l = LongPI $ p 1 (16 * 5 * scale) (4 * 239 * scale)
where n = truncate (fromIntegral l / 1.39793 + 1)
scale = 10 ^ l
p k w v =
if k == n + 1 then 0
else
let wk = w `div` 25
vk = v `div` 57121
qk = (wk - vk) `div` (2 * k - 1)
in (if odd k then qk else -qk) + (p (k + 1) wk vk)
main = print $ longPi 1000