dcを使ってみる。

個人的お気に入りツールの一つ。dc。
逆ポーランド記法(RPN)の計算機。
linuxなら大体さいしょっから入ってるツール。
パッケージで言うとbc*1
プログラミング序論の課題でRPNの話が出たので、ついでだし使い方でもまとめて見ようと思う。


とりあえずRPNがなんだ、ってのは適当にググってくださいな。
あくまでdcの使い方。

計算する

とりあえず、計算したいだけなら

$ dc
1	# 1をpushする
2	# 2をpushする
+	# 2,1をpopして、和をpushする
p	# topを出力する(スタックはいじらない。)
3
q	# 終了する(quit)
$

明示的に出力する様にしないと何も結果を返さないので注意。

負数の扱い

マイナス("-")は演算子として定義されているのでそのまま入力はできないらしい。

-1	# -と1として解釈される。のでスタックが空だとエラーになる
dc: stack empty
p	# スタックの先頭には1が入る
1
_1	# 負数を入力したい場合はアンダーバー("_")を使う
p	# スタックの先頭には-1がちゃんと入ってる
-1

と言う感じになります。

セパレータ

↑の例ではずっと一個入力する度に改行してますが、ホワイトスペースで区切る、もしくは単語境界がはっきりしていれば問題ないので

3 4*5+p	# (3 * 4 + 5)の計算結果を出力する
17

の様にもかけます。

スタックの内容の一覧

"p"だとスタックの先頭しか見られない*2わけですが、全部みたいと言う場合には"f"が使えます。

1 2 3	# 三数の入力
f	# スタックの内容を表示する(スタックなので当然逆順)
3
2
1

先頭要素の削除

"p"も"f"もスタック操作はない。先頭要素を削除したい場合には"n"で。

1 2 3	# 三数を入力
n	# 先頭から要素を消しつつ表示。改行されないケドそのまま次のコマンドを打てばいい
3f	# 3は出力。fが入力したコマンド。3が消えていることが分かる
2
1

スタックのクリア

スタックを全部消したい場合は"c"。

1 2 3	# 三数を入力
c	# スタックをクリアする
p	# 試しに表示してみる。
dc: stack empty

スタックの要素数

1 2 3	# 3個ぐらい数字を積む
zp	# スタックの要素数をスタックに積んで、表示してみる。
3
zp	# ちなみに要素数がスタックにつまれるので、実行する度値が増えるw
4
zzz	# こうすると
f	# 連番が作れるw
7
6
5
4
3
2
1

複製/交換

先頭要素の複製は"d"、先頭要素と次要素のスワップ(交換)は"r"でできます。

123	# 123をpush
f	# スタックの中身を表示。当然一つだけ。
123
d	# スタックの先頭を複製する
f	# スタックの中身が増えている。
123
123
456	# 456をpush
f	# スタックの中には数字が三つ
456
123
123
r 	# 先頭と次のを交換してみる
123
456
123

結構地味に使う機能ですね。
"d*"は複製して掛け算するので二乗の計算になります。

平方根

二乗は"d*"でできるとして、ではルートは?というと"v"というコマンドが用意されています。

169vp	# 169の平方根を出す
13
200vp	# 200の平方根。ディフォルトでは精度は整数までなので14マデ。
14
10k	# "k"は値をpopして、その値を計算精度として設定する。
200vp	# 改めて200の平方根。
14.1421356237
Kp	# ちなみに現在の計算精度は"K"でスタックにつまれる。
10

商/剰余

C言語ではお馴染み"%"も当然アリマス。

50 7%p	# 50を7で割った剰余(あまり)、すなわち1がつまれる。
1
c	# 一旦スタックをクリア
1001 7~	# "~"は割った商と剰余の両方を積みます。
f	# 1001 = 143 * 7 + 0 なので143と0が積まれます。
0
143
1k	# ちなみに計算精度が関わってくるので、精度を変えると
50 7%p	# 50 = 7.1 * 7 + 0.3 と計算されてしまい、0.3が積まれます。
.3
c	# クリア
10 3~f	# "~"も同様。10 = 3.3 * 3 + 0.1 なので3.3と0.1が積まれる。
.1
3.3

