calc and gforth

散歩に出ようとしたら、セリがあのスーパーで特売してるから買ってきてと、 女房から下命が下った。毎日、ipadでチェックをしてますからね。

で、スーパーへ行ってみると、黒山の人だかりには、なっていなかった。 ひとりのおばちゃんが、一生懸命にセリを吟味中。

既に籠には、10束ぐらいは入っているのに、更に追加の様子。そんなに買って どうされるんですか? って、いらぬ質問を思わずしちゃったぞ。

おばちゃんに言うには、セリの味噌漬を作るのよとの事。なかなか、手に入ら ないので、大量に作っておくらしい。

1週間程、つけておくと、食べ頃になり、数ヶ月は大丈夫らしい。そんなに食べ たら、塩分で体に悪いんじゃ?

サラダとかのトッピングにしたりして、少しづつ食べるらしい。

そこから、おばちゃんの弾丸トークが炸裂しましたよ。声をかけたのは、こち らなんで、途中で逃げだす訳にもいかず、じっと我慢の子を装っていましたよ。 おかげで、何が話されたかは、忘却の彼方。

家に帰って、セリの味噌漬って知ってるって女房に聞いたら、美味しいのよね とのたまう。未だ、女房は、ぼーと生きてるね。おばちゃんの知恵がつくのは、 何時になるのだろう?

calc

電磁気学を見ていたら、マクスウェルの電磁方程式が 出てきてて、

誘電率(e) 8.8540e-12
透磁率(u) 1.2566e-6
v = 1 / sqrt( e * u)

誘電率と透磁率から、電磁波の速度(v)が求まるそうな。電磁波も光も同じだから、 関係無いような物と結びついているんだな。emacsで確認してみる。

M-x calc

8.8540e-12 1.2566e-6 * Q  1 TAB / ===> 299800058.661 

数値を2個入れて(space)区切りでよい。* で、それの積をとり、Qで、sqrtし てあげる。次に1をいれてから、TABで、数値の順番を入れ替え、/ で、割り算。

8.8540e-12 1.2566e-6 * Q  &       ===> 299800058.661 

こちらは、逆数(1/x)を一発で求められる、invを使ってみた。 慣れると便利だな。

The GNU Emacs Calculator(ja) を見ながら、復習しておく。

stack

RET   TOPの複製        dup
DEL   TOPの破棄        drop
TAB   TOPと次の入れ替え swap
_     負数の入力
'     代数的入力        '3+4*2
$     TOPの値
U     最後の演算のundo
D     redo

data type

2:3            分数 2/3
'(1,2)         複素数
'1+2i          複素数
1@ 2' 3"       1時2分3秒 時間が整数として扱われるので、時間の加減が可能
'<1 2 2019>    2019年1月2日 日を基数として加減が可能

月日は数字で入力しても、米国風で表示されるんで焦らないで。

日付演算が手軽にできるって面白いな。実践投入の良い例がないかなと脳内バッ ファを検索。姪の所で去年の11月に男子が誕生した。この間、面会と言うか見 てきた。赤子と言うけど、正に赤い顔をしてたな。今日まで何日経過してるの かな? 今日の日付と誕生日を積んで、引き算。

--- Emacs Calculator Mode ---             |Emacs Calculator Trail
1:  68                                    |alg' <Sat Feb 2, 2019>
    .                                     |alg' <Mon Nov 26, 2018>
                                          |   ->68

抱かせてもらったけど、ずっしり重くて腕がしびれた。母はずっと抱いて世話 するんだから、知らず知らずのうちに逞しくなるのも頷けますよ。

ふと、自分の心臓が生れてこのかた、何回鼓動してきたか計算してみたりして。 驚愕な結果にびっくりしてます。2G回、その間に血液はどれぐらい輸送され ていたか? 一回のドッキンで、60ccらしいですよ。ドラム缶何本分?

行列

べクトルと行列もサポートしてる。 ベクトルを作るには、'[1 2 4 5] のようにするのが基本。いきなり、[ して から、数値を入力していき、最後に ] で、閉じてもよい。 行列も同様。

octaveで慣れ親しんだ行列作成方法も可能。'[1 2; 3 4] で、

1:  [ [ 1, 2 ]
      [ 3, 4 ] ]

が、作成される。

足し算や掛け算、逆行列は、それぞれ +, *, & で計算出来る。 行列式は、V D とか。

