FOR〜NEXTループはよく使われる命令ですが,その正確な動作を知っておくと,プログラムが簡単になり,また,予期しないバグを避けることができます。なお,以下の記述はFull BASIC規格に依るものですが,旧規格の基本BASICの場合でも基本的には同じです。
FOR〜NEXT構文は,
FOR 制御変数 = 始値 TO 限界 STEP 増分 ……… NEXT 制御変数
の形に書かれます。
限界と増分はFOR〜NEXTの繰り返しの最初の1回目にのみ評価され,それらの値はプログラムからは見えない変数に保存されます。
その後,増分が正の数のときは,次のように動作します。
(1)制御変数に始値を代入する。
(2)制御変数の値が限界より大きければ,この構文の処理を終えてNEXT文の次の行に進む。
(3)NEXT文を実行すると,制御変数に増分が加算され,(2)に戻る。
増分が負の数のときの動作は,(2)の段階で大小が逆になります。
また,「STEP増分」を省略すると,増分は1とみなされます。
増分に負数を指定することができます。その場合,繰り返しが終了する条件は制御変数の値が限界より小さくなることです。
例
10 FOR i=10 TO 0 STEP -1 20 PRINT i 30 NEXT i
増分が正の数であるとき,始値が限界より大きいと,繰り返し部分は一度も実行されずに先に進みます。このとき,制御変数の値は始値のままです。
繰り返しを抜けるための条件は制御変数の値が限界よりも大きくなることです。したがって,たとえば,
10 FOR i=0 TO 100 STEP 3 20 PRINT i 30 NEXT i
のように 限界と始値との差が増分の倍数でないFOR〜NEXTも実行可能です。20行のPRINT文が最後に実行されるときのiの値は99で,この繰り返しを抜けたときiの値は102になっています。
小数を増分に指定する場合,誤差に注意が必要です。たとえば,
10 FOR x=0 TO PI STEP PI/6 20 PRINT x 30 NEXT x
で最後に出力されるのは, 2.6179938779915 で,3.141592…が出力されて終わると思っていると期待を裏切られます。というのは,この次のxの値は 3.1415926535898 で, 3.14159265358979よりわずかに大きくなるため,そこで繰り返しを抜けてしまうからです。
対策の一つは,限界を少し大きめに指定することで,
たとえば,
10 FOR x=0 TO PI+1E-10 STEP PI/6
とすると,最後の出力は3.1415926535898 となって,ほぼ期待通りになります。
もうひとつのやり方は,FOR文には整数しか使わないと決めて,
10 FOR i=0 TO 6 20 LET x=PI*i/6 30 PRINT x 40 NEXT i
のようにすることです。このやり方は誤差が累積することがないので正確さの点で優位です。
2進モード(および複素数モード)では,0.1のように,見かけ上,切りのよい小数も誤差を持ちます。たとえば,
10 OPTION ARITHMETIC NATIVE 20 FOR x=1 TO 2 STEP 0.1 30 PRINT x 40 NEXT x 50 END
を実行すると最後の出力は1.9になります。
増分と限界はプログラムからは見えない変数に保持されます。そのため,
10 LET s=0.1 20 FOR i=1 TO 10 STEP s 30 PRINT i,s 40 IF i=1 THEN LET s=1 50 NEXT i 60 END
を実行すると,40行でsの値を書き換えているにもかかわらず,増分はずっと0.1のままです。
また,
10 LET n=10 20 FOR i=1 TO n 30 PRINT i,n 40 IF i=4 THEN LET n=5 50 NEXT i 60 END
を実行すると,40行でnの値を書き換えているにもかかわらず,30行は10回実行されます。
10 FOR i=1 TO 100 20 PRINT i 30 IF i=20 THEN LET i=100 40 NEXT i
のように制御変数の値を書き換えると,繰り返しに影響を与えます。
内部副プログラムや内部関数定義で呼び出し元と同じ制御変数を用いる誤りはよくある失敗です。
例
10 FOR i=1 TO 10 20 CALL ss 30 NEXT i 40 SUB ss 50 FOR i=1 TO 10 60 PRINT i 70 NEXT i 80 END SUB 90 END
このプログラムで20行は1回実行されるのみです。なぜなら,内部副プログラムssの実行を終えた時点で変数 i の値が11になっているからです。
(この種の誤りを避ける手段は,(1) 別の変数名を用いる (2) 外部手続きを用いる (3) 拡張命令のLOCAL文を使う。これらのうち,最も確実なのは,外部手続きの利用です。)
EXIT FOR文は制御変数の値を変えません。また,BASICではFOR〜NEXTループを抜けても制御変数は有効な変数です。
したがって,制御変数の値と限界とを比べることで,EXIT FOR文を実行して繰り返しを抜けたのか,それとも最後までFOR〜NEXTの繰り返しを実行して抜けてきたのかを判別することができます。
ある自然数nが素数かどうかは,√nを超えない自然数で順に割ってみればわかります。除数をFOR〜NEXTで順に作って割り切れたら繰り返しを抜けることにしたら,FOR〜NEXTを抜けたところで制御変数の値を調べることで,約数が存在したのか否かを判定することができます。
100 INPUT n 110 FOR k=2 TO SQR(n) 120 IF MOD(n,k)=0 THEN EXIT FOR 130 NEXT k 140 IF k>SQR(n) THEN 150 PRINT "素数" 160 ELSE 170 PRINT "約数";k;"を持つ合成数" 180 END IF 190 END
少し詳しく書くと,
FOR v=始値 TO 限界 STEP 増分 (一連の文) NEXT v |
は,
LET own1=限界 LET own2=増分 LET v=始値 DO UNTIL (v - own1)*SGN(own2)>0 (一連の文) LET v=v+own2 LOOP |
で定義されています。
own1,own2は,翻訳時にFOR〜NEXT構文ごとに確保される変数で,プログラム上は表に現れない変数です。プログラムで直接的に操作する手段はありません。
例 JIS規格のBASIC(基本BASICを含む)では,次のプログラムの30行は11回実行され,100から110までが順に出力されます。
10 LET n=100 20 FOR n=n TO n+10 30 PRINT n 40 NEXT n 50 END
<Note>
own1,own2はFOR文の実行ごとに確保される変数ではないことに注意してください。FOR〜NEXTループ内からGOSUB文を用いてFOR〜NEXTの再帰呼び出しを行うと(制御変数の退避を正しく行ったとしても)正しく動作しません。
なお,Ver.7.2.0でFOR〜NEXTを内部手続き内に書いた場合は,own1,own2を内部手続きの局所変数として確保するように仕様を変更しました。JISとの整合性を確保するために,従来通り,常にプログラム単位の変数として確保するオプションも用意してあります。(内部手続きに書かれたFOR〜NEXTループから内部手続きの再帰呼び出しを行うときに,own1,own2がプログラム単位の変数であると,意図した通りに動作しないかも知れません)