bc, dc - 任意精度計算機

bc - プログラミング言語 C ライクな任意精度計算機

bc を使えば、シェルから数値計算が立派にできてしまう。bc については Wikipedia の bc の記述が判り易いので詳しくはそちらに譲るが、ここでは簡単な例題を他の言語を交えて紹介する。

まず、ストレージサイズでよく使う MiB(220), GiB(230), TiB(240), PiB(250), EiB(260), ZiB(270) の計算をやってみよう。まずは expr で、210 は 1024 であるので、

$ expr 1024 '*' 1024         
1048576

$ expr 1024 '*' 1024 '*' 1024     
1073741824

$ expr 1024 '*' 1024 '*' 1024 '*' 1024        
1099511627776

$ expr 1024 '*' 1024 '*' 1024 '*' 1024 '*' 1024        
1125899906842624

$ expr 1024 '*' 1024 '*' 1024 '*' 1024 '*' 1024 '*' 1024        
1152921504606846976

$ expr 1024 '*' 1024 '*' 1024 '*' 1024 '*' 1024 '*' 1024 '*' 1024
0

このように expr は任意精度計算機ではないので、ここではゼビバイト, ZiB(270) でオーバーフローを起こしてしまっている。

オーバーフローの問題は bash や zsh を使っても同様だ。

$ zsh -c 'for ((i=1; i<=10; i++)); do echo $((2**(10*i))); done'
1024
1048576
1073741824
1099511627776
1125899906842624
1152921504606846976
0
0
0
0

やはり zsh でもゼビバイト, ZiB(270) でオーバーフローを起こしてしまっている。

こういった多倍長演算は bc の得意とするところだ。

$ echo '2^(10*2)' | bc
1048576

$ echo '2^(10*3)' | bc
1073741824

$ echo '2^(10*4)' | bc
1099511627776

$ echo '2^(10*5)' | bc
1125899906842624

$ echo '2^(10*6)' | bc
1152921504606846976

$ echo '2^(10*7)' | bc
1180591620717411303424

$ echo '2^(10*8)' | bc
1208925819614629174706176

$ echo '2^(10*9)' | bc 
1237940039285380274899124224

$ echo '2^(10*10)' | bc
1267650600228229401496703205376

素晴らしい! MiB(220), GiB(230), TiB(240), PiB(250), EiB(260), ZiB(270), YiB(280) を越えて 290 バイト、2100 バイトでも計算可能だ。

ちなみに、bc は標準入力もしくは bc スクリプトファイルで命令を記述するので、expr のようにコマンドライン引数は解釈しないので注意。というより、expr はトークンをコマンドライン引数に任せ、構文解析を放棄している潔さが心地よい。それはさておき。

ここで、十進の国際単位系(SI)の KB(103), MB(106), GB(109), TB(1012), PB(1015), EB(1018), ZB(1021), YB(1024), 1027 バイト、1030 バイトとの乖離の割合「%」を求めてみよう。まずは簡単には以下のようになる。

$ echo '(2^(10*1)/10^(3*1)-1)*100' | bc -l    
2.40000000000000000000

$ echo '(2^(10*2)/10^(3*2)-1)*100' | bc -l
4.85760000000000000000

$ echo '(2^(10*3)/10^(3*3)-1)*100' | bc -l
7.37418240000000000000

$ echo '(2^(10*4)/10^(3*4)-1)*100' | bc -l
9.95116277760000000000

$ echo '(2^(10*5)/10^(3*5)-1)*100' | bc -l
12.58999068426240000000

$ echo '(2^(10*6)/10^(3*6)-1)*100' | bc -l
15.29215046068469760000

$ echo '(2^(10*7)/10^(3*7)-1)*100' | bc -l
18.05916207174113034200

$ echo '(2^(10*8)/10^(3*8)-1)*100' | bc -l 
20.89258196146291747000

$ echo '(2^(10*9)/10^(3*9)-1)*100' | bc -l 
23.79400392853802748900

$ echo '(2^(10*10)/10^(3*10)-1)*100' | bc -l
26.76506002282294014900