べクトルが出てきたら、統計関数も調べておく。 h s するとサマリーが出てくるので、そこで検索。 統計の基本用語meanあたりを手掛りに探すと、集落が見付かる。 たとえば、

   The ‘u S’ (‘calc-vector-sdev’) [‘vsdev’] command computes the
standard deviation of the data values.  If the values are error forms,
the errors are used as weights just as for ‘u M’.  This is the _sample_
standard deviation, whose value is the square root of the sum of the
squares of the differences between the values and the mean of the ‘N’
values, divided by ‘N-1’.
u #    count
u +    sum
u X    max
u N    min
u M    mean
H u M  median
u S    sdev

graph

calcは裏でgnuplotを動かして簡単にグラフを描く事ができる。

基本は、Xの値を格納したベクトルとYの値のベクトルをスタックに積んでから、 g f するだけ。g gとかすると、グリッドをon/offできる。

2:  [0 .. 360]
1:  sin(x)

こんな設定にすると、1周期分のサイン波が描かれる。荒い分解能と思ったら、g Nして、分解能を決定できる。たとえば、g N 360とすれば、1度毎に計算して くれる。

数学用なんで、折れ線グラフのみだ。(ひょっとして、棒グラフ等も可能かも 知れないけど、調べていない)

X値をベクトルじゃなくて、1とかにすると、それを初期値として自動で増加す るようになっている。

じゃ、既にあるデータでグラフを描くときは、どうする? 毎度おなじみの、 血圧データの入った、CSVファイルに登場願おう。

emacs current.csvしてCSVファイルを表示させる。次に取り込みたい範囲を指 定する。それから、C-x * r すると、データが取り込まれた状態でcalcが起動 してくる。

1:  [ [ 18010405, 138, 76, 55 ]
      [ 18010421, 123, 68, 64 ]
      [ 18010505, 136, 70, 55 ]
      [ 18010521, 114, 60, 60 ]
      [ 18010606, 131, 71, 55 ]
      [ 18010621, 140, 68, 61 ] ]

このままでは、グラフを書くのに適さないので、v t で、行列を転置する。 次にv u で行列をベクトルに分解する。

4:  [18010405, 18010421, 18010505, 18010521, 18010606, 18010621]
3:  [138, 123, 136, 114, 131, 140]
2:  [76, 68, 70, 60, 71, 68]
1:  [55, 64, 55, 60, 55, 61]

後は、自由にグラフが描けるぞ。 高、低血圧、脈数と3つ揃っていれば、3次元グラフを g F で描ける。マウス でグリグリ回転させて、3者間の相関を調べるにうってつけ。

余り大量にデータを取り込めないようなんで、 その積もりでね。(上記なら、70行ぐらいはOKだった。)

etc of calc

k p     prime?
k n     next prime
k f     factor
k g     gcd
k l     lcm
s n     store TOP   (n: 0,1,...)
r n     restore TOP (n: 0,1,...)
P       pi (3.14159265359)
1 E	e  (2.71828182846)

parts of calc

こんな多機能な電卓がどれぐらいの規模か調べてみた。

cent:calc$ wc *
    :
   3928   15983  141956 calc.el
   3667   13409  125732 calcalg2.el
   1929    7620   65583 calcalg3.el
   1672    6818   56758 calccomp.el
    301    1101    9351 calcsel2.el
  56561  216744 1933882 total

がんばってますなあ。そろそろ飽きてきたので、河岸をかえてみるか。

gforth

gforth

debianに入れようとしたら、インストール時にエラー。何でかなと、ログを眺 めていたら、m4が入っていなくて、失敗してた。今迄、数々のものをインストー ルしてるんで、環境は十分に整っていると思っていたんだけど、古典的なもの が抜けていたのね。

M-x run-forth で、走り出すのがちょっと蹟だな。単語を調べるには、M-TAB すればいいのね。

チュートリアルに面白い機能が紹介されていた。逆アセンブルできるとな。

sakae@usvr:~$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
see +
Code +

やってみた。そして見事にハングしたよ。で、一日寝て考えた。いくらなんで も、ガチで逆アセを実装してる事はあるまい。きっと誰かに委託してるぞ。 予想確認の為、ハング中に別端末からpsしてみた。

  2309 pts/2    S+     0:00 gforth
  2314 pts/2    S+     0:00 sh -c type mktemp >/dev/null && type gdb >/dev/null
  2318 pts/2    S+     0:00 gdb -nx -q -p 2309 -x /tmp/gforthdis.N5VvdlqjLK

