ニッシー
1990年生まれ
血液型 O型

こんにちは。ITブログアルケーナム管理人のニッシーです。

詳細プロフィールへ

お問い合わせへ






2018-06-30

アセンブリ言語CASL2での除算のやり方




今回は、「除算」、割り算のやり方を見てみたいと思います。


以下のプログラムは、少し長いと感じるかもしれませんが、肝心な部分は「DIV」から始まるサブルーチンプログラム(割り算プログラムの本体)です。


どういう理屈で割り算を実現するのかを理解できればいいため、このDIVサブルーチン以外の行については余り深く考えないようにしてください。










ループ処理を用いた除算



例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 
14   ST GR0, 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 
22   JUMP 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を引いた回数


余り:割る数が割られる数より大きくなった時点の割られる数


となります。


プログラムの行数が長いように見えますが、「DIV」ラベルから始まるプログラム(18~23行)が割り算のアルゴリズムの本体なので、このプログラムの動きにフォーカスしてみてください。


また、赤字になっている5~12行のコードはエラーの処理です。



エラー処理中の


5~8行は、割られる数が0の場合割る数が何であれ答えと余りの両方が0になるので、その処理を行っています。


0 / 5 = 0...0 ・・・割られる数が0の場合






9~12行は、割る数が0の場合「ゼロ除算」という除算のタブーですので、エラー文が出力され処理は終了します。


5 / 0 = 解なし...余りもなし ・・・割る数が0の場合





割り算においてどんな数であれ0で割ることはできません。



それでは、机上デバッグもどきでデータの流れを見てみたいと思います。



例1のDIV(18~23行)のトレース

初期状態

【汎用レジスタ】
GR00
GR11010
GR2100

【フラグレジスタ】
ZFSFOF
000



~1ループ目~

18行 CPA GR1, GR2

GR00
GR11010
GR2100

【フラグレジスタ】
ZFSFOF
000

CPA命令で、GR1の値 10 と GR2の値 4 を比較します。
10 > 4 なのでフラグレジスタに変化はありません。



19行 JMI FIN

【フラグレジスタ】
ZFSFOF
000

SFが0なので、JMI命令で指定されたラベル「FIN」に移動せず20行を処理します。



20行 SUBA GR1, GR2

【汎用レジスタ】
GR00
GR1110
GR2100

【フラグレジスタ】
ZFSFOF
000

GR1の値が6になるため、フラグレジスタに変化はありません。



21行 ADDA GR0, =1

【汎用レジスタ】
GR01
GR1110
GR2100

【フラグレジスタ】
ZFSFOF
000

GR0の値が1になるため、フラグレジスタに変化はありません。



22行 JUMP DIV

JUMP命令でラベル「DIV」に移動し再び18行から処理します。



~2ループ目~

18行 CPA GR1, GR2

【汎用レジスタ】
GR01
GR1110
GR2100

【フラグレジスタ】
ZFSFOF
000

CPA命令で、GR1の値 6 と GR2の値 4 を比較します。
6 > 4 なのでフラグレジスタに変化はありません。



19行 JMI FIN

【フラグレジスタ】
ZFSFOF
000

SFが0なので、JMI命令で指定されたラベル「FIN」に移動せず20行を処理します。



20行 SUBA GR1, GR2

【汎用レジスタ】
GR01
GR110
GR2100

【フラグレジスタ】
ZFSFOF
000

GR1の値が4になるため、フラグレジスタに変化はありません。



21行 ADDA GR0, =1

【汎用レジスタ】
GR010
GR110
GR2100

【フラグレジスタ】
ZFSFOF
000

GR0の値が2になるため、フラグレジスタに変化はありません。



22行 JUMP DIV

JUMP命令でラベル「DIV」に移動し再び18行から処理します。



~3ループ目~

18行 CPA GR1, GR2

【汎用レジスタ】
GR010
GR110
GR2100

【フラグレジスタ】
ZFSFOF
010

CPA命令で、GR1の値 2 と GR2の値 4 を比較します。
2 < 4 なのでフラグレジスタが変化します。



19行 JMI FIN

【フラグレジスタ】
ZFSFOF
010

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で、正しい答えになっていますね。





