Go to the first, previous, next, last section, table of contents.


プログラミング実習

Calc はその全てが拡張性の高い Emacs Lisp で書かれています。 Lisp を知っていれば、Calc をプログラムして好きな事をさせる事ができます。 また書替え規則も強力なプログラミングシステムとして機能します。 しかし Lisp や書替え規則をマスターするにはそれなりの時間がかかります。 一方やりたい事と言えば新しい関数を定義したり、 あるコマンドを数回繰返したり、というのが大抵です。 Calc はこのような事を簡単に行う機能を備えています。

(注: ユーザー定義キーに関するプログラミングコマンド群は、 Lucid Emacs 19 にはまだサポートされていません。)

ユーザー関数定義

プログラミングのある非常に限られた形態は、 ユーザー関数の定義です。 Calc の Z F コマンドは、 任意の式に対して対応する関数名とキーシーケンスを定義することができます。 プログラミングコマンド群は shift-Z プリフィックスで始まり、 それらによって定義されたユーザーコマンド群は z プリフィックスで始まります。

1:  1 + x + x^2 / 2 + x^3 / 6         1:  1 + x + x^2 / 2 + x^3 / 6
    .                                     .

    ' 1 + x + x^2/2! + x^3/3! RET         Z F e myexp RET RET RET y

この多項式は `exp(x)' の Taylor 級数近似です。 Z F コマンドは沢山の質問をします。 上の答は 新関数のキー・シーケンスは z e であり、 同等の M-x コマンドが calc-myexp であり、 新関数の代数式は同じく myexp であり、 デフォルトの引数リスト `(x)' を許容し、 最後に y は「非定数の引数の場合は記号のまま残しますか?」 という質問に答えています。

1:  1.3495     2:  1.3495     3:  1.3495
    .          1:  1.34986    2:  1.34986
                   .          1:  myexp(a + 1)
                                  .

    .3 z e         .3 E           ' a+1 RET z e

0.3 を引数として新 exp 近似を呼び、 本当の exp 関数と比較します。 そして望みどおり、数値でない引数に z e を作用させると、 myexp 関数呼出しが記号表記の形式で残ることがわかります。 先の最後の質問にもし n と答えていたら、 `myexp(a + 1)' は評価されて 定義式中の `x'`a + 1' を代入したものになります。