ビンゴ! gdbさんに頼んでいるのね。じっとコマンド列を眺めていて、テンポ ラリーファイルに鍵がありそうと思った。

sakae@usvr:~$ cat /tmp/gforthdis.N5VvdlqjLK
set verbose off
set logging file /tmp/gforthdis.F7CsJmaKB4
set logging on
disas 0x558F5A6151A9,0x558F5A6151CF
set logging off
quit

やっぱりそうなのね。最近は生きづらい丗の中になったなあ。自分のプロセス にもアクセス出来ないのか。ならばsudo gforthではどうよ。

see +
Code +
   0x000055709f5f81a9 <gforth_engine+2873>:     mov    %r15,0x21b790(%rip)        # 0x55709f813940 <saved_ip>
   0x000055709f5f81b0 <gforth_engine+2880>:     lea    0x8(%r14),%rax
   0x000055709f5f81b4 <gforth_engine+2884>:     mov    (%r14),%rdx
   0x000055709f5f81b7 <gforth_engine+2887>:     add    $0x8,%r15
   0x000055709f5f81bb <gforth_engine+2891>:     add    %rdx,(%rax)
   0x000055709f5f81be <gforth_engine+2894>:     mov    %rax,%r14
   0x000055709f5f81c1 <gforth_engine+2897>:     mov    -0x8(%r15),%rcx
   0x000055709f5f81c5 <gforth_engine+2901>:     jmpq   *%rcx
   0x000055709f5f81c7 <gforth_engine+2903>:     mov    -0x8(%r15),%rcx
   0x000055709f5f81cb <gforth_engine+2907>:     jmpq   *%rcx
   0x000055709f5f81cd <gforth_engine+2909>:     jmpq   *%rcx
end-code

ああ、これウブ上での結果ね。すこし融通が効きそうなやつでやってみると、

FreeBSD ok

see +
Code +
   0x0000000000407363 <gforth_engine+10915>:    mov    %r14,0x21abd6(%rip)        # 0x621f40 <saved_ip>
   0x000000000040736a <gforth_engine+10922>:    lea    0x8(%r13),%rax
   0x000000000040736e <gforth_engine+10926>:    mov    0x0(%r13),%rdx
   0x0000000000407372 <gforth_engine+10930>:    add    $0x8,%r14
   0x0000000000407376 <gforth_engine+10934>:    add    %rdx,(%rax)
   0x0000000000407379 <gforth_engine+10937>:    mov    %rax,%r13
   0x000000000040737c <gforth_engine+10940>:    mov    -0x8(%r14),%rcx
   0x0000000000407380 <gforth_engine+10944>:    jmpq   *%rcx
end-code
 ok

あれれ、コードサイズが違うな。この差は、gcc vs. clang由来? gforth-fastてのが提供されてたので、そちらで試してみた。

see +
Code +
   0x0000565314e693d3 <gforth_engine+2403>:     add    $0x8,%rbp
   0x0000565314e693d7 <gforth_engine+2407>:     add    $0x8,%r15
   0x0000565314e693db <gforth_engine+2411>:     add    0x0(%rbp),%r14
   0x0000565314e693df <gforth_engine+2415>:     mov    -0x8(%r15),%rcx
   0x0000565314e693e3 <gforth_engine+2419>:     jmpq   *%rcx
   0x0000565314e693e5 <gforth_engine+2421>:     mov    -0x8(%r15),%rcx
   0x0000565314e693e9 <gforth_engine+2425>:     jmpq   *%rcx
   0x0000565314e693eb <gforth_engine+2427>:     jmpq   *%rcx
end-code

make bench

gforthのINSTALLを見ていたら、ベンチマークが紹介されてた。

sakae@usvr:~/src/gforth-0.7.3/build$ make bench
  :
time ./gforth-fast --die-on-signal -p ".:/usr/local/lib/gforth/site-forth:/usr/local/share/gforth/site-forth:/usr/local/lib/gforth/0.7.3:/usr/local/share/gforth/0.7.3:.." fib.fs -e "main bye"
0.18user 0.01system 0:00.20elapsed 99%CPU (0avgtext+0avgdata 2948maxresident)k
0inputs+0outputs (0major+264minor)pagefaults 0swaps

