HP 35s でプログラミング
仕事柄、ちょっとした計算や基数変換をする場面が多く、その度に Windows の電卓や、シェルから expr や、ちょっとしたループを伴う計算には awk を使っていたが、起動のオーバーヘッドもあり不便を感じていた。たまたま寄ったオフィス用品店で目に留まった関数電卓を見て、基数変換くらいは出来ても良さそうだよな、と思い幾つかの製品を手に取りキー配列を見てみると、Base やら A, B, C, D, E, F やらのキーが印刷されているものを発見。CASIO の FX-115ES PLUS という電卓である。18 USD 程だった。
基数変換もばっちりでキーが多い割には直観的に使え、機能的にはこれで十分で満足していたのだが、止せばいいのに関数電卓のことを色々調べるうちにプログラム出来る関数電卓、それも HP の製品が欲しくなってしまった。ギーク友達が逆ポーランド記法の電卓の話をアツく語ってくれていたのを思い出した。たしか HP のものだったはず。というわけで、現在手に入る HP 製の中では手ごろな価格の 35s を購入。日本だと1万円弱ほどらしいがアメリカでは 50 USD 程で手に入った。
1年以上 Lisp/Scheme を使っていなかったが逆ポーランド記法には抵抗感は無いし、キー配列が良く考えられているのか操作もすぐ慣れた。で、目的のプログラミングである。
とりあえず 1 ? 10 の合計を計算するプログラムを作ってみた。
A001 : LBL A A002 : 0 A003 : STO S A004 : 10 A005 : STO A A006 : x=0? A007 : GTO A017 A008 : RCL S A009 : RCL A A010 : + A011 : STO S A012 : RCL A A013 : 1 A014 : - A015 : STO A A016 : GTO A006 A017 : VIEW S A018 : RTN
変数 A がカウンタ、S が合計値である。HP の電卓は4つのスタックを備えており、数字を押すとそれが PUSH される。
( スタックの説明は電卓喫茶http://calculator-cafe.com/readings/4LEVEL_RPN/4LEVEL_RPN.html の記事が詳しい)
STO はスタックの先頭にある値を変数に代入する命令で、RCL は変数に格納されている値を取り出しスタックに PUSH する命令である。8 行目から10行目は、変数 S と A の値をスタックに PUSH し、+ 命令でスタックの先頭から二つの値を POP して足してスタックの先頭に PUSH する、ということが行われている( プログラムでも逆ポーランド記法 )。
その値を 11 行目で S に代入することで、
S = S + A
が計算出来たことになる。
で、このプログラムを最適化してみる。まず、ループ制御用の DSE 命令を使ってみる。( DSE は decrement; skip if less than or equal to の略。インクリメント命令として ISG があるが、こちらは increment; skip if greater than の略。最初は DEC とか INC にすれば良いのになぁ、と思っていたが、マニュアルが無くともキートップの印刷で境界が分かるので、良く考えられたものである )
B001 : LBL B B002 : 10.000 B003 : STO A B004 : 0 B005 : STO S B006 : RCL A B007 : INTG B008 : + B009 : STO S B010 : DSE A B011 : GTO B006 B012 : VIEW S B013 : RTN
DSE, ISG 命令はちょっと癖があり、ループ制御用の特殊なフォーマットの値を適当な変数に代入して使う。2, 3行目の 10.000, STO A がそれで、小数点を境に整数部の 10 が初期値、少数部の 000 が最終値である( 初期値は最大7桁、最終値は 3桁固定という制約がある。尚、最終値は少数ではなく、整数として扱われる)。
A だけを見ると値が少数なので 7 行目の INTG で整数に切り詰めているが、最終値が 0 であればこのステップは不要である。最終値が 1 とか、ISG 命令で最終値が 10 とかだと必要。
実はこのプログラムもまだ無駄が多く、A や S の STO はスタックに値が残っている限り不要である。この処理を除いたのがこれ:
C001 : LBL C C002 : 10.000 C003 : STO A C004 : 0 C005 : RCL A C006 : INTG C007 : + C008 : DSE A C009 : GTO C005 C010 : RTN
こちらでは変数 S はもはや使わず、スタックに格納された合計値を利用している。
さて、ループ制御用の命令に DSE を使ったのは、0 との比較のほうが単純で早そうだから、というのと、ISG では最大で 1000カウンタまでのループしか出来ない、二つの理由による。
試しに以下の二つの実行時間を比較してみたが、DSE が19 秒だったのに対して ISG は21秒だったので、DSE のほうが若干効率は良さそうだ。35s では任意の数字同士の比較命令も用意されているが、MIPS の命令セットの例を出すまでもなく、0 の扱いは効率良く行えるようアーキテクチャが設計されているのだろう。
( D002 を 257.001 にしても、実行時間の増加が認められた )
DSE 版 D001 : LBL D D002 : 256.000 D003 : STO A D004 : 0 D005 : 1 D006 : + D007 : DSE A D008 : GTO D005 D009 : RTN ISG 版 E001 : LBL E E002 : 000.255 E003 : STO A E004 : 0 E005 : 1 E006 : + E007 : ISG A E008 : GTO E005 E009 : RTN
という感じで、アーキテクチャに思いを巡らせながら人力で最適化するといった、裸のコンピュータに直に触るような行為は、フリーで手に入る科学計算ライブラリによって高度な数式が一瞬にして解けてしまう便利な世の中になってしまった昨今、崇高か貴重か、というとそうでは無くて、まぁ完全に遊び、おもちゃ、縛りですよね。そもそも前述の CASIO のように構文解析出来る電卓がある以上、逆ポーランド記法自体が誰得かというとコンピュータ得なので。
とはいえ、HP の電卓にはファンが多い、というのも頷ける、魅力を持ったガジェットであるのも事実。しばらく遊んでみようと思う。
( 自分の研究で出てくる数式を解かせてみたい気もするが、256回のループに19秒かかるようだとちょっと厳しいかな、、、 )