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

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

詳細プロフィールへ

お問い合わせへ






2018-06-30

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







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行)のトレース

初期状態

【汎用レジスタ】
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のため、正しい答えになっていますね。



除算でのゼロの処理



除算は、割られる数が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の計算



  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)が求めるべき値。

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



それでは、トレースでデータの流れを見ていきたいと思います。


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


例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   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行)のトレース


初期状態

【汎用レジスタ】
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


10 / 4 = 2...2のため、正しい答えになっていますね。





除算の答えが正しいか確認する方法



最後に、商の確かめ算の方法を見てみたいと思います。


確かめ算は式を変形させることで、答えが正しいかを確かめる計算方法です。


除算は、商と余りが正しいかを以下の式によって確かめることができます。


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


上記式より、10 / 4 = 2...2の確かめ算を行うと、


4 × 2 + 2 = 10


したがって、10 / 4 = 2...2は、正しい式といえます。





最後に



今回はCASL2で除算を実現する方法について紹介いたしました。


CASL2の除算は、減算や筆算の式、そしてループ処理などを駆使して実現しらなければならないため、除算の原理をプログラミングを通して再確認することができるでしょう。


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














プロフィール

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

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

詳細プロフィールへ

お問い合わせへ