07.四則演算の基本と条件分岐

四則演算はS/370命令セット内に於ける最も基本的な命令群の1つです。ハーフワード(半語:2バイト)とフルワード(全語:4バイト)の2種類の整数値を取り扱うことができます。ただし、乗除算だけは少し複雑です。また、演算命令の結果として条件コードというものがPSWにセットされます。条件コードを具体的に知ってもらうために、分岐命令も追加で解説します。

加算・減算命令(Add、Subtract)

AR、A、SR、S

フルワードの符号付き整数の加算と減算を行います。マイナスの数の加算は減算、マイナスの数の減算は加算と同じです。演算した結果は -2,147,483,648~0~2,147,483,647 の範囲に収まらなければなりません。
ARとSR命令はレジスター間での加減算です。r2で示されるレジスターの内容がr1で示されるレジスターの内容に加算または減算され、結果はr1レジスターに格納されます。r2レジスターの内容は変わりません。
AとS命令はレジスターとメモリー間での加減算です。第2オペランドで示される主記憶のフルワードの内容がr1で示されるレジスターの内容に加算または減算され、結果はr1レジスターに格納されます。主記憶の内容は変わりません。

演算の結果、PSWに条件コードがセットされます。条件コードによって分岐し、実行後の処理を変えることができます。

0 … 和または差はゼロ
1 … 和または差は−
2 … 和または差は+
3 … オーバーフロー

AH、SH

ハーフワードの符号付き整数の加算と減算を行います。マイナスの数の扱いと演算結果の範囲はフルワード加減算と同じですが、加減算する値(第2オペランド)の範囲が-32,768 ~0~32,767 に変わります。
AHとSR命令はレジスターとメモリー間での加減算です。第2オペランドで示される主記憶のハーフワードの内容がr1で示されるレジスターの内容に加算または減算され、結果はr1レジスターに格納されます。主記憶の内容は変わりません。なお、RR形式のハーフワード加減算命令(AHR、SHR)はありません。

演算の結果、PSWに条件コードがセットされます。

0 … 和または差はゼロ
1 … 和または差は−
2 … 和または差は+
3 … オーバーフロー

※演算結果が格納されるのはレジスターなので、結果についてはフルワードで取り扱われます。そのためレジスターに32760が入っていて、メモリーの内容が8の時、加算した結果が+32768になってもオーバーフローにはなりません。

ALR、AL、SLR、SL

フルワードの符号なし整数の加算と減算を行います。演算した結果は 0~4,294,967,295(xFFFFFFFF)の範囲に収まらなければなりません。
ALRとSLR命令はレジスター間での加減算です。r2で示されるレジスターの内容がr1で示されるレジスターの内容に加算または減算され、結果はr1レジスターに格納されます。r2レジスターの内容は変わりません。
ALとSL命令はレジスターとメモリー間での加減算です。第2オペランドで示される主記憶のフルワードの内容がr1で示されるレジスターの内容に加算または減算され、結果はr1レジスターに格納されます。主記憶の内容は変わりません。

演算の結果、PSWに条件コードがセットされます。AL[R]とSL[R]ではセットされる条件コードが異なるので、詳細はCPU命令のリファレンスを参照して下さい。筆者自身はこれらの命令をよく使いましたが、実行後に条件コードを意識することは殆どなかったと記憶しています。SLR命令はレジスターを0クリアーする場合にもよく使われます。SLR 1,1 と記せばレジスター1を0クリアーします。SR命令でも同じですが、自分から自分を引けば0になります。

乗算・除算命令(Multiply、Divide)

MR、M

フルワードの符号付き整数の乗算を行います。第1オペランドはレジスターを指定しますが、単独のレジスターではなく偶数番号レジスターと奇数番号レジスターをペアにして使い、必ず偶数側のレジスター番号を指定します。0+1、2+3、…、14+15などとなり、オペランドではそれぞれ0、2、14を記します。
MR命令はレジスター間での乗算です。r2で示されるレジスターの内容がr1+1で示される奇数番号側レジスターの内容に掛けられて、結果はr1およびr1+1レジスターに格納されます。r2レジスターの内容は変わりません。被乗数はr1+1レジスターから取り出され、r1レジスターの内容は参照されません。積は64ビットで扱われ、先頭の32ビットが偶数番号のr1レジスターに、残りの32ビットが奇数番号のr1+1レジスターに格納されます。
M命令はレジスターとメモリー間での乗算です。第2オペランドで示される主記憶のフルワードの内容がr1+1で示される奇数番号側レジスターの内容に掛けられて、結果はr1およびr1+1レジスターに格納されます。主記憶の内容は変わりません。

乗算は加減算と異なり条件コードはセットされません。また、被乗数は奇数番号側レジスターのみが使われるので、命令実行前に偶数番号側レジスターをクリアーしておく必要はありません。あくまでも実行結果だけが64ビットに拡張されます。

MH

