楽屋裏 - 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 など、カシオのプログラム関数電卓の殆どのモデルに当てはまる。


以上が、カシオによる回答をまとめたものです。

ここまでの情報を開示頂いたカシオ殿に、深く感謝致します。




応援クリックをお願いします。励みになるので...

人気ブログランキングへ


FC2ブログランキングへ


keywords: fx-5800PCasioBasic換算プログラムプログラミング入門プログラム関数電卓

リンク集 | ブログ内マップ
関連記事

テーマ : プログラム関数電卓
ジャンル : コンピュータ

コメントの投稿

非公開コメント

No title

管理人様、こんにちは!

Dszによるループ脱出問題は、昨年こちらに最初にコメントさせていただくきっかけとなったとても興味深い題材でした(^^)

管理人様が対策されてたGoto 0:lbl 0を入れるということで、ネスト構造のスタックがクリアされてエラーが出なくなるというのがCasioBasicとしての解決策ともいえる新しい発見で、結論としてはループ構造を強制的に抜ける場合はやはりBreakで抜けるのがCasioBasicの文法的には正しい道なのだろうと思っています。

Dszは制御構造コマンドがすぐ後に来ることの対策をしていない代わりに高速性という特徴があるので仕方ないところですね。


ループが1重の場合には、

20→C
Do
Dsz C
LpWhile 1

このプログラムだけではSyntax ERRORが出ますので1重でもエラーは出ることになりますが、このプログラムの後にコマンドを続けるとエラーが出ないまま次のコマンドの実行に移るので、ループスタックがクリアされないまま後の実行を新たに続けてるだけという感じでしょうか。
ということなので、この後に続くプログラムでGotoを使ってスタッククリアしない限り、プログラムの最後では結局Syntax ERRORが出てしまいます。

ループ1重から抜ける場合、というか、一番外側のループから抜ける場合に限りますが、Dsz+制御構造を使ってもプログラム的にエラーもバグもなく期待する動作をする場合は、CasioBasicの癖として分かった上で使うというのが裏技的な使いこなしということになるのでしょうか(^^)

裏技

sentaro様

コメントを頂く前に、本文を編集・修正してしまいました。

言いたいことは変わっていませんが、ポイントを明確にして

・カシオからはきちんと回答があったこと
・回答に基づいた対応策を明示すること

を分かりやすくした次第。

なお、LpWhile 0 や Goto 0:Lbl 0 の追加、や 問題のコードの後に制御スタックを触る処理があると、異常動作にならないことも事実としてあります。

たまたまなのか、そうでないのか、そのあたりをカシオに改めて問い合わせしました。


> ループ1重から抜ける場合、というか、一番外側のループから抜ける場合に限りますが、Dsz+制御構造を使ってもプログラム的にエラーもバグもなく期待する動作をする場合は、CasioBasicの癖として分かった上で使うというのが裏技的な使いこなしということになるのでしょうか(^^)

裏技を教えてくれるかどうか、カシオ殿の懐の深さが楽しみです。

No title

管理人様、どうもです!

先の私のコメントの、

>ループ1重から抜ける場合、というか、一番外側のループから抜ける場合に限りますが、