ちょいと醜いので整理して、色々なgforthで試してみる。

sakae@usvr:~/src/gforth-0.7.3/build$ time gforth fib.fs -e "main bye"

real    0m0.294s
user    0m0.293s
sys     0m0.000s
sakae@usvr:~/src/gforth-0.7.3/build$ time gforth-fast  fib.fs -e "main bye"

real    0m0.212s
user    0m0.196s
sys     0m0.017s
sakae@usvr:~/src/gforth-0.7.3/build$ time gforth-itc  fib.fs -e "main bye"

real    0m0.314s
user    0m0.295s
sys     0m0.014s

何かを犠牲にしたり、きっちりと保護したりしてるのだろう。それより、試供 品のfib.fsがどんな風になってるか興味有り。

sakae@usvr:~/src/gforth-0.7.3/build$ cat ../fib.fs
\ Note: This is incorrect ("n fib" produces the result for fib(n+1)),
\ but we do not change it to ensure that future timing results are
\ comparable to older timing results.

: fib ( n1 -- n2 )
    dup 2 < if
        drop 1
    else
        dup
        1- recurse
        swap 2 - recurse
        +
    then ;

これがソースだ。そして、それを逆アセしたもの。

see fib
: fib
  dup 2 <
  IF     drop 1
  ELSE   dup 1- fib swap 2 - fib +
  THEN ; ok

ちょいと変形させて、factにしてみる。

: fact ( n1 -- n2 )
    dup 2 < if
        drop 1
    else
        dup
        1- recurse
        *
    then ;

: main 5 fact . ;

定義中は、手続名を内部で使えないので、仮の名前recuseを使う訳ね。

1 1 = . -1  ok
1 2 = .
1 2 = . 0  ok
true .
true . -1  ok
false .
false . 0  ok

factなら、= を使え! trueが -1 で、falseが 0 なのか。

debug

gforth-itcを起動すると、debuggerを使えるようになる。factでやってみるか。

5 dbg fact
: fact
Scanning code...

Nesting debugger ready!
[ 1 ] 00005
7F3061E83318 559E43966498 dup            -> [ 2 ] 00005 00005
7F3061E83320 559E439661C0 2              -> [ 3 ] 00005 00005 00002
7F3061E83330 559E43966320 <              -> [ 2 ] 00005 00000
7F3061E83338 559E439660B8 IF             -> [ 1 ] 00005
7F3061E83370 559E43966498 dup            -> [ 2 ] 00005 00005
7F3061E83378 559E439661F8 1-             -> [ 2 ] 00005 00004
7F3061E83380 559E43966070 fact           -> [ 5 ] 00004 00003 00002 00001
7F3061E83390 559E43966218 *              -> [ 4 ] 00005 00004 00003 00002
7F3061E83398 559E43966088 THEN ;         ->  ok
.s <1> 120  ok

リターンキーを叩くだけじゃ、再帰の追跡はやってくれないのね。n コマンド で追うといいのかな。

Floating point stack

浮動小数点もサポートしてる。スタックに積むには、e形式にする。整数用と スタックが別になってる。

1.23e0 4.5678e0 f.s <2> 1.230000000000E0 4.567800000000E0  ok
f* f. 5.618394  ok

浮動小数点スタックの操作は、接頭語としてfを前置することになってる。

1234.5678 .s <2> 12345678 0  ok
1234.5678e0 8.7654321e3 f.s <2> 1.234567800000E3 8.765432100000E3  ok
f. 8765.4321  ok

間違って、e表記を忘れると、数値はデータスタックの方に入ってしまう。デー タ自身も小数点が省かれているし、更に余計な0も積まれている。

一方、e形式の数値は、正規化されて格納される。データを取り出すと、世 間一般並の形で表示される。

疑問が一つある。なぜe表記の数値のみを浮動小数点スタックに入れる設計に したのだろう? 数値のパースの段階で、小数点が付いていたら、それ用のス タックに入れるって事が十分に出来たはず。 うえの例のような間違いも無くなると思うんだけど。深遠な理由が思いつかな いぞ。

互換性の問題かとも思ったけど、オブジェクトオリエンテッドをサポートした 時点で、完全に破綻してるからなあ。

だれか納得のいく説明を求む。