おい、最近入った若手が「先輩、PL/Iって変数名に `TOTAL` とか `VALUE` って普通に使っても怒られないんですね。予約語ってないんですか?」って聞いてきたんだよ。
そうなんだよな。C言語やJava上がりの連中からすると、言語のキーワードが変数名に使えないのは常識なもんだから、PL/Iの「予約語を持たない」という懐の深さには最初は面食らう。PL/Iにはコンテキスト依存のキーワードしかなくて、`IF = THEN THEN THEN = ELSE;` なんていう悪夢のようなコードすら(文法上は)成立してしまう。まあ、そんなコード書いたら翌日にはコードレビューで徹底的に絞られるがな。
さて、今回はそのPL/Iのデータ制御の肝、`FIXED DECIMAL(p,q)`(いわゆるパック10進数、COBOLでいうところの `COMP-3`)について徹底的に叩き込んでやる。
基幹システムの勘定系やデータレイクとのインターフェースで、このパック10進数の仕組みを理解していないと、データ破損や原因不明の数値化け、あるいはバッチ処理の致命的な性能劣化を引き起こす。現場のリアルな知見を交えて解説するから、しっかりついてこい。
—
1. パック10進数(COMP-3)のメモリ構造と符号ニブルの真実
メインフレームのアーキテクチャ(z/Architecture)において、金銭計算などの厳密な十進演算を行うためには `FIXED DECIMAL` が欠かせない。
IBMのEnterprise PL/Iコンパイラでは、`FIXED DECIMAL(p, q)` はデフォルトで COMP-3(パック10進数) 形式としてメモリ上に配置される。
符号ニブルの仕組み
パック10進数とは、1バイト(8ビット)の中に2つの10進数字(ニブル=4ビット×2)を詰め込み、最後の1ニブルを符号(Sign)として使用する形式だ。
桁数 $p$(Precision)に応じて、必要なバイト数は以下の計算式で決まる。
$$\text{バイト数} = \lfloor (p + 2) / 2 \rfloor$$
例えば、`FIXED DECIMAL(5, 2)` なら、$(5 + 2) / 2 = 3.5$ の切り捨てで 4バイト 消費する。内訳は、整数部・小数部あわせて5桁分の数字と、最後の1ニブルの符号だ。
この符号ニブルには、EBCDICのゾーン付き10進数やハードウェア命令に対応するため、以下のような標準的なビットパターンが格納される。
- 正数(Positive): `C` または `F` (標準は `C`。ただし符号なし定義や演算結果によっては `F` や `A` になることもある)
- 負数(Negative): `D`
- 未符号(Unsigned): `F`
俺たちがよくやるトラブルシューティングで、外部ファイル(VSAMやKSDS、あるいは他系から飛んできた順ファイル)のダンプ(DUMP)をSYSUDUMPやFile-AIDで覗いたとき、末尾の1ニブルが `C` や `D` ではなく `0` や `E` になっていて、画面表示や加算時にアベンド(S0C7:データ例外)を起こすケースがこれだ。C言語などで適当にバイナリを突っ込まれたファイルを読むときは、この符号ニブルの破壊に一番気をつけなければならない。
—
2. 演算時の変換コスト:なぜ「型」を合わせる必要があるのか?
PL/Iは暗黙の型変換(プロモーション)を非常に強力にこなしてくれる言語だ。
例えば、`FIXED DECIMAL` 同士の計算だけでなく、`FIXED BINARY`(固定小数点二進数、COBOLの `COMP` や `BINARY`)や、果ては `FLOAT`(浮動小数点)との混算であっても、コンパイラが勝手に適切な機械語命令(パック十進演算命令である `AP`, `SP`, `MP`, `DP` や、十進・二進の変換命令 `PACK`, `UNPK`, `CVB`, `CVD` など)を生成して帳尻を合わせてくれる。
だがな、そこに大きな落とし穴がある。
バッチ処理のループ内で、異なるデータ属性(例えば `FIXED DECIMAL(9,0)` と `FIXED BINARY(31,0)`)同士の演算を無造作に繰り返していると、コンパイラはその都度、裏で型変換ルーチンやサブルーチンを呼び出すコードを吐き出す。これが数百万件回る日次バッチのループ内で行われたらどうなるか? CPU使用率が跳ね上がり、バッチウィンドウを盛大にブチ破ることになる。
実務のマイグレーションや性能チューニングでは、「算術演算に関わる変数の属性は、可能な限り桁数(Precision)と小数点位置(Scale)まで完全に一致させる」のが鉄則だ。
—
3. 実践:VSAM入出力とONユニット、BUILTIN関数を活用したPL/Iプログラム
百聞は一見にしかずだ。実際のメインフレーム開発現場を想定した、堅牢なPL/Iソースコードを見せてやろう。
VSAM(KSDS)からレコードを読み込み、パック10進数のフィールドに対して安全な演算を行い、データ例外が発生した際のエラーハンドリング(ONユニット)を組み込んだサンプルだ。
1
/——————————————————————–/
/ プログラム名: CUSTCALC /
/ 概要: VSAMファイルから顧客データを読み込み、パック10進数の /
/ 売上金額を集計するバッチ処理サンプル /
/——————————————————————–/
CUSTCALC: PROC OPTIONS(MAIN);
/ — 1. 宣言部 — /
/ VSAM KSDSのキー定義 /
DCL WK_CUST_ID CHAR(5);
/ レコードレイアウト(顧客マスター) /
DCL 1 CUST_REC,
5 R_CUST_ID CHAR(5),
5 R_CUST_NAME CHAR(30),
/ パック10進数(9桁、小数点以下2桁) /
5 R_SALES_AMT FIXED DECIMAL(9,2),
/ パック10進数(整数部のみ 7桁) /
5 R_POINT_QTY FIXED DECIMAL(7,0);
/ 演算用ワーク変数(属性をレコードと完全に一致させる) /
DCL WK_TOTAL_SALES FIXED DECIMAL(11,2) INIT(0);
DCL WK_TOTAL_POINT FIXED DECIMAL(9,0) INIT(0);
DCL EOF_FLG CHAR(1) INIT(‘OFF’);
DCL MSG_BUF CHAR(80);
/ — 2. 条件処理(ONユニット)の設定 — /
/ データの破損や異常な符号ニブルによるS0C7(データ例外)を捕捉 /
ON CONVERSION
BEGIN;
PUT SKIP EDIT (‘【致命的エラー】データ例外(S0C7)を検出しました。不正なパックデータです。’) (A);
PUT SKIP EDIT (‘該当顧客ID: ‘, WK_CUST_ID) (A, A);
/ 異常終了コードを返して処理をアボート /
signal ERROR;
END;
/ — 3. ファイルオープン — /
OPEN FILE(CUSTFILE) INPUT;
/ — 4. メイン処理ループ — /
DO WHILE (EOF_FLG = ‘OFF’);
/ VSAM KSDSからのレコード読み込み /
READ FILE(CUSTFILE) INTO(CUST_REC);
IF ENDFILE(CUSTFILE) THEN
EOF_FLG = ‘ON’;
ELSE
DO;
WK_CUST_ID = R_CUST_ID;
/ BUILTIN関数(SUM等)または直接加算による累積 /
/ 同一属性同士の演算であるため、無駄な型変換が発生しない /
WK_TOTAL_SALES = WK_TOTAL_SALES + R_SALES_AMT;
WK_TOTAL_POINT = WK_TOTAL_POINT + R_POINT_QTY;
END;
END;
/ — 5. 終了処理と結果出力 — /
CLOSE FILE(CUSTFILE);
/ 編集出力(EDIT形式による数値の整地) /
PUT SKIP EDIT (‘=== 集計結果報告 ===’) (A);
PUT SKIP EDIT (‘総売上金額:’, WK_TOTAL_SALES) (A, F(13,2));
PUT SKIP EDIT (‘総獲得ポイント:’, WK_TOTAL_POINT) (A, F(11,0));
END CUSTCALC;
コードの解説と実務のポイント
1. `ON CONVERSION` ユニットの活用
メインフレームのバッチで最も恐ろしいのは、不正なパックデータによってプログラムが突如 `SYSTEM ABEND S0C7` で落ちることだ。このコードのように `ON CONVERSION` を仕掛けておけば、PL/Iのランタイムがデータ例外をトラップし、どのデータでコケたのかをログに残して安全に異常終了させることができる。
2. 属性の完全一致
`WK_TOTAL_SALES` は桁あふれ(Overflow)を防ぐために `FIXED DECIMAL(11,2)` と少し大きめに定義しているが、加算元の `R_SALES_AMT` と小数点位置 (`2`) を完全に一致させている。これにより、ハードウェアの十進演算命令がダイレクトに実行され、パフォーマンスが最大限に引き出される。
3. BUILTIN関数とEDIT編集
出力時の `F(13,2)` などの画像記述子(Picture/Format)は、パック10進数を人間が読める文字列にアンパックする際の定番だ。実務ではここに `P’ZZZ,ZZZ,999.99’` のようなピクチャー句を組み合わせて、帳票やCSV出力フォーマットを整えることになる。
—
先輩からのアドバイス
いいか、PL/Iにおける `FIXED DECIMAL(p,q)` は単なる「数字を入れるハコ」じゃない。CPUのハードウェア仕様(十進演算命令)と直結した、メインフレームの心臓部のようなものだ。
「動けばいいや」と適当な型で受けていると、大規模なデータ移行の際に必ずしっぺ返しを喰らう。ファイルのレイアウト定義(COPY句やINCLUDEメンバー)から、演算ワーク、そして出力に至るまで、データの有効桁数と符号の動きを頭に叩き込んでおけ。
何かバッチの性能問題やS0C7の解析で詰まったら、いつでも俺のところに相談に来い。現場からは以上だ。