シフト演算を用いた除算



例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 
14   ST GR0, 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 
23   SLL GR3, 1 
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 
30   SRL GR3, 1 
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進数の割り算の筆算に基づいて組まれています。



10 / 4の計算



  1. 先ず、割られる数(10)と割る数(4)を2進数にして考える。

  2. 筆算の式にする。

  3. 割る数(4)にどのくらいの数を掛ければ、割られる数(10)と同じか最も近い数になるかを考える。

    例えば...

    割る数(4)に2を掛けると8になるので、「答え」のところに2を書き込む。

    割る数(4)に3を掛けると12になってしまい、割られる数(10)を超えてしまうので、2が答え。

  4. 割られる数(10)から8を引き算し、余り(2)が導き出される。

  5. 余り(2)は、割る数(4)より小さいので、答え(2)と余り(2)が求めるべき値。

    余り < 割る数 になったら計算を終了。



こちらのコードも、


プログラムの行数が長いように見えますが、「DIV」ラベルから始まるプログラム(18~34行)が割り算のアルゴリズムの本体なので、このプログラムの動きにフォーカスしてみてください。


また、赤字になっている5~12行のコードはエラーの処理です。


このプログラムでも、割られる数が0とゼロ除算の処理が記述されていますが、ループを用いた除算の場合と同様なので割愛します。



机上デバッグもどきでデータの流れを見ていきたいと思います。


以下のデバッグは非常に長いです。興味のある方は見てみてください。


また、シミュレータをお持ちの方はそちらで動作を確認してみてください。



例2のDIV内(18~19行)のトレース


初期状態

【汎用レジスタ】
GR00
GR11010
GR2100
GR3?
GR4?

【フラグレジスタ】
ZFSFOF
000



18行 LAD GR3, #0001

【汎用レジスタ】
GR00
GR11010
GR2100
GR31
GR4?

【フラグレジスタ】
ZFSFOF
000




19行 LD GR4, GR2

【汎用レジスタ】
GR00
GR11010
GR2100
GR31
GR4100

【フラグレジスタ】
ZFSFOF
000




例2のDIV内(20~24行)のトレース



~1ループ目~

20行 CPA GR1, GR2

【汎用レジスタ】
GR00
GR11010
GR2100
GR31
GR4100

【フラグレジスタ】
ZFSFOF
000

CPA命令で、GR1の値 10 と GR2の値 4 を比較します。
10 > 4 なのでフラグレジスタに変化はありません。



21行 JMI LP2

【フラグレジスタ】
ZFSFOF
000

SFが0なので、JMI命令で指定されたラベル「LP2」に移動せず22行を処理します。



22行 SLL GR2, 1

【汎用レジスタ】
GR00
GR11010
GR21000
GR31
GR4100

【フラグレジスタ】
ZFSFOF
000

GR2の値が8になるため、フラグレジスタに変化はありません。



23行 SLL GR3, 1

【汎用レジスタ】
GR00
GR11010
GR21000
GR310
GR4100

【フラグレジスタ】
ZFSFOF
000

GR3の値が2になるため、フラグレジスタに変化はありません。



24行 JUMP LP1

JUMP命令でラベル「LP1」に移動し再び20行から処理します。



~2ループ目~

20行 CPA GR1, GR2

【汎用レジスタ】
GR00
GR11010
GR21000
GR310
GR4100

【フラグレジスタ】
ZFSFOF
000

CPA命令で、GR1の値 10 と GR2の値 8 を比較します。
10 > 8 なのでフラグレジスタに変化はありません。



21行 JMI LP2

【フラグレジスタ】
ZFSFOF
000

SFが0なので、JMI命令で指定されたラベル「LP2」に移動せず22行を処理します。



22行 SLL GR2, 1

【汎用レジスタ】
GR00
GR11010
GR210000
GR310
GR4100

【フラグレジスタ】
ZFSFOF
000

GR2の値が16になるため、フラグレジスタに変化はありません。



23行 SLL GR3, 1

【汎用レジスタ】
GR00
GR11010
GR210000
GR3100
GR4100

【フラグレジスタ】
ZFSFOF
000

