HOME > 電算 > dc メモ
dc メモ
最終更新:2006/01/05(木)
dc はいわゆる逆ポーランド式の計算機である。スタックを明示的に扱えるので、愉快至極なものなり。
ちょっと注意
精度は k コマンドを用いて自分で設定する。デフォルトでは小数点以下は無視。
負の数は、たとえば -1 ではなく、_1 と書く。
二種類のスタック
dc は二種類のスタックを持つ。メインスタックとレジスタのスタックである。ともに、数字や文字列を保存することができる(dc のデータ型には、数字と文字列がある)。
メインスタックは一つしかない。演算子はこのメインスタックに対してしか使えない。
レジスタは少なくとも 256 個あり、それぞれ一文字の名前を持っている。256 個あるというところからして、どんな名前を持っているか見当がつく(試してみると、^D なんてものすごい名前のレジスタもあった)。レジスタのスタックのほうは、数字や文字列を保存するだけで、これに対して演算子を使うことはできない。
スタック内容の表示
f コマンドはスタック(以下たんにスタックといえばメインスタックのことなり)の内容をすべて表示する。
メインスタック先頭要素の表示には、スタック先頭の値を pop して(取り出して)表示するコマンドと、たんに表示するだけでスタックの内容に影響を与えないコマンドがある。
取り出し&表示 | 表示のみ | |
---|---|---|
改行なし | P(大文字), n | |
改行あり | p(小文字) |
f はスタックを変更せずに、その内容をすべて表示する
P(大文字)コマンドで数値を表示させると、たぶん予想と異る結果になるだろう。
111 108 108 101 72 PPPPP↵ Hello
これは、ASCII コードを 10 進数であらわしたときに、H が 111、e が 108 となっているからである。
P は、コンパイルされたときの UCHAR_MAX の定義によって出力が異る。私の環境では、limits.h に
# define UCHAR_MAX 255
とある。P コマンドは、UCHAR_MAX (unsigned char の最大値)+ 1 を基数としたバイトストリームを出力し、これが私のターミナルをして Hello を表示せしめたのである。
入力基数と出力基数の設定
さて、端末によっては日本語も表示されるので、日本語の出力に挑戦してみることにしよう。(私のところのロケールは EUC で、ターミナルは日本語に対応している。)
出力させたい文字列が「うんこ」であったとする。まず、EUC で 「う」は 16 進の A4 A6、「ん」は A4 F3、「こ」は A4 B3 である(もしロケールが EUC でターミナルが日本語に対応しているなら、シェルから、echo -n 'うんこ' | hexdump -C などとやってみるとわかる)。入力された数値を 16 進であると解釈させることができれば簡単そうだ。
まず、現在 dc が入力された数値を何進数と解釈しているのかをあらためて調べてみる。I コマンドは、現在の入力基数をスタックの一番上に積むから、これを n コマンドで取り出して表示してみる。
In↵ 10
現在の入力基数が 10 である(10 進である)ということがわかった。これを 16 進にするためには、まずスタックに 16 を積んでおいてから、i コマンドを使う。i コマンドは、スタック先頭を取り出して、入力基数をセットする。
16i↵
これで、これ以後入力される数値は 16 進と解釈されるようになった。ちなみに、ここでもう一度 Ip と打ってみると、10 (16 進での 16)とは出ないで、16 と出力される。出力のための基数は、別途設定できるので、出力のほうはまだ 10 進のままなのだ。
ここで、「うんこ」に対応する 16 進数を後ろから入力し、P コマンドを使ってみる。うしろから入力するのは、スタックが最後に入れたものを最初に出すからにほかならない。
B3 A4 F3 A4 A6 A4PPPPPP↵ うんこ
dc で「うんこ」の出力に成功である。ちなみに、16 進の A-F は大文字を使うのでコマンドと区別できる。次に、10 進で入力を試みる。(16i10o で、16 進入力 10 進出力にセットしておいてから、「うんこ」の 16 進表現を 1 バイトずつ入力し、n コマンドか何かで出力してやれば、「うんこ」の 10 進表現が得られる。)
10i↵ 179 164 243 164 166 164PPPPPP↵ うんこ
10 進入力も成功。ここで、少しいたずらをしてみる。さきほどの 16 進うんこを正順にスペースを入れないで入力し、一回の P コマンドで表示させてみる。
A4A6A4F3A4B3P↵ うんこ
やはり「うんこ」が出力される。ところが、同じことを 10 進うんこでやってみると、支離滅裂なものが出力される。考えてみれば、なんということはない。16 進を 256 進に変換するときは、下の桁からちょうど 2 文字ぶんずつを 1 文字に変換していくので、うまくいったのである。dc とは直接関係のない話であった。
文字列
ところで、スタックの先頭が文字列であった場合 P コマンドは、何を出力するであろうか。
文字列の入力には、[ ] を用いる。
[Hello]P↵ Hello
そのまんまである。[うんこ] も同じようにうまく表示できた。dc は文字列か数値かを「知って」いるので、同じコマンドがそれぞれに応じて異なる動きをすることがあるのである。
p (小文字)コマンドも、n コマンドも、スタック先頭要素が文字列であれば、これを表示する。
レジスタの使用
s コマンドも S コマンドもメインスタック先頭値を取り出しレジスタに値を積む。s を使うと、それまでレジストリにあったスタックの内容が破壊されるが、S を使うと、レジスタスタックの従前の内容は破壊されずに下のほうに押しやられる。
l(エル)コマンドも L コマンドも、レジスタスタックの先頭の値をメインスタクに積む。l コマンドを使うと、レジスタスタックの内容に変更がなく、L コマンドを使うと、レジスタスタックの先頭要素が削除される。l はレジスタスタックの値をコピーするのに比べて、L はポップさせるわけである。
マクロ
マクロの正体は、たんなる文字列である。マクロに名称をつけることはできない。文字列としてメインスタックやレジスタスタックに保存して、必要に応じてコマンド(の集合)としてこれを実行するのみである。
[unko]p は、unko という文字列をメインスタック先頭に積み、次にこれを表示する。しかし、何度も unko を出力したい場合、これをマクロにすることができる。
[[unko]n]↵ dx↵ unkodx↵ unkodx↵ unkodx↵
x コマンドは、マクロとして文字列を実行する。d コマンドは、スタック先頭の内容を複製して、同じものをもう一つ積むものである。これをここで使っているのは、x コマンドがマクロ文字列を実行するときに、これを「取り出し」てしまうからである。
マクロは、レジスタに保存しておくと使いやすい。以下はそれぞれの品物ごとに消費税をかけた値段を調べる例。s コマンドでレジスタ a に 「1.05 * p」を保存し、税抜き価格をメインスタックに積んだ後、l(エル)コマンドでレジスタ a の内容をコピーしてメインスタックに積み、これを x で「取り出し&実行」している。
[1.05*p]sa↵ 100 lax↵ 105.00 300 lax↵ 315.00 ......
この程度のことは、bc を使うよりも楽だと思うが、いかがなものだろう。