ここは1重でなくても多重ループでも同じことでしたね(^^;


この問題の肝は、Goto処理で普通とはちょっと違った動作が行われているらしいということですね。

Gotoは多重ループ内への飛び込みでも上手く動作することが確認できてましたから、制御構造スタックのクリアとともにスタック再構築という独自の処理が行われているらしいというのが実機での動作確認上の結論でした。

私が例示したLpWhile 0でのスタックのつじつま合わせ解決策は多重ループには対応できないのと制御構造の1対1の対応関係が壊れてGotoでエラーになるのでNGですね(^^;
管理人様のGoto 0:Lbl 0はGoto命令の特殊な動作仕様で問題なく動作するのでこれは対応策としては一番いい方法です。

これらのGoto動作は一つ前のCFX-9850GC PLUSでは再現しない仕様なので今のCasioBasicだけの癖というか仕様だけなので、そこのあたりちょい踏み込んで回答がもらえるとすっきりなんですけどね。

高精細な関数電卓の新型機も出て、fx-5800Pの後継機の期待も高まる中、CASIOの回答にも期待してしまいますね(^^)

Undocumented Goto

sentaro様

確かにGoto には何かありそうです。

入力ボックス高速化の検討の際にも、Lbl 0 ~ Goto 0 無限ループの変わりに While 1 ~ WhileEnd に置き換えると、制御構造が複雑になるにつれて、高速化の効果が顕著になることを経験しました。

Goto がスタック整合性をとるための内部処理を行っているなら説明がつきますね。

Undocumented Goto となるか、何らかの説明を頂けるのか、ちょっと楽しみですね。

カシオ殿から「検討する」との返信

皆様

さっそくカシオ殿から返信があり、「検討する」とのこと。放置せずに返信をくれるところは、さすがです。

内部実装に立ち入った質問なので、果たしてどこまで回答を頂けるかナントモ分かりませんが、チョットだけ期待して待とうと思います。


気長に待たせて頂くことにします。

速報 - カシオからの回答

昨日、カシオお客様サポートセンターから回答が来ました。

やはり、カシオはきちんと対応をしてくれます。

結論から言えば、

sentaro様の方法 LpWhile 0 の追加は当然ながら、スタックを戻す制御コマンドであれば何でも良いことが分かりました。この点は以前既に sentaro様がご指摘なさったことが、内部動作に照らして正しいことが分かりました。

Goto は、実行時に対応する Lbl の検索だけでなく、制御構造を全て検索していること、Goto 専用の仮のスタック階層管理(スタック段数)を正規のスタック管理とは別に行っていて、仮のスタック段数と正規のスタック管理とで不整合があると修正を行う仕組みが組み込まれていて、結果として、Goto 0:Lbl 0 は理屈にあった正しい対処方法とのことです。

この土日に、回答を整理して報告しようと思います。

カシオの了承なしに、頂いた回答をそのまま掲載するのはルール違反なので、先ずは私の言葉で速報として報告します。その後、カシオの了承を得た内容を掲載する予定です。

すごいです!

管理人様、こんにちは!

CASIOからの今回の回答はちょっとしびれる内容ですね。

ここまで内部動作に踏み込んだ回答をしてもらえたというのがすごいです。

Gotoのこうした実装上の工夫がCasioBasicの安定度に繋がっているのですね。

昨年以来のもやもやがすっきりと晴れた感じで嬉しいです(^^)

No title

親分、夜分に恐れ入ります。CASIO電卓については「外野」のakatukiです。お知らせを受けて参りました。

最近はプログラムやっていない上、CASIO については外野なのですが、

> Dsz/Isz/⇒で偽の場合に、次のコマンドをスキップするという

という辺り、「ああっ !」と思ってしまいました。そういや、HP15Cの分岐メカって、こんなイムプリメントでした。
最近、ある組み込み用プロセッサのアセンブラをかじっていたのですが、そのプロセッサでは条件分岐の時に「次の命令をNO-Operationに置換する」とかやっているらしいのです。
こういうのみると、CASIOの電卓言語もアセンブラっぽい感じもして、ウーン、懐かしいなぁ。たまには何か、プログラムでも書いてみたいのですが、お題が浮かばんのです。
fx-5800Pを使ってみたくなりましたが、それもさることながら、何かプログラムのネタを探さないとアカンです。



No title

akatuki様、こんにちは!

>最近、ある組み込み用プロセッサのアセンブラをかじっていたのですが、そのプロセッサでは条件分岐の時に「次の命令をNO-Operationに置換する」とかやっているらしいのです。

もしかしてPIC系でしょうか?


>CASIOの電卓言語もアセンブラっぽい感じもして、

CASIOのFX-502P/602P/603P系の言語はほとんどアセンブラで、前にHP-15Cのエミュを触った時にRPN以外はほとんど同じという感覚ですぐに馴染んだ記憶があります。

高級言語にどっぷり慣れてしまった今となってはアセンブラ系言語はパズルを解くみたいな感覚になってしまいますけど、たまにはそういうのもいいものですよね(^^)

組み込みプログラム

akatuki様

組み込みプログラムと言えば、Casio Basicによる電卓プログラムは、
・無限ループのプログラムで、[AC]キーによる割り込みで終了させる
・大域変数を扱う
など、組み込みプログラムに近いところがありそうなことに気付きました。


> という辺り、「ああっ !」と思ってしまいました。そういや、HP15Cの分岐メカって、こんなイムプリメントでした。

この機能は、FX-502P の時代からカシオのプログラム電卓には実装されているわけで、アセンブラそのものと言って良いと思います。


> fx-5800Pを使ってみたくなりましたが、それもさることながら、何かプログラムのネタを探さないとアカンです。

私の場合、ネタ切れになったかなぁ、と思ったら、ネタが出てきてくれます。自然に出てくることもあれば、akatuki様などとの会話から出てくることもあり、助かっています。まいどありがとうございます。

最新記事
最新コメント
カテゴリ
C# (3)
検索フォーム
Visitors
Online Counter
現在の閲覧者数:
プロフィール

やす (Krtyski)

Author:やす (Krtyski)
since Oct 30, 2013


プログラム関数電卓は、プログラムを作り・使ってナンボ!

実際に触って気づいたこと、自作プログラム、電卓プログラミングについて書いています

おもしろい・役に立つならクリックしてください。励みになります。

人気ブログランキングへ


FC2ブログランキングへ


写真: 「4駆で泥んこ遊び@オックスフォード郊外」

リンク
月別アーカイブ
Sitemap

全ての記事を表示する

RSSリンクの表示
最新トラックバック
ブロとも申請フォーム

この人とブロともになる

QRコード
QR