この例のように、浮動少数点を使いたい場合 'bc -l' とするとよい。

そもそも 1024 と SI の十進の3桁 1000 が近いという感覚?から使われた二進の単位系であるが、数が膨大になればその感覚がかなり乖離していくことがわかる。それはよいとして、上記の乖離の割合の様子を bc のスクリプトでスッキリと書いてみよう。以下のようになる。

$ echo 'for (i=1; i<=10; i++) { (2^(10*i)/10^(3*i)-1)*100 }' | bc -l
2.40000000000000000000
4.85760000000000000000
7.37418240000000000000
9.95116277760000000000
12.58999068426240000000
15.29215046068469760000
18.05916207174113034200
20.89258196146291747000
23.79400392853802748900
26.76506002282294014900

このようにワンライナーで簡単に結果を得ることができる。

ここまでは POSIX bc の範囲内であるが、現在実質的に使われているのは GNU bc の実装である。特に POSIX bc には print 文がないので、結果の表示やバッチ処理に困る。GNU bc なら以下のように表示できる。

$ echo 'for (i=1; i<=30; i++) { print i, ":\t", (2^(10*i)/10^(3*i)-1)*100, "\n" }' | bc -l
1:	2.40000000000000000000
2:	4.85760000000000000000
3:	7.37418240000000000000
4:	9.95116277760000000000
5:	12.58999068426240000000
6:	15.29215046068469760000
7:	18.05916207174113034200
8:	20.89258196146291747000
9:	23.79400392853802748900
10:	26.76506002282294014900
11:	29.80742146337069071300
12:	32.92279957849158729000
13:	36.11294676837538538500
14:	39.37965749081639463400
15:	42.72476927059598810500
16:	46.15016373309029182000
17:	49.65776766268445882400
18:	53.24955408658888583500
19:	56.92754338466701909500
20:	60.69380442589902755400
21:	64.55045573212060421500
22:	68.49966666969149871600
23:	72.54365866976409468500
24:	76.68470647783843295800
25:	80.92513943330655534900
26:	85.26734277970591267700
27:	89.71375900641885458100
28:	94.26688922257290709100
29:	98.92929456391465686200
30:	103.70359763344860862600

つまり、2300 と 1030 バイトでは二倍もの乖離となってしまうことがわかる。ストレージサイズと言う意味では、今の時代ではまだ考える必要もないことではあるかも知れないが。

dc - bc のバックエンド、スタック型言語の任意精度計算機

このように bc は任意精度計算が可能なコマンドであるが、歴史的にはスタック型言語 dc のフロントエンドとして動作するものであった。dc そのものは POSIX にも規定がないのであるが、逆ポーランド記法での計算の方が簡単な場合がたまにあるので、ここで解説する意義はあるだろう。

まず、先の、ストレージサイズでよく使う MiB(220), GiB(230), TiB(240), PiB(250), EiB(260), ZiB(270) の計算をやってみよう。

$ dc -e '2 10 2 * ^ p' 
1048576

$ dc -e '2 10 3 * ^ p'
1073741824

$ dc -e '2 10 4 * ^ p'
1099511627776

$ dc -e '2 10 5 * ^ p'
1125899906842624

$ dc -e '2 10 6 * ^ p'
1152921504606846976

$ dc -e '2 10 7 * ^ p'
1180591620717411303424

$ dc -e '2 10 8 * ^ p' 
1208925819614629174706176

$ dc -e '2 10 9 * ^ p' 
1237940039285380274899124224

$ dc -e '2 10 10 * ^ p' 
1267650600228229401496703205376

dc の演算はスタックに詰まれた数値をポップしてその結果をプッシュするといった形態をとる。また 'p' コマンドはスタックトップを表示するコマンドである。

さらに、先の、十進の国際単位系(SI)の KB(103), MB(106), GB(109), TB(1012), PB(1015), EB(1018), ZB(1021), YB(1024), 1027 バイト、1030 バイトとの乖離の割合「%」を求めてみよう。まずは簡単には以下のようになる。