GR3の値が4になるため、フラグレジスタに変化はありません。



24行 JUMP LP1

JUMP命令でラベル「LP1」に移動し再び20行から処理します。



~3ループ目~

20行 CPA GR1, GR2

【汎用レジスタ】
GR00
GR11010
GR210000
GR3100
GR4100

【フラグレジスタ】
ZFSFOF
010

CPA命令で、GR1の値 10 と GR2の値 16 を比較します。
10 < 16 なのでフラグレジスタが変化します。



21行 JMI LP2

【フラグレジスタ】
ZFSFOF
010

SFが1なので、JMI命令で指定されたラベル「LP2」に移動し次の25行を処理します。




例2のDIV内(25~34行)のトレース




~1ループ目~

25行 CPA GR1, GR2

【汎用レジスタ】
GR00
GR11010
GR210000
GR3100
GR4100

【フラグレジスタ】
ZFSFOF
010

CPA命令で、GR1の値 10 と GR2の値 16 を比較します。
10 < 16 なのでフラグレジスタが変化します。



26行 JMI NX3

【フラグレジスタ】
ZFSFOF
010

SFが1なので、JMI命令で指定されたラベル「NX3」に移動し29行を処理します。



29行 SRL GR2, 1

【汎用レジスタ】
GR00
GR11010
GR21000
GR3100
GR4100

【フラグレジスタ】
ZFSFOF
000

GR2の値が8になるため、フラグレジスタに変化はありません。



30行 SRL GR3, 1

【汎用レジスタ】
GR00
GR11010
GR21000
GR310
GR4100

【フラグレジスタ】
ZFSFOF
000

GR3の値が2になるため、フラグレジスタに変化はありません。



31行 CPA GR1, GR4

【汎用レジスタ】
GR00
GR11010
GR21000
GR310
GR4100

【フラグレジスタ】
ZFSFOF
000

CPA命令で、GR1の値 10 と GR4の値 4 を比較します。
10 > 4 なのでフラグレジスタに変化はありません。



32行 JMI FIN

【フラグレジスタ】
ZFSFOF
000

SFが0なので、JMI命令で指定されたラベル「FIN」に移動せず33行を処理します。



33行 JUMP LP2

JUMP命令で指定されたラベル「LP2」に移動し再び25行から処理します。



~2ループ目~

25行 CPA GR1, GR2

【汎用レジスタ】
GR00
GR11010
GR21000
GR310
GR4100

【フラグレジスタ】
ZFSFOF
000

CPA命令で、GR1の値 10 と GR2の値 8 を比較します。
10 > 8 なのでフラグレジスタに変化はありません。



26行 JMI NX3

【フラグレジスタ】
ZFSFOF
000

SFが0なので、JMI命令で指定されたラベル「NX3」に移動せず27行を処理します。



27行 ADDA GR0, GR3

【汎用レジスタ】
GR010
GR11010
GR21000
GR310
GR4100

【フラグレジスタ】
ZFSFOF
000

GR0の値が2になるため、フラグレジスタに変化はありません。



28行 SUBA GR1, GR2

【汎用レジスタ】
GR010
GR110
GR21000
GR310
GR4100

【フラグレジスタ】
ZFSFOF
000

GR1の値が2になるため、フラグレジスタに変化はありません。



29行 SRL GR2, 1

【汎用レジスタ】
GR010
GR110
GR2100
GR310
GR4100

【フラグレジスタ】
ZFSFOF
000

GR2の値が4になるため、フラグレジスタに変化はありません。



30行 SRL GR3, 1

【汎用レジスタ】
GR010
GR110
GR2100
GR3100
GR4100

【フラグレジスタ】
ZFSFOF
000

GR3の値が4になるため、フラグレジスタに変化はありません。



31行 CPA GR1, GR4

【汎用レジスタ】
GR010
GR110
GR2100
GR3100
GR4100

【フラグレジスタ】
ZFSFOF
010

CPA命令で、GR1の値 2 と GR4の値 4 を比較します。
2 < 4 なのでフラグレジスタが変化します。



32行 JMI FIN

