dcコマンドで遊んでみた

dcコマンドってのがあります。lsを打ち間違えてやってしまうslコマンド的な・・・ってわけではなくて、れっきとした便利ツール。 逆ポーランド記法の計算機で、精度設定できたりマクロ使えたりします。

と言うわけでこのマクロとやらを試してみた。 ほぼお遊びなので、あんまり参考にはならないと思います。

とりあえず実行

$ dc

とすればインタプリタが起動します。インタプリタってもプロンプトとかなんにも表示されないけれど。 終了する時はqとタイプすればおっけーです。

簡単な計算をするときなんかは

$ dc -e '1 2 +p'

とかやって実行も可能。1+2の結果を表示します。

他にも、

$ dc filename

ってやってファイルの中身を実行するとかも出来ます。

$ dc file1 file2

みたいにすれば複数のファイルを実行する事も可能。先頭から順番に実行されるようです。

基本的な計算

とりあえず適当に数字を入力してスタックに積みます。

1
2

こんなん。改行区切りでもいいし、スペース区切りとかでも良い。

んで、スタックの中身を足したり引いたり。

+

例えばこれは足し算。四則演算の記号は**+-*/%%^**みたいなよくあるやつです。

計算した結果は

p

とすれば表示されます。

スタックの中身を全部みたいときは

f

で表示。上の方がスタックの先頭側です。

レジスタを使う

本題のマクロに入る前にレジスタについて。vimのレジスタとかなり似てる、かも。

sa

みたいにすると、スタックの先頭をaレジスタに移動しします。 レジスタの名前は文字とか数字とか記号とか諸々らしい。sbとかscとかやって使います。

aレジスタの中身は

la

とすれば取り出せます。同様にlbとかlcとかやれば他のレジスタからも出せる。 何度取り出しても中身は消えない。

マクロを使ってみる

んで本題、マクロ。

[1+]

みたいな感じでマクロを作れます。作ったマクロはスタックの先頭に入ります。

マクロはxコマンドで実行できます。

流れとしてはこんな感じ

2  # スタックに2を積む。
[1+]  # 1を足す動作をマクロとしてスタックに積む。
x  # 実行する。
p  # 結果を確認する。
3  # 計算結果。

スタックに2を入れて、そんでもって先頭を1増やすマクロを作って、実行する。 まとめて書くと2[1+]xpって感じ。2 1+pで済むのに、長い。

xで実行するとスタックから消えてしまうので、実際はレジスタに入れて実行することになります。

[1+]sa  # aレジスタに1増やすマクロを入れる。
2  # 2をスタックに積む。
la  # aレジスタからマクロを取り出して、
x  # 実行する。
lax  # もっかい実行してみる。
p  # 結果を確認してみる。
4  # これは計算結果。

こんな感じで。 レジスタに入れれば何度でも実行できる。

合計値を計算してみる

ちょっと高度な事をしてみよう。と言うわけで、スタックに積んだ値の合計を計算してみます。

とりあえず結論から。

[+z1<a]sa  # マクロ作る
1 2 3 4 5  # 数字積む
lax  # 実行
p
15  # 結果

こんな感じ。ややっこい。

マクロを実行すると、とりあえず+でスタックの先頭二つを取り出して足します。

んでもってスタックの長さをzでスタックに積んで、その後に1を積む。

<で比較をして、スタックの先頭の方が小さければaレジスタの内容を実行。

みたいな流れで計算します。大分ややっこいね。

ポイントは比較してマクロを実行できる<でしょうか。 他にも!<とか>とか!>とか=とか!=とか。めっちゃあるので調べてください。 なんというか、アセンブリ言語のjgとかjlとかににている、かも?

マクロの内容を擬似コードっぽく表すと

function a(){
    push(pop() + pop());
    if(length() &gt; 1){
        a();
    }
}
push(1);
push(2);
push(3);
push(4);
push(5);
a();
print(pop());

的な感じ? 何だこの難解言語。

階乗を計算してみる

せっかくなので階乗も書いてみよう、と言うわけで。

[d1-d2<a*]sa
5
lax
p

こんなん。何かもうbrainfu*kみたいになってる。

今度のマクロは実行されたらdを使ってスタックの先頭をコピーします。

コピーした値を1-でデクリメントします。

dでもっかい値をコピーして、2<で比較。2以上なら再帰的に自分を呼び出す。

最後にレジスタの先頭二つを*でかけて終了。

dでスタックの先頭をコピーできるの、結構便利。なのかも。

これも擬似コードで表すと、

function a(){
    copy();
    push(pop() - 1);
    copy();
    if(pop() &gt; 2){
        a();
    }
    push(pop() * pop());
}
push(5);
print(pop());

大体こんなん。 分かり安くなった気がしない…。

スタックの中身はこんな感じで推移する、はず。

5 la x
5 [d 1 - d 2 <a *] x
5 d 1 - d 2 <a *
5 5 1 - d 2 <a *
5 4 d 2 <a *
5 4 4 2 <a *
5 4 d 1 - d 2 <a * *
5 4 4 1 - d 2 <a * *
5 4 3 d 2 <a * *
5 4 3 3 2 <a * *
5 4 3 d 1 - d 2 <a * * *
5 4 3 3 1 - d 2 <a * * *
5 4 3 2 d 2 <a * * *
5 4 3 2 2 2 <a * * *
5 4 3 2 d 1 - d 2 <a * * * *
5 4 3 2 2 1 - d 2 <a * * * *
5 4 3 2 d 2 <a * * * *
5 4 3 2 2 2 <a * * * *
5 4 3 2 * * * *
5 4 6 * * *
5 24 * *
120 *

うーん…。長い。 最後に*が残ってしまうのだけれど、これなんやろね。

平均

さっき作った合計値を利用して

[+z1<s]ss
[zsx lsx lx /]sa
1 2 3 4 5
lax
p

みたいな感じで平均を計算できる。 小数点以下の桁数は2kとか3kとかやって設定できます。

最大/最小

最大値は

[sx]sc  # スタックの値をレジスタに移動するマクロ
[n]sd  # スタックの頭を消すマクロ
[dlx<c dlx>d z1<m]sm
0sx
1 3 2
lmx
lxp

こんなもんか? 異様に面倒くさい感じになってしまった。

最小は最大値のコードを

[dlx>c dlx<d z1<m]sm

こんな感じにすればおっけー。

0sx

の部分を修正しないと多分ダメなので注意。


まあそんなわけで。 面白いけれど、少しでも複雑な計算となると実用的じゃない。面倒くさい。 awkのがいいかなぁ…。