(*) 練習問題 1. "正弦積分" 関数 Si(x) は、 `sin(t)/t't = 0 から x (ラジアン)までの積分として定義されます。 (この積分には基本関数による解がないのでこんな関数が考案されました。 もし Calc の a i コマンドにこの積分をやらせたら、 長考の後に give up するでしょう。) 数値積分コマンドは使えますが、 それは代数表記では `ninteg(f(t), t, 0, x)' のように書かれます (ここで `f(t)' は非積分関数です)。 z s コマンドと Si 関数を定義してインプリメントしなさい。 デフォルトの引数リストを若干変更する必要があるでしょう。 テストとして、 `Si(1)' は 0.946083 を返すはずです。 (ヒント: 精度をあらかじめ 6桁くらいに下げておけば、 ninteg はかなり速く走ります。) プログラミング 練習問題 1 解答「正弦積分関数」 参照 . (*)

キーボードマクロ

Emacs で一番簡単な現実の「プログラミング」のやり方は、 キーボードマクロ(keyboard macro) を定義することです。 キーボードマクロとは Emacs が一連のキーストロークを記録して 必要に応じてプレイバックするようにしたものです。 例えば、あなたが H a S x RET をよくタイプすることが判ったら、 キーボードマクロをプログラムしてそれを自動化したいと思うでしょう。

1:  y = sqrt(x)          1:  x = y^2
    .                        .

    ' y=sqrt(x) RET       C-x ( H a S x RET C-x )

1:  y = cos(x)           1:  x = s1 arccos(y) + 2 pi n1
    .                        .

    ' y=cos(x) RET           X

C-x ( をタイプすると、Emacs は記録を開始します。 しかも、打ったキーストロークは実行されるので、 ある手順をいったん Emacs に示してまさに「トレーニング」しているようなものです。 X を打つと、同じキーストロークが再実行されます。

Z K を打つと、マクロに名前を付けることができます。

1:  .              1:  y = x^4         1:  x = s2 sqrt(s1 sqrt(y))
                       .                   .

  Z K x RET            ' y=x^4 RET         z x

shift-Z を使ってコマンドを定義し、 小文字の z でそれを呼出すことに注意してください。

キーボードマクロは他のマクロを呼出すことができます。

1:  abs(x)        1:  x = s1 y                1:  2 / x    1:  x = 2 / y
    .                 .                           .            .

 ' abs(x) RET   C-x ( ' y RET a = z x C-x )    ' 2/x RET       X

(*) 練習問題 2. スタックの他の内容に影響を与えずに、 第3レベルだけ正負反転するキーボードマクロを定義しなさい。 プログラミング 練習問題 2 解答「スタック第3項の正負反転」 参照 . (*)

(*) 練習問題 3. 以下の機能を持つ キーボードマクロを定義しなさい。

  1. sin(x) / x を計算する。 ただし x はスタック先頭にあるものとする。
  2. b を底とする対数を計算する。 ちょうど B キーと同じで、引数はそれと逆の順に取る。
  3. 1からスタック先頭の整数までの整数を並べたベクトルを生成する。

プログラミング 練習問題 3 解答「sin(x) / x の計算, 他」 参照 . (*)

(*) 練習問題 4. 数値リスト(並び)の平均値を求める キーボードマクロを定義しなさい。 プログラミング 練習問題 4 解答「リスト(並び)の平均値」 参照 . (*)

ループ

多くのプログラムでは、手順の一部が繰返し実行されます。 Calc はこれを可能にするループコマンドを持っています。 ループはキーボードマクロの中で特に便利ですが、実際はいつでも有効です。

1:  x^6          2:  x^6        1: 360 x^2
    .            1:  4             .
                     .

  ' x^6 RET          4         Z < a d x RET Z >

「繰返しループ」構造に微分コマンドを組み込んで x^6 の 4階微分を計算しました。 この構造は繰返し回数をスタックから pop して、 その回数だけループ本体を実行します。

ループ本体を入力中に間違ってしまったら、 Z C-g をタイプしてループコマンドを取り消します。

次は別の一例です。

3:  1               2:  10946
2:  1               1:  17711
1:  20                  .
    .

1 RET RET 20       Z < TAB C-j + Z >

レベル 2 とレベル 1 にある数値は、 それぞれ 21番目と 22番目のフィボナッチ数のはずです。 (どうなっているのか知りたければ、 ループ本体を数回繰返してみなさい。 C-j と、もし有れば Line-Feed や LFD キーは、 レベル 2 にある数をコピーします。)

フィボナッチ数の素敵な性質は、 n 番目のフィボナッチ数が φ^n / sqrt(5) を計算し直近の整数に丸めると直接求められる事です。 ここで「黄金比」 φ(1 + sqrt(5)) / 2 です。 (便利のため、 この定数は変数 phi または I H P コマンドで利用できます。)

1:  1.61803         1:  24476.0000409    1:  10945.9999817    1:  10946
    .                   .                    .                    .

    I H P               21 ^                 5 Q /                R

(*) 練習問題 5. 連分数(continued fraction) 表現による φ1 + 1/(1 + 1/(1 + 1/( ... ))) です。 この方法で近似値をどこまでも計算することができます。 最も内側の 1/( ... ) は 1 で置きかえます。 20階の連分数を使って φ の近似値を求めなさい。 プログラミング 練習問題 5 解答「連分数 φ」 参照 . (*)

(*) 練習問題 6. フィボナッチ数におけるような 線形の漸化式は行列として表現できます。 a, b c を 3つの連続するフィボナッチ数として、 ある行列にベクトル [a, b] を掛けた時、 積が [b, c] になるような行列が決定されます。 さて、行列計算を使って、与えられた整数 n から n 番目の フィボナッチ数を計算するプログラムを書きなさい。 プログラミング 練習問題 6 解答「行列フィボナッチ数」 参照 . (*)

"for" ループ

for ループはより洗練されたループです。 第20次の「調和」数を計算したいとしましょう。 これは 1 から 20 までの整数の逆数の合計です。

3:  0               1:  3.597739
2:  1                   .
1:  20
    .

0 RET 1 RET 20         Z ( & + 1 Z )

"for" ループは 2つの数(下限と上限)を pop し、 内部カウンタを下限から上限まで増加させながらループ本体を繰返します。 ループ本体を実行する直前に、ループカウンタの現在値を push します。 (訳注:このあたりは目に見えないので注意) ループ本体実行直後に、"step"すなわちループカウンタの増分を pop します。 ご覧のように、このループの増分は常に 1 です。

(訳注: 判りにくいが、 訳者の理解を書下せば(怪しい言語だが)次の通り

`Z ('
Pop(min,max); for (i = min to max) { Push(i);
ループ直前のスタックには最小値と最大値を置け。
ループ内側左端で ループカウンタが Push される。
`Z )'
Pop(delta); i = i + delta; }
ループ内側右端で増分をスタックに置け。

ループの内側に Push や Pop が隠れているのがポイント。)

この調和数関数は、 種々のループ制御変数だけでなく計算中の合計もスタックに保持します。 これが判りにくいなら、変数を使って合計することもできます。

1:  0         2:  1                  .            1:  3.597739
    .         1:  20                                  .
                  .

    0 t 7       1 RET 20      Z ( & s + 7 1 Z )       r 7

s + コマンドは、スタック先頭の値を変数に足し込みます (そして先頭の値を取除きます)。

"for" ループを必要とする処理の多くは、 Calc の高レベル処理によってもっと容易に実行できることに気付くべきです。 調和数を計算するには他に 2つ方法があって、 ベクトルのマッピングやまとめ作用を使う方法 (v x 20, そして V M &, そして V R +)や、 合計コマンド a + を使う方法があります。 これらは、ループを使うよりもおそらく簡単でしょう。 しかしながら、ループが最善の方法である場合もあります。

(*) 練習問題 7. "for" ループを使って 4 より大きな最初の調和数を求めなさい。 プログラミング 練習問題 7 解答「4 より大きな調和数」 参照 . (*)

ローカル変数

もちろん、プログラムで変数を使うなら、 呼出し前に同じ変数に保存されていた値をプログラムが書替えてしまうことを 心配しなければなりません。 これはしかし、次のようにして簡単に手当てできます。

    .        1:  0.6667       1:  0.6667     3:  0.6667
                 .                .          2:  3.597739
                                             1:  0.6667
                                                 .

   Z `    p 4 RET 2 RET 3 /   s 7 s s a RET    Z '  r 7 s r a RET

Z `(これはバッククォート文字です)をタイプした時、 Calc はその時のモード設定と10個の「クイック変数」の内容を 将来の利用のためにセーブします。 Z '(今度はアポストロフィーです)をタイプした時、 Calc はそれらのセーブされた値を復旧します。 従って p 4s 7 は、この手順の外部に影響を及ぼしません。 キーボードマクロの本体をこれで囲むことにより、 マクロがマクロユーザーの仕事に干渉しないことが保証されます。 スタックの内容や名前付き変数の値が Z ' 以降も存続するということです。

条件分岐

ベルヌーイ数(Bernoulli numbers)は面白い性質を持った数列で、 奇数項は全てゼロであり、 偶数項は計算が困難ですが大まかには式 2 n! / (2π)^n によって近似できます。 (近似)ベルヌーイ数を計算するキーボードマクロを書いてみましょう。 (Calc はベルヌーイ数を厳密に計算する k b コマンドを持っていますが、 高位のベルヌーイ数は非常に大きな数の分数なので、 このコマンドは大きな n については非常に遅いです。)

1:  10               1:  0.0756823
    .                    .

    10     C-x ( RET 2 % Z [ DEL 0 Z : ' 2 $! / (2 pi)^$ RET = Z ] C-x )

Z [ を "then"、 Z : を "else"、Z ] を "end-if" と 読替える事ができます。 あからさまな "if" コマンドは必要ありません。 Z [ はその目的のためにスタックから値を pop し、 それがゼロ以外の数値ならば「真」、 ゼロまたは数値以外(式のような)であれば「偽」とします。

実際の 10番目のベルヌーイ数は 5/66 です。

3:  0.0756823    1:  0          1:  0.25305    1:  0          1:  1.16659
2:  5:66             .              .              .              .
1:  0.0757575
    .

10 k b RET c f   M-0 DEL 11 X   DEL 12 X       DEL 13 X       DEL 14 X

ループをもう少し練習するために、 ベルヌーイ数の偶数項の表を計算しましょう。

3:  []             1:  [0.10132, 0.03079, 0.02340, 0.033197, ...]
2:  2                  .
1:  30
    .

 [ ] 2 RET 30          Z ( X | 2 Z )

垂直線 | は、ベクトル結合コマンドです。 これを実行するとき、 作成中のリスト(最初これは空です)はスタックレベル 2 に置かれ、 次のベルヌーイ数がレベル 1 に置かれます。 実行結果はそのベルヌーイ数がリストの最後に追加されます。 (厳密なベルヌーイ数表を作るには、 上記のキーストローク手順における X を単に k b で置換えます。)

ループと条件文を使えば、本質的に Calc の何でもプログラムできます。 ループを便利にするもう一つのコマンドは Z / で、 これはスタックから条件を取って真(非ゼロ)ならループから脱出します。 これを使って、"while" や "until" 型のループが作れます。

マクロプログラムの編集

もしキーボードマクロの入力を間違えたら、 Z E で編集できます。 まずそれを Z K で何かキーに割り付けなければなりません。 まずいいかげんなダミーのマクロ定義を入力して、 それから編集コマンドできちんと入れ直す、というのも手です。

1:  3                   1:  3           Keyboard Macro Editor.
    .                       .           Original keys: 1 RET 2 +

                                        type "1\r"
                                        type "2"
                                        calc-plus

C-x ( 1 RET 2 + C-x )    Z K h RET      Z E h

ここにお見せする表示は、 `macedit' というキーボードマクロエディタがインストールされている前提です。 `macedit' は Calc と同梱されて配布されているので、これが普通です。

キーボードマクロは純粋なキーストローク手順としてストアされます。 (Z E によって起動される) `macedit' パッケージは指定したマクロを順に調べ、 人間が読みやすいステップにデコードします。 もしキーストロークが M-x 名前 というコマンドの短縮形であれば、 その名前が表示されます。 M-x コマンドに対応しないキーストロークは、 全て `type' コマンドとして表示されます。

調和数を計算する新プログラムを編集しましょう。 まず、ダミー入力した旧プログラムの 3行を消します。 そして、新プログラムを打込みます (あるいは Emacs の M-wC-y コマンドを使って、 Info ファイルのこのページからプログラムをコピーします; `#'で始まるコメントは省いても構いません)。

calc-kbd-push         # Save local values (Z `)
type "0"              # Push a zero
calc-store-into       # Store it in variable 1
type "1"
type "1"              # Initial value for loop
calc-roll-down        # This is the TAB key; swap initial & final
calc-kbd-for          # Begin "for" loop...
calc-inv              #   Take reciprocal
calc-store-plus       #   Add to accumulator
type "1"
type "1"              #   Loop step is 1
calc-kbd-end-for      # End "for" loop
calc-recall           # Now recall final accumulated value
type "1"
calc-kbd-pop          # Restore values (Z ')

M-# M-# を押して編集を終了し、Calc に戻ってください。

1:  20         1:  3.597739
    .              .

    20             z h

書きたいコマンドが `macedit' 形式で書けなくても、 type コマンド中にキーストロークを書けば OK です。 keys コマンドというのもあって、 後続部分を Emacs 標準のキーストローク名に翻訳してくれます。 実際、`macedit' は便利な read-kbd-macro コマンドを定義していて 現バッファの現リージョンをキーストローク名形式のプログラムとして読み、 そのプログラムを X (そして C-x e ) キーに定義します。
(訳注: C-x e は最も最近のキーボードマクロを実行する Emacs 標準コマンド call-last-kbd-macro' である。)
これはとても便利なので、 Calc ではこのコマンドを M-# m キーにバインドしています。 下の書式のこのマクロを読込んでみましょう?????。 下のテキストの片端で C-@ (あるいは C-SPC) を押し、 反対の端で M-# m とタイプしてください。

Z ` 0 t 1
    1 TAB
    Z (  & s + 1  1 Z )
    r 1
Z '

(*) 練習問題 8. 方程式の数値解を見つける一般的アルゴリスムに ニュートン法(Newton's Method) があります。 任意の関数 f について、与えられた方程式 f(x) = 0 と、 求める解に充分近い初期推定値 x_0 に対し次式を繰返し適用します。

new_x = x - f(x)/f'(x)

ここで f'(x)f の導関数です。 x の値は速やかに解に収束、 つまり new_xx がやがて現行精度限界内で等しくなります。 スタック上にある x の式と初期推定値 x_0 を取り、 式がゼロになる x の値(根)を求めるプログラムを書きなさい。 そのプログラムを使って、 sin(cos(x)) = 0.5x = 4.5 付近の解を求めなさい。 (角度はラジアン単位。) ちなみに組込みの a R (calc-find-root) コマンドは、 可能な限りニュートン法を使います。 プログラミング 練習問題 8 解答「ニュートン法」 参照 . (*)

(*) 練習問題 9. ディガンマ(digamma) 関数 ψ(z)ln(Γ(z)) の導関数として定義されます。 大きな数 z に対し、無限級数で近似できます。

ψ(z) ~= ln(z) - 1/2z - sum(bern(2 n) / 2 n z^(2 n), n, 1, inf)

ここで sumn が 1 から無限大(または充分な精度を得るに足る大きな数)までの合計を表し、 そして bern 関数はベルヌーイ数の(厳密な)値を計算します。 この級数は収束が保証されないとはいえ、実際は大丈夫です。 オイラーのガンマは興味深い数学的定数で、およそ 0.5772 です。 これを式で表す一例は、 γ = - ψ(1) です。 残念ながら 1 はあまり大きな数ではなく、上の近似式が使いものになりません (5 なら z としてずっと安全です)。 幸い、 ψ(5) から漸化式 ψ(z+1) = ψ(z) + 1/z を使って ψ(1) を計算できます。 あなたの課題は、 ψ(z) を計算するプログラムを書くことです。 必要に応じ、z を 5 以上に「持ち上げ」てから上記近似式を使いなさい。 ループコマンドを使って級数を計算しなさい。 あなた自身のユーザー定義関数を使って γ を精度 12桁で計算しなさい。 (Calc はオイラー定数を計算する組込みコマンド I P を持っています。 これを利用して自分の答を検証しなさい。) プログラミング 練習問題 9 解答「ディガンマ関数」 参照 . (*)

(*) 練習問題 10. x の多項式と 数 m がスタック上に与えられていて、 ただし多項式は m 次以下(つまり x^m よりも高次の項が無い)とします。 この多項式を係数リスト形式に変換するプログラムを書きなさい。 例えば、5 x^4 + (x + 1)^2m = 6 の場合、 [1, 2, 1, 0, 5, 0, 0] のようなリストになります。 また、この形式から普通の代数形式に戻す方法を考えなさい。 プログラミング 練習問題10 解答「多項式の展開」 参照 . (*)

(*) 練習問題 11. 第1種スターリング数は、 漸化式によって定義されます。

s(n,n) = 1   for n >= 0,
s(n,0) = 0   for n > 0,
s(n+1,m) = s(n,m-1) - n s(n,m)   for n >= m >= 1.

これは Calc の再帰的(recursive)プログラムを使って実現できます。 そのプログラムは、 一般式の2つの右辺の数を計算するために自分自身を呼出さねばなりません。 常に「より簡単な」引数で自身を呼出すため、 遂には計算が終わることが明らかです。 Emacs のキーボードマクロで再帰呼出しをやろうとすると、 マクロ定義が完了する前に実行されることになるのでちょっと厄介です。 そこでお薦めの戦略があります。 「ダミーマクロ」を作り、例えば Z K s でキーに割付けます。 ここで、z s コマンドを再帰的に呼出す本当の定義を入力して Z K s で同じキーに割付けます。 すると z s コマンドは完全に再帰的なプログラムを実行するようになります。 (もう一つの方法は、Z EM-# m (read-kbd-macro) を 使ってマクロ全体を一気に読込み、「トレーニング」を省略することです。)

課題: スタックに与えられた nm から 第1種スターリング数を計算するプログラムを書きなさい。 s(4,2) のように小さな入力でプログラムをテストしなさい。 (スターリング数の組込みコマンド k s で答をチェックすることができます。) プログラミング 練習問題11 解答「再帰によるスターリング数」 参照 . (*)

このチュートリアルで見てきたプログラミングコマンド群は低レベルで汎用のものです。 いちいち詳細に定義するプログラムよりも、 ベクトルマッピングや書替え規則のような高レベル関数のほうが ずっと便利であることがしばしばです。

(*) 練習問題 12. 今度は書替え規則を使って、 第1種スターリング数を計算する別のプログラムを書きなさい。 今回も同様に、 nm はスタックから与えなさい。 プログラミング 練習問題12 解答「書替えによるスターリング数」 参照 . (*)


Calc マニュアルのチュートリアルはこれで終わります。 今やあなたは Calc をいろいろな計算に上手に使えるようになりました。 しかし Calc にはこのチュートリアルでまだ触れていない多くの機能があります。 マニュアルの残りでその全貌を網羅します。


Go to the first, previous, next, last section, table of contents.     利用度数