- 「メインフレーム・コンピューター」で遊ぼう - http://www.arteceed.net -

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

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


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

    ----+----1----+----2----+----3----+----4----+----5----+----6----+----7--
             AR    r1,r2               レジスター←レジスター
             SR    r1,r2               レジスター←レジスター
             A     r1,d2(x2,b2)        レジスター←メモリー(フルワード)
             S     r1,d2(x2,b2)        レジスター←メモリー(フルワード)
             AH    r1,d2(x2,b2)        レジスター←メモリー(ハーフワード)
             SH    r1,d2(x2,b2)        レジスター←メモリー(ハーフワード)
             ALR   r1,r2               レジスター←レジスター
             SLR   r1,r2               レジスター←レジスター
             AL    r1,d2(x2,b2)        レジスター←メモリー(フルワード)
             SL    r1,d2(x2,b2)        レジスター←メモリー(フルワード)
    

    AR,A,SR,S

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

      ----+----1----+----2----+----3----+----4----+----5----+----6----+
               AR    R0,R1               GR0 = GR0 + GR1
               S     R0,FWORD            GR0 = GR0 - FWORD
               :
      FWORD    DC    F'100'
      

      演算の結果、PSWに条件コードがセットされます。条件コードによって分岐し、実行後の処理を変えることができます。
      0 … 和または差はゼロ
      1 … 和または差は−
      2 … 和または差は+
      3 … オーバーフロー


    AH,SH

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

      ----+----1----+----2----+----3----+----4----+----5----+----6----+
               AH    R0,HWORD            GR0 = GR0 + HWORD
               :
      HWORD    DC    H'100'
      

      演算の結果、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)

    ----+----1----+----2----+----3----+----4----+----5----+----6----+----7--
             MR    r1,r2               レジスター←レジスター
             DR    r1,r2               レジスター←レジスター
             M     r1,d2(x2,b2)        レジスター←メモリー(フルワード)
             D     r1,d2(x2,b2)        レジスター←メモリー(フルワード)
             MH    r1,d2(x2,b2)        レジスター←メモリー(ハーフワード)
    

    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レジスターに格納されます。主記憶の内容は変わりません。

      ----+----1----+----2----+----3----+----4----+----5----+----6----+
               MR    R0,R7               GR0:1 = GR1 * GR7
               M     R14,FWORD           GR14:15 = GR15 * FWORD
               :
      FWORD    DC    F'100'
      

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


    MH

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

      ----+----1----+----2----+----3----+----4----+----5----+----6----+
               MH    R1,HWORD            GR1 = GR1 * HWORD
               :
      HWORD    DC    H'100'
      

      条件コードはセットされません。積は最大で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----+----2----+----3----+----4----+----5----+----6----+
               DR    R0,R7               GR1 = GR0:1 / GR7
               D     R14,FWORD           GR15 = GR14:15 / FWORD
               :
      FWORD    DC    F'100'
      

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

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

      ----+----1----+----2----+----3----+----4----+----5----+----6----+
      消費税を求める
               L     R1,=H'1000'
               MH    R1,=H'5'
               SLR   R0,R0
               D     R0,=F'100'               GR1 = x32 = 50
      
      百分率を求める(1000を掛けてnn.n%まで計算)
               LH    R1,=F'812'
               MH    R1,=H'1000'
               SR    R0,R0
               D     R0,=F'2500'              GR1 = x144 =324
      
      答えの324は、32.4%と出力時に編集すればよい。
      

2進数と10進数の変換

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

    ----+----1----+----2----+----3----+----4----+----5----+----6----+----7--
             CVD   R2,DOUBLE                CONVERT GR2 TO PACKED DECIMAL
             UNPK  TAX,DOUBLE               CONVERT DECIMAL TO ZONE FORMAT
             OI    TAX+L'TAX-1,C'0'         CORRECT SIGN BITS TO READABLE
             :
             CVD   R3,DOUBLE
             UNPK  PERCENT,DOUBLE
             OI    PERCENT+L'PERCENT-1,C'0'
             :
    DOUBLE   DC    D'0'
    TAX      DC    CL6' '
    PERCENT  DC    CL4' '
    

    CVD(Convert to Decimal)

      ----+----1----+----2----+----3----+----4----+----5----+----6----+----7--
               CVD   r1,d2(x2,b2)        レジスター→メモリー(ダブルワード)
      

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

      x0001E240 → 000000000123456C
      xFFFE1DC0 → 000000000123456D
      

      符号は+の時はC、−の時はDとなります。


    UNPK(Unpack)

      ----+----1----+----2----+----3----+----4----+----5----+----6----+----7--
               UNPK  d1(l1,b1),d2(l2,b2)      メモリー←メモリー
      

    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で定義した領域の長さをアセンブラーに自動計算させる時の書き方です。

      000000000123456C  → F0F0F1F2F3F4F5C6   8バイトのパック10進数を、8バイトのゾーン10進数に変換
      000000000123456D  → F3F4F5D6           8バイトのパック10進数を、4バイトのゾーン10進数に変換
      

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

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


分岐命令(Branch on Condition)

ちょっと横道へ逸れて、分岐命令を1つ解説します。加減算命令で出てきましたが、命令には実行した結果が条件コードとしてセットされるものがあります。条件コードがどうなるかを具体的に覚えるにはPSWを見るより、後続で実行する命令を分岐させる方がわかりやすいです。

    ----+----1----+----2----+----3----+----4----+----5----+----6----+----7--
             BCR   m1,r2               分岐←レジスター
             BC    m1,d2(x2,b2)        分岐←メモリー
    

    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として指定します。

      ----+----1----+----2----+----3----+----4----+----5----+----6----+
               BC    8,LABEL1            CC=0の時に分岐
               BC    4,LABEL1            CC=1の時に分岐
               BC    2,LABEL1            CC=2の時に分岐
               BC    1,LABEL1            CC=3の時に分岐
               BC    10,LABEL1           CC=0または2の時に分岐
               BC    7,LABEL1            CC=0でなければ分岐
               BC    15,LABEL1           常に分岐
               BC    0,LABEL1            常に分岐しない
      

      これはとってもわかりにくいです。そこでこんな書き方ができます。

      ----+----1----+----2----+----3----+----4----+----5----+----6----+
               BC    B'1000',LABEL1      CC=0の時に分岐
               BC    B'0100',LABEL1      CC=1の時に分岐
               BC    B'0010',LABEL1      CC=2の時に分岐
               BC    B'0001',LABEL1      CC=3の時に分岐
               BC    B'1010',LABEL1      CC=0または2の時に分岐
               BC    B'0111',LABEL1      CC=0でなければ分岐
               BC    B'1111',LABEL1      常に分岐
               BC    B'0000',LABEL1      常に分岐しない
      

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

      ----+----1----+----2----+----3----+----4----+----5----+----6----+
               AR    0,1                      GR0 = GR0 + GR1
               BZ    ZERO                     IF ZERO, .........
               BM    MINUS                    IF MINUS, ........
               BP    PLUS                     IF PLUS, .........
               BO    OVERFLOW                 IF OVERFLOW, .....
               :
      

      足し算の結果で処理を変えるような時、このように書ければわかりやすいです。一目瞭然です。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(ノーオペレーション)って何の意味があるの?って思いますか?まだ知らなくてもいいですが、実用上も大きな意味を持ちます。規模が大きい、内容が高度・複雑、商用、などのプログラムなどを書くことになると、NOP命令を使うようになります。

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