dc(desk calculator)は、任意精度の演算をサポートする
クロスプラットフォームな逆ポーランド記法の計算機ユーティリティです。
ベル研究所のロバート・モリスによって開発され、
UNIXユーティリティの中でも非常に古い歴史を持ちます。
C言語の発明以前から存在し、その簡潔な構文が特徴です。伝統的に、中間記法計算機ユーティリティであるbcは、dcをバックエンドプロセスとして利用していました。
歴史
dcは現存するUnix言語の中で最も古いものです。
ベル研究所に
PDP-11が導入された際、アセンブラよりも先に、
B言語で記述されたdcが最初に動作した言語となりました。
ケン・トンプソンはdcがこのマシンで書かれた最初のプログラムだと述べています。
基本的な演算
dcで4×5を計算するには、以下のように記述します。
4 5
この操作は、「4と5を
スタックにプッシュし、乗算演算子()が
スタックから2つの要素をポップし、それらを乗算した結果を
スタックにプッシュする」という流れになります。
`p`コマンドは
スタックの先頭要素を表示し、`q`コマンドはdcプログラムを終了します。演算子と数値の間の
スペースは省略可能ですが、数値同士の
スペースは省略できません。数値は、3.14や.318のような小数も使用できますが、指数表記(1.23e4など)には対応していません。
演算の精度(小数点以下の桁数)を変更するには`k`コマンドを使用します。デフォルト値は0なので、`2 3 / p`の結果は0となります。`5 k`のように精度を設定することで、任意の小数点以下の桁数で演算が可能です。
5 k
2 3 / p
上記のコマンドの結果は、.66666となります。
`v`コマンドは
スタックの先頭要素の平方根を演算し、`_`は負の値を表します。例として、以下の計算を行うコマンドを示します。
12 _3 4 ^ + 11 / v 22 - p
これは、√((12+(-3)⁴)/11)-22 を計算します。
他にも、
スタックの先頭2つの要素の順序を入れ替える`r`コマンドや、
スタックの先頭要素を複製する`d`コマンドがあります。
入出力
`?`コマンドを利用すると、標準入力を1行読み込み、dcコマンドとして評価します。入力はdcコマンドとして正しい構文である必要があります。また、`!`コマンドを使用すると任意のユーザーコマンドを実行できるため、セキュリティ上の問題が発生する可能性があります。
`p`コマンドは
スタックの先頭要素を表示して改行します。`n`コマンドは
スタックからポップした値を改行なしで表示します。`f`コマンドは
スタックの内容を先頭から順にすべて表示します。
dcでは、入力と出力の基数を任意に設定できます。`i`コマンドは入力時の基数を設定し、`o`コマンドは出力時の基数を設定します。利用できる基数は2から16までで、10以上の場合はAからFの大文字を使用します。現在の精度・入力基数・出力基数を得るには、`K`、`I`、`O`コマンドを使用します。これらのコマンドは現在の値を
スタックにプッシュします。
以下は16進数を2進数に変換する例です。
言語の特徴的な機能
dcは、基本的な演算や
スタック操作に加えて、マクロ、条件分岐、演算結果の一時保存などの機能を備えています。
レジスタ
dcのレジスタは、マクロや条件式の基本となる仕組みです。各レジスタは1文字の名前を持ちます。`sc`コマンドは
スタックからポップした値をレジスタ`c`に保存し、`lc`コマンドはレジスタ`c`の値を
スタックにプッシュします。
3 sc 4 lc p
上記の例では、レジスタを利用して3と4を掛けた結果を表示します。レジスタを
スタックとして利用することもでき、`S`と`L`コマンドでプッシュとポップが可能です。
文字列
dcでは、`[`と`]`で囲まれたものを文字列として扱います。文字列は数値と同様に
スタックにプッシュしたり、レジスタに格納したりできます。`a`コマンドは
スタックから要素をポップし、数値の場合は
ASCII文字に変換してプッシュし、文字列の場合は最初の1文字をプッシュします。`x`コマンドでマクロとして実行したり、`P`コマンドで表示したりできますが、文字列を構築したり操作する方法はありません。
また、`#`から行末までをコメントとして扱うことができます。
マクロ
dcでは、レジスタや
スタックに数値を格納するだけでなく、文字列を格納することでマクロ機能を実現しています。文字列は表示するだけでなく、dcコマンドとして実行できます。
[1 + 2 ] sm
上記の例では、1を加算して2を掛けるマクロをレジスタ`m`に格納しています。`x`コマンドでマクロを実行します。
3 lm x p
条件分岐
マクロの仕組みを利用して条件分岐を行うことができます。`=r`コマンドは
スタックから要素を2つポップし、それらが等しい場合にレジスタ`r`に格納されたマクロを実行します。
[[Equal]p]sm 5 =m
上記の例では、
スタックの先頭が5の場合に「Equal」と表示します。他にも、`>`、`!>`、`<`、`!<`、`!=`などの比較演算子があります。
ループ
dcで直接ループを記述することはできませんが、マクロを
再帰的に実行することで実現できます。
[d1-d1
上記の例では、
スタックの先頭の要素の階乗を計算します。
`1Q`コマンドはマクロから脱出し、`q`コマンドはマクロとその呼び出し元から脱出します。`z`コマンドは
スタックの深さをプッシュします。
例
レジスタ`a`に格納されたマクロを
再帰的に呼び出して、
スタック全体の合計を計算する例です。
1 2 4 8 16 100 # 加算したい数を
スタックに積む
0d[+z1
結果は131となります。
ファイル中のdcコマンドの合計
ファイルの各行に記述されたdcコマンドを実行し、その合計値を計算する例です。各行に数値を記述することで、数値の合計を計算できます。
?d[+z1
dcの演算は無限精度であるため、オーバーフローや精度落ちを気にする必要がありません。ただし、空行に遭遇するとループが停止する点や、負符号を`_`に置換する必要がある点に注意が必要です。
単位変換
メートルで表された距離をフィートとインチに変換する例です。
最大公約数
ユークリッドの互除法を利用して最大公約数を求める例です。
階乗
入力された値nの階乗n!を計算する例です。
Quine
dcにもquineが存在します。
すべての素数を求める
すべての素数を表示する例です。
素因数分解
与えられた数の素因数分解を行う例です。
Diffie–Hellman鍵交換
Perlスクリプトに組み込まれた
Diffie-Hellman鍵交換の例です。これは、ITAR議論の際に
サイファーパンクたちの間で人気がありました。
dcは、その簡潔な構文と多様な機能により、Unix環境で長きにわたり利用されてきた計算機ユーティリティです。