$ dc -e '20 k' -e '1si 2 10 li * ^ 10 3 li * ^ / 1 - 100 * p'
2.40000000000000000000

$ dc -e '20 k' -e '2si 2 10 li * ^ 10 3 li * ^ / 1 - 100 * p'
4.85760000000000000000

$ dc -e '20 k' -e '3si 2 10 li * ^ 10 3 li * ^ / 1 - 100 * p'
7.37418240000000000000

$ dc -e '20 k' -e '4si 2 10 li * ^ 10 3 li * ^ / 1 - 100 * p'
9.95116277760000000000

$ dc -e '20 k' -e '5si 2 10 li * ^ 10 3 li * ^ / 1 - 100 * p'
12.58999068426240000000

$ dc -e '20 k' -e '6si 2 10 li * ^ 10 3 li * ^ / 1 - 100 * p'
15.29215046068469760000

$ dc -e '20 k' -e '7si 2 10 li * ^ 10 3 li * ^ / 1 - 100 * p'
18.05916207174113034200

$ dc -e '20 k' -e '8si 2 10 li * ^ 10 3 li * ^ / 1 - 100 * p' 
20.89258196146291747000

$ dc -e '20 k' -e '9si 2 10 li * ^ 10 3 li * ^ / 1 - 100 * p' 
23.79400392853802748900

$ dc -e '20 k' -e '10si 2 10 li * ^ 10 3 li * ^ / 1 - 100 * p'
26.76506002282294014900

この例のように、浮動少数点を使いたい場合 "dc -e '20 k'" とするとよい。'si' コマンドはスタックトップをレジスタ 'i' に保存する。'li' コマンドはレジスタ 'i' に保存された内容をスタックトップに詰む。

そして、上記の乖離の割合の様子を dc のスクリプトでスッキリと書いてみよう。以下のようになる。

$ dc -e '20k' -e '[q]sQ1si[2 10li*^10 3li*^/1-100*pcli1+d30<QsilFx]dsFx'

さすがにこのままだと判りづらいので、以下のように書き改めよう。

$ dc -e '20k' -e '
[q]sQ
1si
[
  2 10 li * ^ 10 3 li * ^ / 1 - 100 * p c
  li 1 + d 30<Q si
  lF x
]
d sF x'
2.40000000000000000000
4.85760000000000000000
7.37418240000000000000
9.95116277760000000000
12.58999068426240000000
15.29215046068469760000
18.05916207174113034200
20.89258196146291747000
23.79400392853802748900
26.76506002282294014900
29.80742146337069071300
32.92279957849158729000
36.11294676837538538500
39.37965749081639463400
42.72476927059598810500
46.15016373309029182000
49.65776766268445882400
53.24955408658888583500
56.92754338466701909500
60.69380442589902755400
64.55045573212060421500
68.49966666969149871600
72.54365866976409468500
76.68470647783843295800
80.92513943330655534900
85.26734277970591267700
89.71375900641885458100
94.26688922257290709100
98.92929456391465686200
103.70359763344860862600

'[q]sQ' はブラケットで囲まれたコマンド列(ここでは 'q' コマンドのみ)をレジスタ 'Q' に保存する。'si' はスタックトップをレジスタ 'i' に保存する。次のブラケットは、始めは先の例と同一だが、レジスタ 'i' に保存された内容をスタックトップに詰んで、'1' 加算及び複製し、それが 30 よりも大きかったら 'Q' レジスタに保存されたコマンドを呼び出している。つまり、終了である。さもなくば、加算された数値をレジスタ 'i' に保存して、レジスタ 'F' に保存されたコマンド列を実行する。それは、'd sF x' にて、複製され、レジスタ 'F' に保存され実行された、このコマンド列そのものである。

dc はスタックの3つ以上の回転が直接的にはできないので非常に不便である。やはり、現在はあまり使われないスクリプト言語と言える。

参考文献

  1. Wikipedia の bc の記述
  2. POSIX bc
  3. dc
Written by Taiji Yamada <taiji@aihara.co.jp>