楽屋裏 - Dsz によるループ脱出
楽 屋 裏
e-Gadget
2015/01/11 ポイントを整理するため修正
2015/02/01 カシオからの回答のまとめ
※ 本件に関する注記を Casio Basic 入門14 の最後に追加しています
以前、Do や While を使った2重ループで、内側ループを脱出するために Dsz 命令を使うとエラーになる、と言う問題を取り上げました。
⇒ 楽屋裏 - 多重ループの謎
⇒ 楽屋裏 - ループ脱出の問題(カシオの回答)
カシオお客様センターとやりとりをし、カシオ殿の回答にも多少の時間はかかっておりましたが、それ以上に私が中途半端なままにしておりました。そこで、一旦カシオ殿の回答をまとめて掲載致します。以前のコメント欄を追いかけて頂ければ、お分かりの方にはお分かりだと思うのですが、それではカシオ殿には申し訳ないので、改めて別エントリーとしました。
他の方からもコメントがあるように、カシオお客様センターは、時間がかかってもきちんと対応をしてくれます。
さて、問題の起点は、以下の2重ループで Syntax ERRORが出ることでした。
While 1
20→C
Do
Dsz C
LpWhile 1
WhileEnd
これに対して、カシオ殿の回答は、以下のものでした。
2重ループ構造という条件はございません。
Dsz/Isz/⇒で偽の場合に、次のコマンドをスキップするという
仕様のため、次の条件の場合にSynERRORとなります。
・条件1: DoやWhileを用いたループがあること
・条件2:ループ脱出に Dsz/Isz/⇒を利用すること
・条件3:Dsz/Isz/⇒ の次のコマンドが、LpwileかWhileEndであること
この期待しない動作については、制御のスタック管理において、古くからある Dsz / Isz / ⇒ と 後から実装された Do ループと While ループ で整合性がとれていないのではないか?と言う質問については、
ご指摘の通りでございます。従来のコマンドに慣れ親しんだユーザー様に配慮し、従来仕様をあえて残させて頂きました。
とのことでした。結果として現行 Casio Basic の内部実装通りの動作と言うことです。
これを反映させた具体的な対応策は、下記のようになります。カシオ殿の提示はIf 文を用いたものですが、私が敢えて ⇒ 命令を使って書き直しています(上の不具合条件に抵触しません)。
1) Dsz / Isz を用いて脱出するループに Do / While ループを使わず Lbl / Goto を使う方法
While 1
20→C
Lbl 0
C-1→C
C⇒Goto 0
WhileEnd
2) Do / While ループの脱出に Dsz / Isz の代わりに Break を使う方法
While 1
20→C
Do
C-1→C
C=0⇒Break
LpWhile 1
WhileEnd
さて、
これまでのところ、sentaro様により、以下の回避方法が提示されています。
While 1
20→C
Do
Dsz C
LpWhile 1
LoWhile 0
WhileEnd
私も、以下の回避方法を見つけております。
While 1
20→C
Dsz C
LpWhile 1
Goto 0:Lbl 0
WhileEnd
sentaro様の方法は、内側の Do ループのスタック制御が必ず行われるようにした自然な方法です。しかしこの方法は、Doループでは使えますが、While ループでは使えないと思います。一方、私が見つけた方法は Do / While ループのいずれでも使えるように見え、Goto の実装に深入りする方法です。
これらの方法は、たまたまうまく言っているだけの可能性があります。
さらに、下記のようなコードは、上記の異常動作の条件に合致しますが、これまで動作異常の経験がありません。
20→C
Do
Dsz C
LpWhile 1
[スタック制御を行う何かの処理]
これも、たまたまうまくいっているだけの可能性があります。その1つの例として、Goto 0:Lbl 0 だったと考えられます。
今回、外側のループに Dsz で脱出させてみたところ、上の対処療法では問題があることが分かりました。
例えば、
20→D
While 1
20→C
Do
Dsz C
LpWhile 1
LpWhile 0
Dsz D
WhileEnd
は、D が 0 になった段階で Syntax ERROR になります。
20→D
Do
20→C
Do
Dsz C
LpWhile 1
LpWhile 0
Dsz D
LpWhile 1
も、D が 0 になった段階で Suntax ERROR になります。
外側のループにも同じ対処療法を施すと、
20→D
Do
20→C
Do
Dsz C
LpWhile 1
LpWhile 0
Dsz D
LpWhile 1
LpWhile 0
これでは、Syntax ERROR の表示は出ませんが、D が 1 の状態で固まってしまい、先に進みません。Dsz でのループ脱出は、やはり本質的なスタック管理のエラーがあるようで、対処療法も効きません。
Dsz の実行速度は魅力的ですが、少なくとも、
D-1→D:D=0⇒Beak
WhileEnd
或いは
D-1→D:D=0⇒Break
LpWhile 1
といった記述で、Break を使うのが安全のようです。カシオが提示した対処方法には、If 文での Break でしたが、可読性や処理速度を考えれば、条件ジャンプ命令 ⇒ を使う方がマシなので、これを対処方法とします。
但し、今のところ、Do や While ループの脱出に Dsz を使い、上記のような対処療法(Goto 0:Lbl 0 の挿入)の結果、エラーや異常動作を経験していません。たまたまなのかどうか、今はそこに最大の興味が移っています。
[2015/02/01] カシオお客様センターからの回答
以下の質問をカシオお客様センターに投げかけました。
A) 上記の Goto 0:Lbl 0 による対処方法の是非
B) 上記の LpWhile 0 による対処方法の是非
C) Do / While ループに比べて、制御構造が複雑になると Goto/Lbl ループが遅くなる理由
以上について、関連する Goto の内部動作について教えて欲しい。
その結果、Goto の内部動作に関する情報と共に、丁寧で貴重な回答が得られました。それを以下にまとめます。
Goto と その他のスタック制御を行うコマンドでは内部管理が異なっている。
Goto 以外の制御コマンドは、1本のスタックを使って、制御構文開始コマンドでスタックを1つ積み、制御構文終了コマンドででスタックを1つ破棄する。ここで積まれるスタックとスタック段数を"正式なもの"と呼ぶ。
Goto については、それが実行されると、先ずプログラムの先頭から対応する Lbl を検索し、見つからない場合はエラーになる。
対応する Lbl が見つかるまで、Goto は 他の制御コマンドも検索し、Goto 専用の仮スタックと仮スタック段数カウンタに記録する。
そして Goto に対応する Lbl が見つかると、その Lbl の仮スタック段数と、正式スタック段数を比較する。
正式と仮のスタック段数の比較;
(A) Goto 実行時の正式スタック段数が、Lbl の仮スタック段数よりも大きい場合は、正式スタック段数を段数の差だけ破棄する。
(B) Goto 実行時の正式スタック段数が、Lbl の仮スタック段数よりも小さい場合は、正式スタック段数を更新して、仮スタック段数と同じにする。
以上の内部動作の後、Lbl の位置までプログラム動作を飛ばす。
1) Goto 0:Lbl 0 による対処について
20→C
Do
Dsz C
LpWhile 1
Goto 0:Lbl 0
Goto 0 が実行される時、
・ LpWhile 1 がスキップされているので、正式スタック段数は +1
・Goto 専用仮スタック段数は、Lbl 0までの検索で LpWhile 1 を見つけた時に 仮スタック段数が1つ戻されるので 0、つまり、Lbl 0 での仮スタック段数は 0
正式と仮のスタック段数の比較の結果、上記の(A)に相当するので、正式スタックを1段だけ破棄するので、正式スタック段数は 0 となり、正しく修正される。
つまり、Dsz C で制御構文終了コマンドがスキップされる場合でも、その後に Goto 0:Lbl 0 があれば、正常に修正される。
2) LpWhile 0 による対処について
20→C
Do
Dsz C
LpWhile 1
LpWhile 0
の場合、制御構文終了コマンド LpWhile 1 がスキップされても LpWhile 0 があるので、スタックが1段破棄される。
正式スタックが1本しかないので、LpWhile 0 の代わりに、WhileEnd、Next などスタックを1つ破棄するような他の制御構文終了コマンドがあれば、正常動作する。
20→C
Do
Dsz C
LpWhile 1
・・・
・・・
Retuen
このように、・・・ の部分で、LpWhie 1 を超えて前に戻るジャンプがなければ、Return で全てのスタックが破棄されるので、プログラムは正常動作する。
20→C
Do
Dsz C
LpWhile 1
Break
LpWhile 1
の場合、Break の後に制御構文終了コマンドが必要だが、それには LpWhile 1 など何でも良いことになる。
3) Goto / Lbl ループが他のループよりも大幅に処理速度遅くなる条件について
Goto / Lbl ループは、ループが回るたびに上記の Goto による仮スタック管理と正式スタックとの比較を行い、Do, While For ループでは、スタックに記録されたアドレスへジャンプする動作のみ。従って、制御構造が複雑になるほど Goto / Lbl ループが遅くなる。
さらに、Goto / Lbl ループでは、Lbl がプログラムの先頭から遠くなるに従って、動作が遅くなる。
例1 例2
Lbl 0 ・・・・
・・・・ ・・・・
・・・・ ・・・・
・・・・ ・・・・
・・・・ Lbl 0
・・・・ ・・・・
Goto 0 Goto 0
例1は、Lbl 0 がプログラム先頭にあり、検索時に最初にヒットするため、すぐにジャンプ処理ができる。
例2は、Lbl 0 に行き着くまでの間、制御構文の処理なども実施しながら検索していくことから、処理の負担がかなり大きくなり、その分処理時間も長くなる。
4) 他の Casio Basic バージョンへの適用について
今回説明した Goto の内部動作については、fx-5800P、fx-9860シリーズ、fx-CG20シリーズ、fx-FD10Pro など、カシオのプログラム関数電卓の殆どのモデルに当てはまる。
以上が、カシオによる回答をまとめたものです。
ここまでの情報を開示頂いたカシオ殿に、深く感謝致します。
応援クリックをお願いします。励みになるので...