パック10進数をゾーン形式に変換するUNPK命令は、一般のアプリケーション・プログラムでもよく使われる命令の1つです。使い方としては、第2オペランド領域のパック10進数をゾーン10進数に変換して別領域に格納するのが分かり易いです。
1 2 3 4 5 6 7 8 |
L R0,=F'123456789' LOAD INTEGER VALUE CVD R0,DECIAREA CONVERT IT TO PACKED DECIMAL OI DECIAREA+7,X'0F' CORRECT SIGN FOR HUMAN READABLE UNPK ZONEAREA(9),DECIAREA CONVERT IT TO ZONED DECIMAL : : DECIAREA DC D'0' DOUBLE WORD WORKAREA ZONEAREA DC XL16'00' ZONED DECIMAL VALUE |
整数 123456789 をパック10進数(x000000123456789F)に変換してから、ZONEAREA領域に9バイトのゾーン10進数(xF1F2F3F4F5F6F7F8F9) として変換、格納します。とても一般的な使い方です。しかし、古いアセンブラー・コードには下記のようなコードが書かれていたりします。
1 2 3 4 5 6 7 |
L R0,=F'123456789' LOAD INTEGER VALUE CVD R0,ZONEAREA CONVERT IT TO PACKED DECIMAL OI ZONEAREA+7,X'0F' CORRECT SIGN FOR HUMAN READABLE UNPK ZONEAREA+3(9),ZONEAREA+3(5) CONVERT IT TO ZONED DECIMAL : : ZONEAREA DC XL16'00' ZONED DECIMAL VALUE |
こちらは別領域に出力するのではなく、入力元であるパック10進数の領域をそのまま出力先領域としても指定して変換、出力するものです。一見トリッキーで、同じ領域でUNPK?いいのか??となりますが、昔は少しでもプログラムは小さくしたいということもあり、命令の結果が意図したようになるならこのような「同じ領域でアンパックする」というコードが書かれたりもしました。
1 2 3 |
UNPK命令実行前のZONEAREA領域:00000012 3456789F 00000000 00000000 ................ UNPK ZONEAREA+3(9),ZONEAREA+3(5) UNPK命令実行後のZONEAREA領域:000000F1 F2F3F4F5 F6F7F8F9 00000000 ...123456789.... |
CVD命令は桁数に関係なく出力域は8バイト固定なので、有効数字の最大桁数が9桁なら先頭3バイトはx00になっています。そのため、入力となるパック10進数のアドレスをZONEAREAの先頭からではなく+3離れた位置をオペランドで示しています。同じ領域でアンパックする際のポイントは、出力先をZONEAREAの先頭からではなく入力と同じ+3離れたところをオペランドで示すことです。先頭から指定すると、
1 2 3 |
UNPK命令実行前のZONEAREA領域:00000012 3456789F 00000000 00000000 ................ UNPK ZONEAREA+0(9),ZONEAREA+3(5) UNPK命令実行後のZONEAREA領域:FFF5FFF5 F5F6F7F8 F9000000 00000000 .5.556789....... |
と変換中に入力側のパック10進数が壊れてしまい、期待した結果になりません。
マニュアル「z/Architecture解説書」のUNPACK命令の解説にある「プログラミング上の注意:」には、「不適切なオーバーラップがあると、アンパックするフィールドが破壊されることがあります。オペランドをオーバーラップさせて記憶域スペースを節約するには、第1オペランドの右端バイトが、第2オペランドの右端バイトより、第2オペランドのバイト数から2を引いたバイト数だけ右になければなりません。1バイトまたは2バイトのみをアンパックする場合は、両方のオペランドの右端バイトが重なっていても構いません。」との説明があります。
上記の例では、第2オペランドは5バイトなので、第1オペランドの右端バイトは5-2で3バイト分第1オペランドの右端バイトより右になければなりません。第2オペランドの右端バイトはZONEAREA+7ですから、第1オペランドの右端バイトはZONEAREA+10以降である必要があります。最初に示した例の第1オペランドZONEAREA+3(9)の右端バイトは、ZONEAREA+11の位置にありますから要件を満たしていて意図した通りに変換されています。しかし、2番目の例の第1オペランドZONEAREA+0(9)の右端バイトはZONEAREA+8の位置にありますから、ZONEAREA+10以降という要件を満たしておらず変換中に入力となるパック10進数のデータを壊してしまっています。
同じ領域でアンパックしているようなプログラムを保守するような際、数値の桁数を変更したり出力位置をずらすような必要が生じた場合は、UNPK命令の特性を理解して意図しない破壊が起きないように注意します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
UNPK命令実行前のZONEAREA領域:1F000000 00000000 00000000 00000000 ................ UNPK ZONEAREA(1),ZONEAREA(1) UNPK命令実行後のZONEAREA領域:F1000000 00000000 00000000 00000000 1............... UNPK命令実行前のZONEAREA領域:012F0000 00000000 00000000 00000000 ................ UNPK ZONEAREA(2),ZONEAREA(2) UNPK命令実行後のZONEAREA領域:F1F20000 00000000 00000000 00000000 12.............. UNPK命令実行前のZONEAREA領域:123F0000 00000000 00000000 00000000 ................ UNPK ZONEAREA(3),ZONEAREA(2) UNPK命令実行後のZONEAREA領域:F1F2F300 00000000 00000000 00000000 123............. UNPK命令実行前のZONEAREA領域:01234F00 00000000 00000000 00000000 ................ UNPK ZONEAREA(4),ZONEAREA(3) UNPK命令実行後のZONEAREA領域:F1F2F3F4 00000000 00000000 00000000 1234............ UNPK命令実行前のZONEAREA領域:12345F00 00000000 00000000 00000000 ................ UNPK ZONEAREA(5),ZONEAREA(3) UNPK命令実行後のZONEAREA領域:F1F2F3F4 F5000000 00000000 00000000 12345........... |
上の例が示すように、入力元と出力先のアドレスを同じにしておけば領域の長さに応じた有効桁数分のゾーン10進数への変換がなされます。同じ領域を指定してしまうと後続の入力データを壊してしまうように見えますが、UNPK命令は第1、第2それぞれのオペランドの先頭からではなく右端(最後尾)バイトから1バイト取り出して変換して2バイト書き込む(最下位桁のみ1バイトの書き込み)、それを長さ分上位桁に向かって繰り返す動きをします。同じ領域でアンパックするなら、第1オペランド・アドレスが第2オペランドより前に来ないようにすれば間違いないです。レジスター内のバイナリー整数をゾーン10進変換して表示や印刷を行う場合、帳票などの出力レコード域にパック10進数を置いておき、その先頭バイトから被せるようにゾーン10進数を格納すればいいでしょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
UNPK命令実行前のZONEAREA領域 (-3) 00000012 3456789F 00000000 00000000 ................ (-2) 00001234 56789F00 00000000 00000000 ................ (-1) 00123456 789F0000 00000000 00000000 ................ (00) 12345678 9F000000 00000000 00000012 ................ (+1) 3456789F 00000000 00000000 00001234 ................ (+2) 56789F00 00000000 00000000 00123456 ................ (+3) 789F0000 00000000 00000000 00000000 ................ UNPK命令実行後のZONEAREA領域 (-3) FFF5FFF5 F5F6F7F8 F9000000 00000000 .5.556789....... (-2) FFF3F3F4 F5F6F7F8 F9000000 00000000 .33456789....... (-1) F1F2F3F4 F5F6F7F8 F9000000 00000000 123456789....... (00) F1F2F3F4 F5F6F7F8 F9000000 00000012 123456789....... (+1) F1F2F3F4 F5F6F7F8 F9000000 00001234 123456789....... (+2) F1F2F3F4 F5F6F7F8 F9000000 00123456 123456789....... (+3) F1F2F3F4 F5F6F7F8 F9000000 00000000 123456789....... |
上の例は、第1オペランドを第2オペランドよりも3バイト前方、低位のアドレスにした位置から1バイトずつ第2オペランド側に寄せていった場合の変換例です。出力先を第2オペランドよりも2バイト以上前にすると壊れてしまうことがわかります。壊れるかどうかはアドレスだけでなく長さとも兼ね合いますが、全く同じアドレスに被せるとデータが壊れそうだから少しずらした方がいいのでは?などと気にしない方がいいでしょう。
新規に作るプログラムであれば素直に別領域にすればわかりやすいですが、過去に作られたプログラム保守であれば余程特別な理由がない限り元のロジックをなるべく直さずに新たな要件や仕様に対応することになるので、多少トリッキーであっても既に出来上がっていて正しく動いているものに合わせるのが実務的でしょう。