【フラグレジスタ】
ZFSFOF
010

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   LAD GR0, 0 ;GR0を答え格納用に初期化
3   LD GR1, A 
4   LD GR2, B 
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 
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 
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 


シミュレータにそのままコピー&ペーストで使えます。



解説



このプログラムは、例1のループ処理を用いた除算に似ています。


繰り返し処理が再起処理に置き換わっただけです。


先ず割る数が割られる数より大きくなるまで、割られる数から割る数を引き算します。


その際、割る数の値の分再起処理を行い、再起した回数をカウントしています。


そして、カウントした再起の回数が答えで、割る数が割られる数より大きくなった段階の割られる数が余りとなります。


また、赤字になっている5~12行のコードはエラーの処理です。





それでは、処理の流れを机上デバッグもどきで見てみたいと思います。



例3のDIV(19~24行)のトレース


初期状態

【汎用レジスタ】
GR00
GR11010
GR2100

【フラグレジスタ】
ZFSFOF
000



19行 CPA GR1, GR2

【汎用レジスタ】
GR00
GR11010
GR2100

【フラグレジスタ】
ZFSFOF
000

CPA命令で、GR1の値 10 と GR2の値 4 を比較します。
10 > 4 なのでフラグレジスタに変化はありません。



20行 JMI FIN

【フラグレジスタ】
ZFSFOF
000

SFが0なので、JMI命令で指定されたラベル「FIN」に移動せず21行を処理します。



21行 SUBA GR1, GR2

【汎用レジスタ】
GR00
GR1110
GR2100

【フラグレジスタ】
ZFSFOF
000

GR1の値が6になるため、フラグレジスタに変化はありません。



22行 ADDA GR0, =1

【汎用レジスタ】
GR01
GR1110
GR2100

【フラグレジスタ】
ZFSFOF
000

GR0の値が1になるため、フラグレジスタに変化はありません。



23行 CALL DIV

CALL命令で、「DIV」サブルーチンを呼び出し、再起処理により再び19行を処理します。




~再起1回目~

19行 CPA GR1, GR2

【汎用レジスタ】
GR01
GR1110
GR2100

【フラグレジスタ】
ZFSFOF
000

CPA命令で、GR1の値 6 と GR2の値 4 を比較します。
6 > 4 なのでフラグレジスタに変化はありません。



20行 JMI FIN

【フラグレジスタ】
ZFSFOF
000

SFが0なので、JMI命令で指定されたラベル「FIN」に移動せず21行を処理します。



21行 SUBA GR1, GR2

【汎用レジスタ】
GR01
GR110
GR2100

【フラグレジスタ】
ZFSFOF
000

GR1の値が2になるため、フラグレジスタに変化はありません。



22行 ADDA GR0, =1

【汎用レジスタ】
GR010
GR110
GR2100

【フラグレジスタ】
ZFSFOF
000

GR0の値が2になるため、フラグレジスタに変化はありません。



23行 CALL DIV

CALL命令で、「DIV」サブルーチンを呼び出し、再起処理により再び19行を処理します。




~再起2回目~

19行 CPA GR1, GR2

【汎用レジスタ】
GR010
GR110
GR2100

【フラグレジスタ】
ZFSFOF
010

CPA命令で、GR1の値 2 と GR2の値 4 を比較します。
2 < 4 なのでフラグレジスタが変化します。



20行 JMI FIN

【フラグレジスタ】
ZFSFOF
010

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
なので、正しい答えになっていますね。





割り算の答えが正しいかを確かめる



割る数 × 答え + 余り = 割られる数


の式によって、割り算の答えと余りが正しいを確かめることができます。


4 × 2 + 2 = 10


なので、やはり正しい答えになってますね。





最後に



というわけで、割り算についてでした。


CASL2の割り算は、掛け算よりも分かり難く、特に、2番目の例のシフト演算を用いた除算は、慣れるまで時間が掛かると思います。


諦めずに分かるまで考えてください。考えてさえいれば理解できる日が必ず訪れます。


それでは、続きはまた次回にご期待を!









プロフィール

ニッシー
1990年生まれ
血液型 O型

こんにちは。ITブログアルケーナム管理人のニッシーです。

詳細プロフィールへ

お問い合わせへ