はじめに:なぜ今、私たちは「パック10進数(COMP-3)」の深淵を覗くのか
基幹システムの現場で長年生き抜いてきたシニアアーキテクトなら、深夜の運用保守で突然遭遇する「S0C7アベンド(データ例外)」の冷や汗を一度や二度は経験しているはずだ。JavaやC#といったモダン言語に慣れ親しんだ若いエンジニアから見れば、「なぜ1バイトに数字が2つも詰まっているのか」「なぜ符号が一番下のニブル(4ビット)に隠れているのか」と、古めかしいパズルに映るかもしれない。
しかし、金融、クレジット、勘定系といった一円の狂いも許されないミッションクリティカルな世界において、浮動小数点演算が持つ「丸め誤差」を完全に排除し、人間が意図した通りの10進数演算を保証する `FIXED DECIMAL(p,q)`――いわゆるCOMP-3形式こそが、今なおメインフレームの心臓部を支え続けている。
今回は、このパック10進数(COMP-3)の内部構造における「符号ニブルの狂気と挙動」、そしてオープン系(Java/C#)へのマイグレーションやDB2/CICS環境におけるエッジケース対策について、コンパイラの裏側まで踏み込んで徹底的に紐解いていこう。
—
1. FIXED DECIMAL(p,q) とパック10進数の内部構造
PL/Iにおける `DCL WS-AMT FIXED DECIMAL(9,2);` のような宣言は、IBM S/370アーキテクチャ以降のハードウェア命令(Decimal Instructions:ZAP, AP, SP, MP, DPなど)と完全に直結している。
パック10進数のメモリレイアウトと符号ニブル
パック10進数は、1バイト(8ビット)の中に2桁の数字(1桁4ビット=ニブル)を詰め込み、最後の右側ニブル(最下位4ビット)に符号を配置するという、極めて高密度なフォーマットだ。
- データ表現: `C`(プラス)、`D`(マイナス)、`F`(符号なし/デフォルトプラス)
- メモリサイズの計算式: $\text{Bytes} = \lfloor (p + 2) / 2 \rfloor$
- 例:`FIXED DECIMAL(9,2)` の場合、$(9 + 2) / 2 = 5.5 \rightarrow$ 5バイト 消費する。
ここで恐ろしいのは、PL/Iコンパイラはデフォルトではメモリー上のビットパターンを厳密にバリデーションしない点だ。もし外部ファイルや不正なポインタ操作によって、最下位ニブルが `C`/`D`/`F` 以外(例えば `E` や `A`)に書き換わった場合、ハードウェアがDECIMAL DATA CHECK割り込みを検出し、容赦なく S0C7アベンド を引き起こす。
—
2. 実践:ベース変数とポインタによる動的メモリ操作とエッジケース
レガシーなバッチプログラムや電文解析では、ストレージの特定領域をオーバーレイ(定型化された構造の重ね合わせ)して処理することが多いため、ベース変数(Based Variable)とポインタ(Pointer)が多用される。
以下に、パック10進数を含むエリアをポインタで受けて安全に操作する実践的なPL/Iコードを示す。
——————————————————————-
- モジュール名: PKTRUT01
- 概要: ポインタとベース変数を用いたパック10進数の動的制御と
- 符号チェックのサンプルコード
——————————————————————-
PKTRUT01: PROC OPTIONS(MAIN);
%INCLUDE SQLCA;
/ ワーキングストレージ変数の定義 /
DCL RAW_BUFFER CHAR(100) BASED(P_BUFFER);
DCL P_BUFFER POINTER INIT(NULL());
/ パック10進数を含む構造体を定義(COMP-3相当) /
DCL 1 ACCOUNT_RECORD BASED(P_ACC_REC),
5 ACC_NO FIXED DEC(7,0), / 口座番号 (4バイト) /
5 ACC_BALANCE FIXED DEC(11,2); / 残高 (6バイト) /
DCL P_ACC_REC POINTER INIT(NULL());
/ 外部から取得したストレージの模擬(ここでは自前で確保) /
P_BUFFER = STORAGE(RAW_BUFFER);
P_ACC_REC = P_BUFFER;
/ 値の設定(コンパイラが自動的にパック形式へ変換して格納) /
ACC_NO = 1234567;
ACC_BALANCE = 987654321.89;
/ ————————————————————- /
/ アーキテクトの知見:内部符号反転バグのシミュレーションと検知 /
/ ————————————————————- /
CALL INSPECT_AND_VALIDATE_SIGN(P_ACC_REC);
RETURN;
INSPECT_AND_VALIDATE_SIGN: PROC(P_REC);
Dcl P_REC POINTER;
Dcl 1 LOCAL_REC BASED(P_REC),
5 L_ACC_NO FIXED DEC(7,0),
5 L_BAL_BYTE CHAR(6); / 6バイトの物理領域を直接覗く /
Dcl WORK_CHAR CHAR(1);
Dcl SIGN_VAL BIT(4);
/ 残高の最下位バイト(最後の1バイト)を取得 /
WORK_CHAR = SUBSTR(L_BAL_BYTE, 6, 1);
/ 下位ニブル(右側の4ビット)を抽出して符号を確認する /
SIGN_VAL = UNIBBR(WORK_CHAR); / ※概念的な表現:実際はBIT演算 /
PUT SKIP EDIT (‘ACC_NO: ‘, L_ACC_NO) (A, F(10));
PUT SKIP EDIT (‘RAW LAST BYTE HEX: ‘, SUBSTR(L_BAL_BYTE,6,1)) (A, HEX(A));
END INSPECT_AND_VALIDATE_SIGN;
END PKTRUT01;
—
3. 演算時の変換コストとコンパイラ最適化の罠
JavaやC#の `BigDecimal` を触ったことがある人なら、任意の精度を持つ算術演算がいかにCPUサイクルのコストを伴うか知っているだろう。PL/Iでも同様だ。
異なる精度・小数点桁数(p,q)同士の演算コスト
例えば、`FIXED DEC(5,0)` と `FIXED DEC(7,2)` の変数を加算する場合、コンパイラは内部的に桁合わせ(Alignment)のコードを生成する。
- スケール(q)が異なる場合、乗算や除算だけでなく、加減算であっても一時的なレジスタやパディング領域への伸長・シュリンクが発生する。
- 特にループ内での暗黙の型変換や精度拡張は、メインフレームのCPUサイクルを無駄に消費する温床となる。
最適化オプション(OPTIMIZE)の挙動
Enterprise PL/Iコンパイラにおいて、`OPTIMIZE(2)` などを指定すると、ループ内で繰り返し使用されるパックデシマルの符号判定や、定数倍の乗算(例:消費税計算など)がハードウェア命令レベルでインライン展開・最適化される。
しかし、不適切なデータ定義(例:意図せぬバイナリデータの混入や、パディングの不一致)があると、最適化によってレジスタにロードされた不正データが想定外のタイミングでS0C7を引き起こすケースがあるため、マイグレーション時のテストでは `-TEST-` やコンパイルオプションの差異に細心の注意を払う必要がある。
—
4. 埋め込みSQL(DB2)とCICS環境におけるエッジケース対策
基幹系PL/Iプログラムの真骨頂は、DB2やCICSとの密な統合にある。ここで発生しがちなエッジケースを見ておこう。
1. DB2の `DECIMAL` 型とPL/Iの `FIXED DECIMAL` のマッピング
DB2テーブル定義が `DECIMAL(11,2)` である場合、PL/I側も `FIXED DEC(11,2)` でホスト変数を受けなければならない。もしホスト変数の定義精度を誤り、例えば `FIXED DEC(9,2)` で受けてしまうと、オーバーフロー または 数値切り捨てエラー(SQLCODE -407 / SQLSTATE 22001系など) が発生する。
さらに、DB2が返すNULL値を受け取るためには、必ずインジケータ変数(IND-VARIABLE FIXED BIN(15))をセットで用意しなければならない。これを怠ると、データベースからNULLが返却された瞬間にS0C7または専用のDB2アベンドに直結する。
2. CICSオンラインにおける電文(COMMAREA)の罠
CICSのトランザクション間でデータを渡す際、`DFHCOMMAREA` を通じてパック10進数をやり取りすることが多い。
オープン系システム(Java等)へマイグレーションする際、最も多いバグが 「符号ニブルの解釈ミス」 である。メインフレーム側が生成したCOMP-3のバイナリを、Javaの単なるビッグエンディアンの整数として解釈してしまうと、最下位ニブルの符号(`C` や `D`)がデータの一部(数字のオンスケール)として誤読され、金額が何倍にも跳ね上がったり、マイナスがプラスに反転したりする大惨事を引き起こす。
移行設計の急所
JavaやC#へマイグレーションする際は、単にソースコードを書き換えるだけでなく、
- バイナリレイアウトの厳密なエミュレーション(IBM COMP-3のパッキング・アンパッキングアルゴリズムの再現)
- 不正符号ニブルのサニタイジング処理(レガシーデータに潜むゴミデータのクレンジング)
この2点を担保する共通ライブラリを移行先アーキテクチャに組み込むことが、プロジェクトの成否を分ける絶対条件となる。
—
おわりに:レガシーの知見を未来のアーキテクチャへ
パック10進数(COMP-3)という、一見すると泥臭く古い技術の裏側には、限られたハードウェア資源の中で「正確性と速度」を極限まで追求した先人たちの執念が宿っている。
JavaやC#へのマイグレーションプロジェクトを率いるテックリードやアーキテクトにとって、単に「動くコードに書き換える」のではなく、こうした「データの物理表現とハードウェアの挙動」までを理解した上で移行設計を行うことこそが、移行後の予期せぬトラブルを防ぎ、真に信頼性の高いシステムを構築するための唯一の道である。
基幹システムのマグマのような歴史をリスペクトしつつ、モダンなアーキテクチャへと確実に橋を架けていこう。