計算精度に注意!!

べき乗

べき乗もございます。"^"はXORじゃなくてべき乗です。

2 3^p	# 2の3乗
8
7 13^p	# 7の13乗
96889010407
197 13^	# こんなでかい数字も計算できます。
p	# とぅ!!!
159062675662500126674140597886681170875443042679204776389855428184687\
276405304062906792625669373837025051301593845716975064804018190726015\
1077160772568556085207855841
7 2 3|p	# "|"は「べき乗余」。ある数のべき乗を何かの数で割った余り、を求めます。
1
7	# これが法。つまり割る数
2	# これが指数。
3	# 底
|p	# べき乗余、つまり、2の3乗を7で割った剰余、2^3 = 8 = 1 * 7 + 1なので、1。
1

べき乗余って何に使うのさ?とか言われそうですが、暗号化とかの話でよく出てきます。
世に言う公開鍵だとか秘密鍵とかってヤツです。*3

基数の変更

dcは2進〜16進の入力と2進〜の出力をサポートしてるそうです。
果たして2,8,10,16以外使う機会があるのか...と言う話ですが...。

入力基数の変更

(n)はn進数の意。

2i	# 2進数で入力する("i"はpopして、入力基数に設定するコマンド
101p	# 101(2)を入力して、出力する。出力は10進数のまま。
5
3p	# 実は0,1以外の値も受け取れる(何故!?
3
42p	# 位取りは2進数で行われるので、4*2 + 2*1 = 10(10)が入る
10
10000i	# 10000(2)つまり、16進数に変える
10p	# 10(16)を入力して表示。
16
DEAD p	# 16進数の入力は大文字で行う。
57005
BEAF p	# お肉
48815
0.C	# 小数入力もどんとこい。レイテンシーは1.2
1.2
I	# ちなみに今の入力基数は"I"でスタックに積むことができる。
p	# 入力基数を出力する
16
出力基数の変更
2o	# 2進数で出力する。詳細はiと同じ。
100p	# 100(10)を2進数で表示する
1100100
16o	# 16進数で表示する
123p	# 123(10)を16進数で表示する
7B
O	# 現在出力基数をスタックに積む。
p	# 16を16進数で出すので"10"が表示されてしまう。
10
2o	# たとい2進数表示にしても
Op	# やっぱり"10"(実際には10(2)です。注意を。

入出力基数に別の数をぞれぞれ指定できますが、混乱の元なので、どちらかは10進数にしておくか、両方同じ基数にすることをおすすめします。


で、ここまでは、なんてことはないタダのRPN計算機。
dcのポイントは"文字列"と"レジスタ"とマグロ...もとい、"マクロ"。
なんで計算機なのに文字列があるのかとか、まぁ、色々あるでしょうが、便利です。

文字列

とりあえず文字列の例。

hoge	# 適当に文字を打つと当然コマンド解釈されエラーになる("o"は出力基数変換コマンドなのでエラーが変わる。
dc: 'h' (0150) unimplemented
dc: output base must be a number greater than 1
dc: 'g' (0147) unimplemented
dc: 'e' (0145) unimplemented
[hoge]	# かぎ括弧で括ってやると文字列となる
p	# スタックの先頭には文字列"hoge"がちゃんと入ってる
hoge
1+	# 当然、演算は定義されていない(文字列同士もダメ)
dc: non-numeric value


演算ができないなら何のためにあるのか、というと、一つはスクリプトにしたときの出力用。
そしてなによりマクロのため。ということで詳細はマクロの項で。

レジスタ

スタックだけでなくレジスタも持ってるdc。

1	# 1をpush
sa	# 1をpopして、レジスタaに保存(save)
p	# popしてしまったので当然スタックは空
dc: stack empty
la	# レジスタaの内容をpushする(load)
p	# スタックにはレジスタaの内容、1が入ってる
1
lalala	# 調子に乗って三回ぐらいloadしてみる
f	# スタックは1だらけ
1
1
1
1