ハーフワードの符号付き整数の乗算を行います。M[R]命令と異なり、第1オペランドは任意の番号のレジスター、第2オペランドはハーフワードの主記憶を示します。MH命令はレジスターとメモリー間での乗算です。第2オペランドで示される主記憶のハーフワードの内容がr1で示されるレジスターの内容に掛けられて、結果はr1レジスターに格納されます。主記憶の内容は変わりません。

条件コードはセットされません。積は最大で46または47ビット(負数の時)となりますが下位32ビットが第1オペランドに入れられます。その左側ビットは捨てられます。条件コードは設定されないのでオーバーフローはプログラム自らがテストしなければなりません。レジスターをペアで使用する必要がないため、積が2^31未満で、乗数が32767以下で済むなら乗算にはMH命令を使う方が簡単です。

DR、D

フルワードの符号付き整数の除算を行います。第1オペランドはレジスターを指定しますが、単独のレジスターではなく偶数番号レジスターと奇数番号レジスターをペアにして使い、必ず偶数側のレジスター番号を指定します。0+1、2+3、…、14+15などとなり、オペランドではそれぞれ0、2、14を記します。
DR命令はレジスター間での除算です。r2で示されるレジスターの内容でr1およびr1+1で示されるペアーのレジスターの内容が割られ、結果の商がr1+1レジスター、剰余がr1レジスターに格納されます。r2レジスターの内容は変わりません。
D命令はレジスターとメモリー間での除算です。第2オペランドで示される主記憶のフルワードの内容でr1およびr1+1で示されるペアーのレジスターの内容が割られ、結果の商がr1+1レジスター、剰余がr1レジスターに格納されます。主記憶の内容は変わりません。
除数(第2オペランド)は32ビットですが、被除数(第1オペランド)は64ビットになる点に注意して下さい。被除数の先頭の32ビットは偶数番号レジスターに、残りの32ビットは奇数番号レジスターに格納されていなければなりません。被除数がフルワード32ビットの値であれば偶数番号レジスターは0クリアーしておかなければなりません。この点が乗算と異なるので注意が必要です。

条件コードはセットされません。商は奇数番号、余りは偶数番号レジスターに格納されます。

小数点が絡む計算は整数演算命令ではできませんが、だからと言って浮動小数点のような面倒な演算命令を使うのも考えものです。小数点第1位までなら10を、第2位までなら100を掛けて整数にしてしまい、表示上だけ辻褄を合わせる方法もあります。
消費税なら0.05を掛けるのではなく、5を掛けてから100で割る。百分率なら100を掛けてから割るといった計算で行えば、事務系や制御系の処理なら整数演算で十分対応できるはずです。

答えの324は、32.4%と出力時に編集すればよい。

2進数と10進数の変換

整数は2進整数なので、そのままでは人間が見てわかるアウトプットには使えません。まずは10進数に変えて、さらに数字(文字)に変える必要があります。

CVD(Convert to Decimal)

CVD命令はレジスター内の符号付き2進整数をパック10進数に変換します。
r1で示されるレジスター内容がパック10進数に変換され、第2オペランドで示される主記憶のダブルワードの領域に書き込まれます。桁数に関係なく8バイトが使用されます。

符号は+の時はC、−の時はDです。

UNPK(Unpack)

UNPK命令はパック10進数をゾーン10進数(数字)に変換します。
第2オペランドで示される主記憶のパック10進数がゾーン10進数に変換され、第1オペランドで示される主記憶に書き込まれます。領域の長さは第1、第2オペランドそれぞれに指定し、最大16バイトです。変換処理は右から左(下位桁から上位桁)に向かって行われ、第1オペランド側が短ければ溢れる桁は無視され、第1オペランド側が長ければ余った上位桁に0がセットされます。最下位桁のゾーン部(上位4ビット)はパック10進数の符号がそのままセットされる(CnまたはDn)ので、そのままではアウトプットしても正しい数字文字になりません。そのためOIと言う命令を使ってゾーン部をxFにすることが行われます。第1オペランドで指定したゾーン10進数領域の最終バイトを文字’0’でORします。OI命令についてはここでは解説しませんので、上記のサンプルコーディングを参考にしてください。L’TAXの表記はラベル名TAXで定義した領域の長さをアセンブラーに自動計算させる時の書き方です。

負数の場合、単純にC’0’でORすると+か−か区別できないので、元の整数が入っているレジスターをLTRして正負を判定し、最終桁の後ろに正負記号を付けるような工夫をします。負数も扱うなら単なるUnpack処理ではなく、編集機能を持った別の命令がありますがここでは解説しません。

出力と言うことで2進数から10進数への変換について紹介しましたが、入力データであれば10進数から2進数への変換を行うことになります。UNPKの逆はPACK、CVDの逆にはCVBという命令があります。詳細は命令セットのリファレンス・マニュアルを参照してください。

分岐命令(Branch on Condition)

加減算命令の解説で出てきましたが、命令には実行した結果が「条件コード」として設定されるものがあります。条件コードがどうなるかを具体的に覚えるには、PSWを見るより後続で実行する命令を分岐させる方がわかりやすいです。

BCR、BC

