例28を実行すると,6,8,10のような無意味なピタゴラス数も出力します。
これを防ぐためにはx,yの最大公約数が1でないものを除外すればいいのですから,
例28のプログラム中に最大公約数を求めるプログラム例35を埋め込んで
100 FOR x=1 TO 100 110 FOR y=x TO 100 120 LET a=x 130 LET b=y 140 DO 150 LET r=MOD(a,b) 160 IF r=0 THEN EXIT DO 170 LET a=b 180 LET b=r 190 LOOP 200 IF b=1 THEN 210 LET z=SQR(x^2+y^2) 220 IF INT(z)=z THEN PRINT x,y,z 230 END IF 240 NEXT y 250 NEXT x 260 END
とすればいいでしょう。しかし,このようなプログラムを作るのは,実際には容易ではありません。たとえば,例28のプログラムが変数名としてx,y,zでなくa,b,cを用いていたとしたら,合体させたプログラムは正常に動作しません。
BASICには多くの関数が組込み関数として用意されていますが,それで十分とはいえません。DEF文は利用者が関数を定義する機能を提供していますが,DEF文では数値式で書けるような関数しか定義できません。BASICには,より複雑な処理をしなければ求められないような関数でも定義できる機能が用意されています。
前項に示したプログラムは,2数a,bの最大公約数をGCD(a,b)と書くことにすればもっとわかりやすく書くことができます。そのために外部関数定義というものを使います。
BASICのプログラムではEND行までの部分を主プログラムといいます。そして,END行以降には外部手続きと呼ばれるものが続きます。外部関数定義も外部手続きの一種です。
例47は,200行から280行までの部分がGCD関数を定義する外部関数定義です。外部関数定義部はEXTERNAL
FUNCTION行で始まり,END FUNCTION行で終わります。EXTERNAL FUNCTION行には,関数名と引数(ひきすう)を書きます。引数は括弧でくくって書きます。引数が2個以上ある場合にはそれらをコンマで区切って書きます。
例47
100 DECLARE EXTERNAL FUNCTION GCD 110 FOR x=1 TO 100 120 FOR y=x TO 100 130 IF GCD(x,y)=1 THEN 140 LET z=SQR(x^2+y^2) 150 IF INT(z)=z THEN PRINT x,y,z 160 END IF 170 NEXT y 180 NEXT x 190 END 200 EXTERNAL FUNCTION GCD(a,b) 210 DO 220 LET r=MOD(a,b) 230 IF r=0 THEN EXIT DO 240 LET a=b 250 LET b=r 260 LOOP 270 LET GCD=b 280 END FUNCTION
主プログラムと外部手続きはプログラム単位と呼ばれます。外部関数を利用するプログラム単位には,その外部関数の名前を関数名として用いることの宣言文を書きます。上のプログラムでは,主プログラムで外部関数としてGCDを用いるために100行のような宣言文を書いています。このような宣言を書くことで,外部関数の名前と同じ組込み関数があったとしても正しく動作させることが可能となります。
宣言文は,実際に関数を使う行よりも上の行に書きます。文法的には,DECLARE
EXTERNAL FUNCTION に続けて目的の関数名を書くだけです。
上のプログラムでは,GCD関数を130行で利用しています。これを関数の呼び出しといいます。また,関数を呼び出すとき書く引数(上のプログラムではxとy)を実引数と呼びます。GCD関数が呼び出されると,変数a,bに実引数の値が代入されて,関数定義部の各行が順に実行されます。関数の値は,270行にあるような関数名に代入する形のLET文で決定されます。
プログラム単位は,変数名に関して独立した存在です。異なるプログラム単位に同じ名前の変数があってもコンピュータの内部では別の変数です。たとえば,例47の主プログラムの変数x,y,zをa,b,cに書き換えたとしても,プログラムは正常に動作します。
外部関数定義を利用すれば3数の最大公約数を求めるのは簡単です。次のようにすればいいのです。
100 DECLARE EXTERNAL FUNCTION GCD 110 INPUT a,b,c 120 PRINT GCD(GCD(a,b),c) 190 END 200 EXTERNAL FUNCTION GCD(a,b)
例47と同じ 280 END FUNCTION
n!に関して漸化式 0!=1,n!=n・(n-1)! が成立します。ですから,7!は6!に7をかければ求められます。そして,6!は5!に6をかければ求められます。このように数値を逆にたどっていくと,最後は0!=1
という式に行き着いて問題が解決します。
このように,同種の,しかし,より規模の小さい問題に帰着させることを繰り返し実行して問題を解決する手法を再帰と呼びます。
BASICでは,関数を定義するときに自分自身を利用することができます。次のプログラムでは,階乗(factorial)を計算する関数FACTの定義のなかで自分自身を利用しています。コンピュータのプログラミングでは,関数の定義のなかで自分自身を呼び出すことを再帰呼び出しといいます。
例48 階乗の計算
10 DECLARE EXTERNAL FUNCTION FACT 20 INPUT n 30 PRINT FACT(n) 40 END 100 EXTERNAL FUNCTION FACT(n) 110 IF n=0 THEN 120 LET FACT=1 130 ELSE 140 LET FACT=n*FACT(n-1) 150 END IF 160 END FUNCTION
BASICで関数の再帰呼び出しがうまく機能するのは,関数が呼び出されるごとに変数が新たに割り当てられるからです。たとえば,140行は
LET FACT=FACT(n-1)*n
としても正しく動作しますが,FACT(n-1)の計算の途中でnの値が変化してしまうと正しく動作しないはずです。つまり,関数FACTが呼び出されるごとに,別の場所にその回の呼び出しにだけ通用する変数nが新たに割り当てられているのです。
最大公約数を求めるユークリッドの互除法も再帰的な算法の典型例です。BASICではこの算法を次のように書くことができます。
例 49
10 DECLARE EXTERNAL FUNCTION GCD 20 INPUT a,b 30 PRINT GCD(a,b) 40 END 100 EXTERNAL FUNCTION GCD(a,b) 110 LET r=MOD(a,b) 120 IF r=0 THEN LET GCD=b ELSE LET GCD=GCD(b,r) 130 END FUNCTION
Full BASICには円を描く命令は用意されていませんが,円を描く命令を定義して利用する手段が用意されています。それを絵定義と呼びます。
例50
10 DECLARE EXTERNAL PICTURE circle 20 OPTION ANGLE DEGREES 30 SET WINDOW -8,8,-8,8 40 DRAW circle 50 DRAW circle WITH SCALE(2) 60 DRAW circle WITH SCALE(3,2) 70 DRAW circle WITH SCALE(3,2)*SHIFT(3,4) 80 DRAW circle WITH SCALE(5,3)*ROTATE(60) 90 END 100 EXTERNAL PICTURE circle 110 OPTION ANGLE DEGREES 120 FOR t=0 TO 360 130 PLOT LINES:COS(t),SIN(t); 140 NEXT t 150 END PICTURE
100行から150行までの部分を外部絵定義といいます。外部絵定義の最初の行にはEXTERNAL
PICTUREに続けて絵名を書きます。上のプログラムでは,circleが絵名です。もし必要があれば外部関数定義の場合と同様に引数部を書くこともできます。
絵circleは原点を中心とする半径1の円を描きます。正確にいえば正360角形を描いているだけですが,コンピュータのディスプレーでは円に見えると思います。
プログラム単位はOPTION文に関しても独立です。OPTION ANGLE DEGREESを主プログラムに書いても外部絵定義では角の大きさの単位は変更されません。
絵を実行するのにDRAW文を用います。DRAW文では絵を描くときに原点を中心とする拡大・縮小や回転,あるいは平行移動などの変形を指定することができます。
SCALE(a,b)は原点を中心とするx軸方向にa倍,y軸方向にb倍の拡大です。a,bは負の数でも差し支えありません。a=bの場合には,SCALE(a)のように書くこともできます。
SHIFT(a,b)は,x軸方向にa,y軸方向にbの平行移動です。
ROTATE(α)は,原点を中心とする角αの回転です。
変形は*で結合することで合成することもできます。合成した変形は左から順に実行されます。たとえば,SCALE(3,2)*SHIFT(3,4)は拡大してから平行移動します。
外部副プログラムの機能はほとんど外部絵定義のなかに含まれています。文法的にも用いる用語が異なるだけでほとんど同じです。ただし,副プログラムでは図形を変形して描くことはできません。
例51
100 DECLARE EXTERNAL SUB circle 120 SET WINDOW -4,4,-4,4 130 CALL circle(1,-1,2) 140 END 200 EXTERNAL SUB circle(a,b,r) 210 OPTION ANGLE DEGREES 220 FOR t=0 TO 360 230 PLOT LINES: a+r*COS(t),b+r*SIN(t); 240 NEXT t 250 END SUB
例51では,点(a,b)を中心とする半径rの円を描く副プログラムcircle(a,b,r)を定義しています。副プログラムを呼び出すのにCALL文を用います。副プログラムに引数があるときは関数と同様の形式で引数を記述します。
絵および副プログラムには関数定義と異なる特性があります。それがこれから説明する変数引数です。
例52
10 DECLARE EXTERNAL SUB double 20 LET a=3 30 CALL double(a) 40 PRINT a 50 END 100 EXTERNAL SUB double(x) 110 LET x=x*2 120 END SUB
例52で副プログラムdoubleは実引数として書いた変数の値を2倍にして返します。副プログラムでは,実引数に変数を書くと,副プログラムの実行中,実引数の変数に割り当てられた領域を引数の領域として使います。したがって,副プログラムの実行中に引数の値を変更すると,実引数の値が変化します。
なお,実引数が変数そのものではないとき,たとえば,30行を
30 CALL double(2*a)
とした場合には,副プログラムに2*aの計算結果の数値が引き渡されるだけです。
a,b,cが整数の定数であるとき,方程式 a x + b y = c の整数解を探すという問題を考えてみます。なお,この型の方程式は1つ解を持てば無数の解を持つことになるので,ここでは解をひとつ求めればよいものとします。
b=0のときの解は明らかです。cがaで割り切れればx=c/a が解です。yの値は何でもよいのですが,たとえば,y=0とでもしておけばよいでしょう。反対にcがaで割り切れなければ解はありません。
b≠0のときは,aをbで割ったときの商をq,余りをrとします。a=bq+rですから,はじめの方程式をb(qx+y)+rx=cと書き換えることができます。したがって,方程式bu+rv=cの解が求まれば,x=v,y=u-qvが求める解ですし,bu+rv=cに解がなければ解はありません。
ここで,bu+rv=cの解は必ず確定します。なぜかというと,ax+by=cとbx+ry=cを比較したとき,yの係数は確実に0に近づいているからです。yの係数は整数ですから,何回かの繰り返しの後,必ず0になります。
例53
10 DECLARE EXTERNAL SUB solve 20 INPUT a,b,c 30 WHEN EXCEPTION IN 40 CALL solve(a,x,b,y,c) 50 PRINT x,y 60 USE 70 PRINT "解なし" 80 END WHEN 90 END 100 EXTERNAL SUB solve(a,x,b,y,c) 110 IF b=0 THEN 120 IF MOD(c,a)=0 THEN 130 LET x=c/a 140 LET y=0 150 ELSE 160 CAUSE EXCEPTION 999 170 END IF 180 ELSE 190 LET q=INT(a/b) 200 LET r=MOD(a,b) 210 CALL solve(b,u,r,v,c) 220 LET x=v 230 LET y=u-q*v 240 END IF 250 END SUB
160行のCAUSE EXCEPTION文は指定した例外番号(EXTYPE)の実行時エラー(例外)を起こします。1〜999の例外番号は利用者用として予約されています。
例外を起こした文がWHEN EXECPTION構文で囲まれていなければ,例外は呼び出し元に伝達されます。