s[レジスタ名]で保存、l[レジスタ名]で読み込みです。
manpageによると「少なくとも256個のメモリレジスタを持っています」だそうです。
個人的に非ASCIIなレジスタへのアクセス方法が気になるのですが...。


実はレジスタはそれ自信がスタックだったりもします。

1Sa	# S[レジスタ名]は、メインスタックをpopして、レジスタのスタックにpushします。
2Sa	# レジスタaに、2もpushしてみた。
lap	# レジスタスタックの先頭をロードするには先ほどの"l"を使います。
2
lap	# "l"はレジスタスタックを変更しないのでなんど使っても同じ値がでます。
2
3sa	# "s"はレジスタスタックの先頭の要素を書き換えてしまうので
lap	# 先頭要素は3になってしまう。
3
Lap	# "L"はpopする"l"。レジスタaからpopして、その値をメインスタックにpushします。
3
Lap	# "L"はレジスタスタックをpopするので使う度に別の値がでます。
1
La	# ついに空になってしまった。
dc: stack register 'a' (0141) is empty
f	# その代わりメインスタックはたっぷり。("l"の分だけ複製されている
1
3
3
2
2

マクロ

おまちかね。マクロ。
説明の前に具体例。
うちが作ったマクロなんでちと汚いかもしれんが勘弁です。

[+z1<a]saz1<a		# 和
zsa[+z1<b]sbz1<bla/	# 平均

ばりばり黒魔術ですが、使ってみましょう

1 2 3 4 5 6 7		# いくつかの値を入れる。
[+z1<a]saz1<a		# 和を求める
p			# 出力してみると確かに 1+2+3+4+5+6+7=28 で、あってる。
28
c			# くりあ。続いて平均。
1 2 3 4 5 6 7		# 同様にいくつかの値を入れて
zsa[+z1<b]sbz1<bla/	# 平均を求める。
p			# でました。
4			


和の方をターゲットにして、マクロの説明です。

[+z1<a]	# これは文字列、この文字列をマクロとして実行します。
sa	# この文字列をいったんレジスタaに保存します。
z 1	# 要素数と数字の1をスタックに積みます。
<a	# "<[レジスタ名]"は値を二つpopして、比較が成立したらレジスタをマクロとして実行します。

要約すれば、要素数が1より大きいときはレジスタaのマクロを実行する、です。
で、レジスタaの中身ですが

+	# とりあえず足す。(マクロを呼ぶ前に要素数チェックしたのはこのため。
z 1	# ↑でもやった要素数チェック用の数字。
<a	# 要素数が1より大きければレジスタaを実行(つまりこいつ自身。

世に言う再帰です。
dcには反復実行がないようなので再帰でがんばってミマシタ。
"s","l"を使ってるので元からあるレジスタaの値を殺してますが、まぁキニシナイ。


マクロの実行には、条件に一致したときだけ実行する"<","!<",">","!>","=","!="の6種類と、問答無用で実行する"x"があります。
"x"はスタックからpopして実行するので、"[10 20 + p]x"の様に直接実行できる一方、レジスタの内容を実行する時は"lax"などの様に"l"で読み込んでやる必要があります。
"!<", "!>", "!="はそれぞれ"<",">","="の否定です。"<="とかはないので注意。
あとスタックですので比較は

[[run	# 改行するための苦肉の策w
]n]sa	# 実行されたら"run"を画面に出力するマクロ
2 1 >a	# これはマクロが呼び出され「ない」!!
1 2 >a	# これはマクロが呼び出され「る」!!
run

と逆順になることに注意してください。


と、まぁ、多分こんな感じです。
下手にdcなんか作るよりrubyなりshell scriptでちゃちゃっと書いちゃった方が楽なんでしょうが、まぁ、興味あったらmanpageも覗いてみてください。
結構いろいろ発見ありますよ。
あと、ここで説明していないものがいくつかあるので*4そちらもman pageを。ということで。
ではでは、課題をやらないといけないものでд)ノシ

*1:bc : こちらは「普通の」中置記法の計算機

*2:スタックとしてはそれはそれでいいのだが...

*3:これを習った授業は、授業自体が暗号化されてました...д

*4:"a"とか"X","Z"など