S/370命令セットにおける分岐命令は、このBC[R]命令です。第1オペランド(m1)で示されるマスク値とPSWの条件コードが比較され、分岐条件が成立すると第2オペランドで示されるアドレスへ分岐します。分岐先アドレスをレジスターに格納する場合はBCR、ラベル名など主記憶アドレスを示す場合はBC命令を使います。分岐先アドレスが固定されているかどうかで使い分けます。
以下、条件コードをCCと表記します。m1マスクは4ビットで、左からCC=0、CC=1、CC=2、CC=3に対応します。

CC=0で分岐 → BC[R] 8,d2(x2,b2)|[r2]
CC=1で分岐 → BC[R] 4,d2(x2,b2)|[r2]
CC=2で分岐 → BC[R] 2,d2(x2,b2)|[r2]
CC=3で分岐 → BC[R] 1,d2(x2,b2)|[r2] です。

CC=0または2の時に分岐するなら、BC[R] 10,…、常に分岐(無条件分岐)ならBCR 15,…です。(10は8+2、15は8+4+2+1)分岐したい条件に合わせて、4ビットのマスク値を計算してオペランドm1として指定します。

10進数でのマスク値表記はわかりにくい場合、下記のような書き方もできます。

条件を示すビットは左から、CC=0,CC=1,CC=2,CC=3、に対応しますから B’xxxx’のようにビットの0/1で表すと、特に複数の条件に対応させる場合などではだいぶわかりやすくなります。しかし、これでも各命令の条件コードの値と意味がわかっていないとプログラムが書けませんし追えません。やはり不便なことには変わりません。そこで拡張簡略命令という便利な書き方がアセンブラーに用意されています。

足し算の結果で処理を変えるような時、このように書ければわかりやすいです。一目瞭然です。10進数やビットパターンの書き方では間違ってもわかりにくいですが、こちらなら直感で気づけます。
拡張簡略命令はCPU命令ではなくアセンブラーの機能です。BZ と書けば BC  8 として翻訳してくれるわけです。オブジェクトだけが変換されアセンブリー・リストには書いた BZ のままで表示されますから、プログラムも追いやすくなります。CPU命令は機能によってカテゴライズされますが、同じカテゴリーだと大体同じ条件コードが設定されます。アセンブラーは命令を演算系、比較系、ビットテストに分けて条件コードの意味に沿った簡略命令を用意しています。

演算命令の後に使える簡略表記の分岐命令
命令コード 意味 対応する機械命令
BZ[R] 0の時、分岐 BC[R] 8
BNZ[R] 0でない時、分岐 BC[R] 7
BM[R] 負の時、分岐 BC[R] 4
BNM[R] 負でない時、分岐 BC[R] 11
BP[R] 正の時、分岐 BC[R] 2
BNP[R] 正でない時、分岐 BC[R] 13
BO[R] 桁あふれの時、分岐 BC[R] 1
BNO[R] 桁あふれでない時、分岐 BC[R] 14

比較命令(A:B)の後に使える簡略表記の分岐命令
命令コード 意味 対応する機械命令
BE[R] AとBが等しい時、分岐 BC[R] 8
BNE[R] AとBが等しくない時、分岐 BC[R] 7
BL[R] Aが小さい時、分岐 BC[R] 4
BNL[R] Aが小さくない時、分岐 BC[R] 11
BH[R] Aが大きい時、分岐 BC[R] 2
BNH[R] Aが大きくない時、分岐 BC[R] 13

比較命令の結果に於いては、「Aが小さい」の反対は「Aが大きい」ではありません。あくまでも「Aは小さくない」です。未満・以下・以上を取りたいときは、どれを使えばいいかよく考えて下さい。意外にこんなところでバグを出します。

ビットテスト命令の後に使える簡略表記の分岐命令
命令コード 意味 対応する機械命令
BZ[R] 0の時、分岐 BC[R] 8
BNZ[R] 0でない時、分岐 BC[R] 7
BM[R] 0と1が混合の時、分岐 BC[R] 4
BNM[R] 0と1が混合でない時、分岐 BC[R] 11
BO[R] 1の時、分岐 BC[R] 1
BNO[R] 1でない時、分岐 BC[R] 14

BMのMはMinusあるいはMixのM、BOのOはOverflowあるいはOnのO、上手くできてます。条件コードの値も同じになるように設計されています。

その他の分岐命令
命令コード 意味 対応する機械命令
B[R] 無条件分岐 BC[R] 15
NOP[R] 何もしない(分岐しない)
ノーオペレーション
BC[R] 0

無条件分岐はわかるが、NOP(ノーオペレーション)って何の意味があるの?って思うかも知れません。アプリケーション・プログラムで使われることは殆どありませんが、制御系プログラムではタイミング合わせやプログラム修正をソース・コードではなくバイナリー・コード上で行う為の予約域確保などに用いられます。

演算命令の結果でセットされる条件コードを実際の動きで確認したい時は、対応する分岐命令を直後に置いて、その命令実行後に指定した飛び先に分岐するか否かを試せばいいでしょう。