COMET2は、基本的なハードウェアで構成されているだけのため、「除算」を行うことができません。
COMET2上で除算を行うためには、減算やループ処理などを駆使して、ソフトウェア的に除算の機能を作る必要があります。
今回は、CASL2による「除算」の方法を紹介したていきます。
ループ処理を用いた除算
例1: 10 / 4 の計算
1 | MAIN | START |
|
2 | | LD GR1, A |
|
3 | | LD GR2, B |
|
4 | | LAD GR0, 0 | ;GR0を商の格納用に初期化
|
5 | | CPA GR1, =0 | ;割られる数と0を比較
|
6 | | JNZ NX1 | ;割られる数が0でないならNX1に移動
|
7 | | LAD GR1, 0 | ;割られる数を0に設定
|
8 | | RET |
|
9 | NX1 | CPA GR2, =0 | ;割る数と0を比較
|
10 | | JNZ NX2 | ;割られる数が0でないならNX1に移動
|
11 | | OUT ERR, ERRLEN | ;エラー文出力
|
12 | | RET |
|
13 | NX2 | CALL DIV | ;「DIV」サブルーチンの呼び出し
|
14 | | ST GR0, ANS | ;商をANS番地に格納
|
15 | | ST GR1, MOD | ;割られる数(余り)をMOD番地に格納
|
16 | | RET |
|
17 | | |
|
18 | DIV | CPA GR1, GR2 | ;割られる数と割る数を比較
|
19 | | JMI FIN | ;割られる数が割る数より小さいならFINに移動
|
20 | | SUBA GR1, GR2 | ;割られる数から割る数を引く
|
21 | | ADDA GR0, =1 | ;商に1を加算
|
22 | | JUMP DIV | ;DIV番地に移動
|
23 | FIN | RET |
|
24 | | |
|
25 | A | DC 10 | ;割られる数
|
26 | B | DC 4 | ;割る数
|
27 | ANS | DS 1 | ;商
|
28 | MOD | DS 1 | ;余り
|
29 | ERR | DC 'ERROR' | ;エラー文字列
|
30 | ERRLEN | DC 5 | ;エラー文字列の長さ
|
31 | | END |
|
シミュレータにそのままコピー&ペーストで使えます。
解説
ループ処理を用いた除算は、割る数が割られる数より大きくなるまで割られる数から1を引くことで実現しています。
除算において商は、
割られる数から1を引いた回数、余りは、
割る数が割られる数より大きくなった時点の割られる数、ということになります。
- 商 ・・・割られる数から1を引いた回数
- 余り ・・・割る数が割られる数より大きくなった時点の割られる数
したがって、ループする毎に減算することで、除算を行うことができます。
コード上の
赤字になっている5~12行は、エラー処理です。
コードのエラー処理には、割る数や割られる数が0であった場合に、どのように処理すべきかが記してあります。
それでは、トレースでデータの流れを見てみたいと思います。
例1のDIV(18~23行)のトレース
初期状態
【汎用レジスタ】
【フラグレジスタ】
~1ループ目~
18行 CPA GR1, GR2
【フラグレジスタ】
CPA命令で、GR1の値 10 と GR2の値 4 を比較します。
10 > 4 なのでフラグレジスタに変化はありません。
19行 JMI FIN
【フラグレジスタ】
SFが0なので、JMI命令で指定されたラベル「FIN」に移動せず20行を処理します。
20行 SUBA GR1, GR2
【汎用レジスタ】
【フラグレジスタ】
GR1の値が6になるため、フラグレジスタに変化はありません。
21行 ADDA GR0, =1
【汎用レジスタ】
【フラグレジスタ】
GR0の値が1になるため、フラグレジスタに変化はありません。
22行 JUMP DIV
JUMP命令でラベル「DIV」に移動し再び18行から処理します。
~2ループ目~
18行 CPA GR1, GR2
【汎用レジスタ】
【フラグレジスタ】
CPA命令で、GR1の値 6 と GR2の値 4 を比較します。
6 > 4 なのでフラグレジスタに変化はありません。
19行 JMI FIN
【フラグレジスタ】
SFが0なので、JMI命令で指定されたラベル「FIN」に移動せず20行を処理します。
20行 SUBA GR1, GR2
【汎用レジスタ】
【フラグレジスタ】
GR1の値が4になるため、フラグレジスタに変化はありません。
21行 ADDA GR0, =1
【汎用レジスタ】
【フラグレジスタ】
GR0の値が2になるため、フラグレジスタに変化はありません。
22行 JUMP DIV
JUMP命令でラベル「DIV」に移動し再び18行から処理します。
~3ループ目~
18行 CPA GR1, GR2
【汎用レジスタ】
【フラグレジスタ】
CPA命令で、GR1の値 2 と GR2の値 4 を比較します。
2 < 4 なのでフラグレジスタが変化します。
19行 JMI FIN
【フラグレジスタ】
SFが1なので、JMI命令で指定されたラベル「FIN」に移動し23行を処理します。
23行 RET
RET命令で、「DIV」サブルーチンを抜けて、呼び出し元の「MAIN」サブルーチンに戻り処理が終了します。
以上で処理が終了し、GR0が商、GR1が余りとなります。
GR0: (10)2 = 2
GR1: (10)2 = 2
10 / 4 = 2...2
10 / 4 = 2...2のため、正しい答えになっていますね。
除算でのゼロの処理
除算は、割られる数が0のときは、商と余りの両方が0になります。
5~8行は、割られる数が0の場合の処理を行っています。
0 / 5 = 0...0 ・・・割られる数が0の場合
【割られる数が0の除算】
除算は、割る数が0のときは「ゼロ除算」という、タブーに該当するため計算できません。
9~12行は、割る数が0の場合に除算を行うと、
「ゼロ除算」という除算のタブーのため、エラー文を出力し処理を終了します。
なぜ、0を割ることができないかについては、割愛します。
というかわからん^^;
5 / 0 = 解なし...余りもなし ・・・割る数が0の場合
【割る数が0の除算】
除算では、どんな数であっても0で割ることはできません。
シフト演算を用いた除算
例2: 10 / 4 の計算
1 | MAIN | START |
|
2 | | LD GR1, A |
|
3 | | LD GR2, B |
|
4 | | LAD GR0, 0 | ;GR0を商格納用に初期化
|
5 | | CPA GR1, =0 | ;割られる数と0を比較
|
6 | | JNZ NX1 | ;割られる数が0でないならにNX1移動
|
7 | | LAD GR1, 0 | ;割られる数を0に設定
|
8 | | RET |
|
9 | NX1 | CPA GR2, =0 | ;割る数と0を比較
|
10 | | JNZ NX2 | ;割られる数が0でないならにNX2移動
|
11 | | OUT ERR, ERRLEN | ;エラー文出力
|
12 | | RET |
|
13 | NX2 | CALL DIV | ;「DIV」サブルーチンの呼び出し
|
14 | | ST GR0, ANS | ;商をANS番地に格納
|
15 | | ST GR1, MOD | ;割られる数(余り)をMOD番地に格納
|
16 | | RET |
|
17 | | |
|
18 | DIV | LAD GR3, #0001 | ;GR3に商になる値を転送
|
19 | | LD GR4, GR2 | ;割る数の元の数をGR4に転送
|
20 | LP1 | CPA GR1, GR2 | ;割られる数と割る数を比較
|
21 | | JMI LP2 | ;割られる数が割る数より小さいならLP2に移動
|
22 | | SLL GR2, 1 | 割る数を2倍
|
23 | | SLL GR3, 1 | 商になる値を2倍
|
24 | | JUMP LP1 |
|
25 | LP2 | CPA GR1, GR2 | ;割られる数と割る数を比較
|
26 | | JMI NX3 | ;割られる数が割る数より小さいならNX3に移動
|
27 | | ADDA GR0, GR3 | ;商になる値を商に加算
|
28 | | SUBA GR1, GR2 | ;割られる数から割る数を減算
|
29 | NX3 | SRL GR2, 1 | ;割る数を1/2倍
|
30 | | SRL GR3, 1 | ;商になる値を1/2倍
|
31 | | CPA GR1, GR4 | ;割られる数と割る数の元の数を比較
|
32 | | JMI FIN | ;割られる数が割る数の元の数より小さいならFINに移動
|
33 | | JUMP LP2 |
|
34 | FIN | RET |
|
35 | | |
|
36 | A | DC 10 | ;割られる数
|
37 | B | DC 4 | ;割る数
|
38 | ANS | DS 1 | ;商
|
39 | MOD | DS 1 | ;余り
|
40 | ERR | DC 'ERROR' | ;エラー文字列
|
41 | ERRLEN | DC 5 | ;エラー文字列の長さ
|
42 | | END |
|
シミュレータにそのままコピー&ペーストで使えます。
解説
シフト演算を用いた除算は、2進数の除算の筆算に基づいて組まれています。
このプログラムは、ループを用いた除算よりも行数は多いですが高速に動作します。
例として2進数の除算を筆算で行う方法を以下に示します。
10 / 4の計算
- 先ず、割られる数(10)と割る数(4)を2進数にして考える。
- 筆算の式にする。
- 割る数(4)にどのくらいの数を掛ければ、割られる数(10)と同じか最も近い数になるかを考える。
例えば...
割る数(4)に2を掛けると8になるので、「答え」のところに2を書き込む。
割る数(4)に3を掛けると12になってしまい、割られる数(10)を超えてしまうので、2が商。
- 割られる数(10)から8を減算し、余り(2)が導き出される。
- 余り(2)は、割る数(4)より小さいので、商(2)と余り(2)が求めるべき値。
余り < 割る数 になったら計算を終了。
それでは、トレースでデータの流れを見ていきたいと思います。
以下のデバッグは
非常に長いものですから、興味のある方は見てみてください。
例2のDIV内(18~19行)のトレース
初期状態
【汎用レジスタ】
GR0 | 0 |
GR1 | 1010 |
GR2 | 100 |
GR3 | ? |
GR4 | ? |
【フラグレジスタ】
18行 LAD GR3, #0001
【汎用レジスタ】
GR0 | 0 |
GR1 | 1010 |
GR2 | 100 |
GR3 | 1 |
GR4 | ? |
【フラグレジスタ】
19行 LD GR4, GR2
【汎用レジスタ】
GR0 | 0 |
GR1 | 1010 |
GR2 | 100 |
GR3 | 1 |
GR4 | 100 |
【フラグレジスタ】
例2のDIV内(20~24行)のトレース
~1ループ目~
20行 CPA GR1, GR2
【汎用レジスタ】
GR0 | 0 |
GR1 | 1010 |
GR2 | 100 |
GR3 | 1 |
GR4 | 100 |
【フラグレジスタ】
CPA命令で、GR1の値 10 と GR2の値 4 を比較します。
10 > 4 なのでフラグレジスタに変化はありません。
21行 JMI LP2
【フラグレジスタ】
SFが0なので、JMI命令で指定されたラベル「LP2」に移動せず22行を処理します。
22行 SLL GR2, 1
【汎用レジスタ】
GR0 | 0 |
GR1 | 1010 |
GR2 | 1000 |
GR3 | 1 |
GR4 | 100 |
【フラグレジスタ】
GR2の値が8になるため、フラグレジスタに変化はありません。
23行 SLL GR3, 1
【汎用レジスタ】
GR0 | 0 |
GR1 | 1010 |
GR2 | 1000 |
GR3 | 10 |
GR4 | 100 |
【フラグレジスタ】
GR3の値が2になるため、フラグレジスタに変化はありません。
24行 JUMP LP1
JUMP命令でラベル「LP1」に移動し再び20行から処理します。
~2ループ目~
20行 CPA GR1, GR2
【汎用レジスタ】
GR0 | 0 |
GR1 | 1010 |
GR2 | 1000 |
GR3 | 10 |
GR4 | 100 |
【フラグレジスタ】
CPA命令で、GR1の値 10 と GR2の値 8 を比較します。
10 > 8 なのでフラグレジスタに変化はありません。
21行 JMI LP2
【フラグレジスタ】
SFが0なので、JMI命令で指定されたラベル「LP2」に移動せず22行を処理します。
22行 SLL GR2, 1
【汎用レジスタ】
GR0 | 0 |
GR1 | 1010 |
GR2 | 10000 |
GR3 | 10 |
GR4 | 100 |
【フラグレジスタ】
GR2の値が16になるため、フラグレジスタに変化はありません。
23行 SLL GR3, 1
【汎用レジスタ】
GR0 | 0 |
GR1 | 1010 |
GR2 | 10000 |
GR3 | 100 |
GR4 | 100 |
【フラグレジスタ】
GR3の値が4になるため、フラグレジスタに変化はありません。
24行 JUMP LP1
JUMP命令でラベル「LP1」に移動し再び20行から処理します。
~3ループ目~
20行 CPA GR1, GR2
【汎用レジスタ】
GR0 | 0 |
GR1 | 1010 |
GR2 | 10000 |
GR3 | 100 |
GR4 | 100 |
【フラグレジスタ】
CPA命令で、GR1の値 10 と GR2の値 16 を比較します。
10 < 16 なのでフラグレジスタが変化します。
21行 JMI LP2
【フラグレジスタ】
SFが1なので、JMI命令で指定されたラベル「LP2」に移動し次の25行を処理します。
例2のDIV内(25~34行)のトレース
~1ループ目~
25行 CPA GR1, GR2
【汎用レジスタ】
GR0 | 0 |
GR1 | 1010 |
GR2 | 10000 |
GR3 | 100 |
GR4 | 100 |
【フラグレジスタ】
CPA命令で、GR1の値 10 と GR2の値 16 を比較します。
10 < 16 なのでフラグレジスタが変化します。
26行 JMI NX3
【フラグレジスタ】
SFが1なので、JMI命令で指定されたラベル「NX3」に移動し29行を処理します。
29行 SRL GR2, 1
【汎用レジスタ】
GR0 | 0 |
GR1 | 1010 |
GR2 | 1000 |
GR3 | 100 |
GR4 | 100 |
【フラグレジスタ】
GR2の値が8になるため、フラグレジスタに変化はありません。
30行 SRL GR3, 1
【汎用レジスタ】
GR0 | 0 |
GR1 | 1010 |
GR2 | 1000 |
GR3 | 10 |
GR4 | 100 |
【フラグレジスタ】
GR3の値が2になるため、フラグレジスタに変化はありません。
31行 CPA GR1, GR4
【汎用レジスタ】
GR0 | 0 |
GR1 | 1010 |
GR2 | 1000 |
GR3 | 10 |
GR4 | 100 |
【フラグレジスタ】
CPA命令で、GR1の値 10 と GR4の値 4 を比較します。
10 > 4 なのでフラグレジスタに変化はありません。
32行 JMI FIN
【フラグレジスタ】
SFが0なので、JMI命令で指定されたラベル「FIN」に移動せず33行を処理します。
33行 JUMP LP2
JUMP命令で指定されたラベル「LP2」に移動し再び25行から処理します。
~2ループ目~
25行 CPA GR1, GR2
【汎用レジスタ】
GR0 | 0 |
GR1 | 1010 |
GR2 | 1000 |
GR3 | 10 |
GR4 | 100 |
【フラグレジスタ】
CPA命令で、GR1の値 10 と GR2の値 8 を比較します。
10 > 8 なのでフラグレジスタに変化はありません。
26行 JMI NX3
【フラグレジスタ】
SFが0なので、JMI命令で指定されたラベル「NX3」に移動せず27行を処理します。
27行 ADDA GR0, GR3
【汎用レジスタ】
GR0 | 10 |
GR1 | 1010 |
GR2 | 1000 |
GR3 | 10 |
GR4 | 100 |
【フラグレジスタ】
GR0の値が2になるため、フラグレジスタに変化はありません。
28行 SUBA GR1, GR2
【汎用レジスタ】
GR0 | 10 |
GR1 | 10 |
GR2 | 1000 |
GR3 | 10 |
GR4 | 100 |
【フラグレジスタ】
GR1の値が2になるため、フラグレジスタに変化はありません。
29行 SRL GR2, 1
【汎用レジスタ】
GR0 | 10 |
GR1 | 10 |
GR2 | 100 |
GR3 | 10 |
GR4 | 100 |
【フラグレジスタ】
GR2の値が4になるため、フラグレジスタに変化はありません。
30行 SRL GR3, 1
【汎用レジスタ】
GR0 | 10 |
GR1 | 10 |
GR2 | 100 |
GR3 | 100 |
GR4 | 100 |
【フラグレジスタ】
GR3の値が4になるため、フラグレジスタに変化はありません。
31行 CPA GR1, GR4
【汎用レジスタ】
GR0 | 10 |
GR1 | 10 |
GR2 | 100 |
GR3 | 100 |
GR4 | 100 |
【フラグレジスタ】
CPA命令で、GR1の値 2 と GR4の値 4 を比較します。
2 < 4 なのでフラグレジスタが変化します。
32行 JMI FIN
【フラグレジスタ】
SFが1なので、JMI命令で指定されたラベル「FIN」に移動し34行を処理します。
34行 RET
RET命令で、「DIV」サブルーチンを抜けて、呼び出し元の「MAIN」サブルーチンに戻り処理が終了します。
以上で処理が終了し、GR0が商、GR1が余りとなります。
GR0: (10)2 = 2
GR1: (10)2 = 2
10 / 4 = 2...2
10 / 4 は、2余り2のため、正しい答えになっていますね。
再起処理を用いた除算
例3: 10 / 4 の計算
1 | MAIN | START |
|
2 | | LD GR1, A |
|
3 | | LD GR2, B |
|
4 | | LAD GR0, 0 | ;GR0を商格納用に初期化
|
5 | | CPA GR1, =0 | ;割られる数と0を比較
|
6 | | JNZ NX1 | ;割られる数が0でないならにNX1移動
|
7 | | LAD GR1, 0 | ;割られる数を0に設定
|
8 | | RET |
|
9 | NX1 | CPA GR2, =0 | ;割る数と0を比較
|
10 | | JNZ NX2 | ;割られる数が0でないならにNX1移動
|
11 | | OUT ERR, ERRLEN | ;エラー文出力
|
12 | | RET |
|
13 | NX2 | CALL DIV | ;DIVサブルーチンの行に移動
|
14 | | ST GR0, ANS | ;商をANS番地に格納
|
15 | | ST GR1, MOD | ;割られる数(余り)をMOD番地に格納
|
16 | | RET |
|
17 | | |
|
18 | DIV | CPA GR1, GR2 | ;割られる数と割る数を比較
|
19 | | JMI FIN | ;割られる数が割る数よる小さいならFINに移動
|
20 | | SUBA GR1, GR2 | ;割られる数から割る数を減算
|
21 | | ADDA GR0, =1 | ;商に1を加算
|
22 | | CALL DIV | ;再起処理
|
23 | FIN | RET |
|
24 | | |
|
25 | A | DC 10 | ;割られる数
|
26 | B | DC 4 | ;割る数
|
27 | ANS | DS 1 | ;答え
|
28 | MOD | DS 1 | ;余り
|
29 | ERR | DC 'ERROR' | ;エラー文
|
30 | ERRLEN | DC 5 | ;エラー文字列
|
31 | | END |
|
シミュレータにそのままコピー&ペーストで使えます。
解説
再起処理を用いた除算は、ループ処理を用いた除算のループが再起処理に置き換わったものです。
再起処理を用いた除算も、ループ処理を用いた除算と同じく
割る数が割られる数より大きくなるまで、割られる数から割る数を減算します。
その際、
割る数の値の分再起処理を行い、再起した回数をカウントしています。
そして、
カウントした再起の回数が商、
割る数が割られる数より大きくなったときの割られる数が余りとなります。
また、
赤字になっている5~12行のコードはエラーの処理です。
それでは、トレースで処理の流れを見てみたいと思います。
例3のDIV(19~24行)のトレース
初期状態
【汎用レジスタ】
【フラグレジスタ】
19行 CPA GR1, GR2
【汎用レジスタ】
【フラグレジスタ】
CPA命令で、GR1の値 10 と GR2の値 4 を比較します。
10 > 4 なのでフラグレジスタに変化はありません。
20行 JMI FIN
【フラグレジスタ】
SFが0なので、JMI命令で指定されたラベル「FIN」に移動せず21行を処理します。
21行 SUBA GR1, GR2
【汎用レジスタ】
【フラグレジスタ】
GR1の値が6になるため、フラグレジスタに変化はありません。
22行 ADDA GR0, =1
【汎用レジスタ】
【フラグレジスタ】
GR0の値が1になるため、フラグレジスタに変化はありません。
23行 CALL DIV
CALL命令で、「DIV」サブルーチンを呼び出し、再起処理により再び19行を処理します。
~再起1回目~
19行 CPA GR1, GR2
【汎用レジスタ】
【フラグレジスタ】
CPA命令で、GR1の値
6 と GR2の値
4 を比較します。
6 >
4 なのでフラグレジスタに変化はありません。
20行 JMI FIN
【フラグレジスタ】
SFが
0なので、JMI命令で指定されたラベル「FIN」に移動せず21行を処理します。
21行 SUBA GR1, GR2
【汎用レジスタ】
【フラグレジスタ】
GR1の値が
2になるため、フラグレジスタに変化はありません。
22行 ADDA GR0, =1
【汎用レジスタ】
【フラグレジスタ】
GR0の値が
2になるため、フラグレジスタに変化はありません。
23行 CALL DIV
CALL命令で、「DIV」サブルーチンを呼び出し、再起処理により再び19行を処理します。
~再起2回目~
19行 CPA GR1, GR2
【汎用レジスタ】
【フラグレジスタ】
CPA命令で、GR1の値
2 と GR2の値
4 を比較します。
2 <
4 なのでフラグレジスタが変化します。
20行 JMI FIN
【フラグレジスタ】
SFが
1なので、JMI命令で指定されたラベル「FIN」に移動し24行を処理します。
24行 RET
RET命令で、「DIV」サブルーチンを抜けて、呼び出し元の「DIV」サブルーチンに戻り24行を処理します。
~再起1回目に戻る~
24行 RET
RET命令で、「DIV」サブルーチンを抜けて、呼び出し元の「DIV」サブルーチンに戻り24行を処理します。
~再起を抜ける~
24行 RET
RET命令で、「DIV」サブルーチンを抜けて、呼び出し元の「MAIN」サブルーチンに戻り処理を終了します。
以上で処理が終了し、GR0が答えで、GR1が余りになります。
GR0: (10)2 = 2
GR1: (10)2 = 2
10 / 4 = 2...2
10 / 4 = 2...2のため、正しい答えになっていますね。
除算の答えが正しいか確認する方法
最後に、商の確かめ算の方法を見てみたいと思います。
確かめ算は式を変形させることで、答えが正しいかを確かめる計算方法です。
除算は、商と余りが正しいかを以下の式によって確かめることができます。
割られる数 = 割る数 × 答え + 余り
上記式より、
10 / 4 = 2...2の確かめ算を行うと、
4 × 2 + 2 = 10
したがって、
10 / 4 = 2...2は、正しい式といえます。
最後に
今回はCASL2で除算を実現する方法について紹介いたしました。
CASL2の除算は、減算や筆算の式、そしてループ処理などを駆使して実現しらなければならないため、除算の原理をプログラミングを通して再確認することができるでしょう。
それでは、続きはまた次回にご期待を!