【実務・中級編】FIXED DECIMAL(p,q)のパック10進数表現 – PL/Iの基本構文とデータ制御実践ガイド

おい、新人。今度の勘定系バッチの改修で、金額項目の定義に悩んでいるらしいな。
「とりあえず、今まで通り `FIXED DECIMAL(15,2)` にしておけばいいや」なんて安易に考えていないか?

その甘い考えが、夜間バッチのCPU使用量を跳ね上げさせたり、最悪の場合、本番稼働中のデータイーストで「S0C7(データ例外)」の嵐を巻き起こす原因になるんだ。IBMメインフレームの世界では、データの持ち方一つでマシンの息切れの仕方がガラッと変わる。

今日は、PL/Iにおけるパック10進数――すなわち `FIXED DECIMAL(p,q)` が、裏側のハードウェア(System z)でどう扱われ、なぜアライメントや変換コストに気を配らなければならないのか、実務に直結するノウハウを徹底的に叩き込んでやる。しっかりついてこいよ。

1. パック10進数(COMP-3)のリアル:なぜPL/IのDECIMALなのか

現代のオープン系から流れてきた連中は、「なぜわざわざ10進数を使うのか? 2進数(BINARY)の方が処理が速いだろう」と口を揃える。だがな、金銭を扱う基幹システムにおいて、浮動小数点や単純なバイナリ演算で発生する「丸め誤差」は致命傷になるんだ。1円のズレも許されない世界では、人間が普段使う10進数をそのままメモリ上に表現できるパック10進数(COBOLでいう `COMP-3`)がデファクトスタンダードなのさ。

PL/Iにおいて、このパック10進数は `FIXED DECIMAL(p, q)`(あるいは略して `DEC`)として定義される。

  • `p` (Precision / 精度): 全体の桁数(1〜31)
  • `q` (Scale / 小数桁数): 小数点以下の桁数

IBMメインフレームのハードウェアは、このパック10進数を1バイトあたり2桁の数字として詰め込み、最後の4ビット(下位ニブル)に符号(`C`がプラス、`D`がマイナス、`F`が符号なし/ゾーン等)を格納する専用の命令群(Decimal Instructions)を持っている。これがSystem zの真骨頂だ。

2. アライメントの罠と変換コストの正体

さて、ここからが本題だ。メインフレームの性能を語る上で避けて通れないのが「境界アライメント(Alignment)」「暗黙のデータ変換コスト」だ。

奇数桁と偶数桁のメモリ上の違い

`FIXED DECIMAL(5)` と定義した場合、全体で5桁だ。
1バイトに2桁入るから、`5 / 2 = 2.5`。切り上げて 3バイト 消費する。
では、`FIXED DECIMAL(6)` と定義したらどうなる? `6 / 2 = 3` で 3バイト だ。
実は、PL/IのDECIMALは、定義された桁数が偶数であっても、内部的には奇数桁として切り上げられてメモリを占有することが多い。だが、レコード入出力や外部インターフェース(特にCOBOLやC言語、あるいは古いVSAMファイルとのレイアウト共有)において、この「偶数・奇数の定義ミス」は、オフセットのズレを引き起こす最大の元凶になる。

演算時の暗黙の型変換がCPUを殺す

一番恐ろしいのは、計算式の中に異なる属性のデータを混ぜたときだ。
例えば、`FIXED DECIMAL` と `FIXED BINARY`(固定小数点2進数)を演算させると、コンパイラは裏側でこっそりとデータ型の変換ルーチンを生成する。

数百万件を回る月次バッチのループ内でこれが起きるとどうなるか?
「たった1行の四則演算のために、CPUサイクルが型変換のパッキング・アンパッキングに無駄撃ちされ、バッチウィンドウ(処理時間枠)からはみ出す」という、レガシー現場では笑えない事故が起きる。

演算を行うときは、極力同じ `DECIMAL` 同士で完結させるか、あらかじめ変数の型をそろえるのがプロの作法だ。

3. 実践:VSAM入出力とONユニット制御を含むPL/Iプログラム

百聞は一見にしかずだ。実際のメインフレーム開発現場で通用する、PACKAGE構造を用いたモダンかつ堅牢なPL/Iプログラムのサンプルを見せてやろう。

このコードは、VSAM(KSDS)ファイルから売上データを読み込み、`FIXED DECIMAL` で厳密に金額を集計しつつ、万が一のデータ不正(S0C7の元になるパックスペースの破損など)を `ONCONDITION`(ONユニット)でトラップして安全にリカバーする骨組みだ。

/================================================================/
/ PACKAGEブロックによるカプセル化と構造化 /
/================================================================/
SALES_CALC: PACKAGE OPTIONS(MAIN);

/ 内部プロシージャの宣言 /
DCL PROC_MAIN EXTERNAL ENTRY;

/メイン処理エントリ/
MAIN_PROC: PROC OPTIONS(MAIN);

DISPLAY ‘ 処理開始: 月次売上集計バッチ ‘;

CALL PROC_MAIN;

DISPLAY ‘ 正常終了: 月次売上集計バッチ ‘;

END MAIN_PROC;

/ 実処理を担うプロシージャ /
PROC_MAIN: PROC;

/ — 変数定義 — /
/ 売上レコード構造体(VSAM KSDSのレイアウトを想定) /
DCL 1 SALES_REC,
5 SR_CUST_ID CHAR(5), / 顧客ID /
5 SR_TRAN_DATE CHAR(8), / 取引日付(YYYYMMDD) /
5 SR_AMOUNT FIXED DEC(11,2); / 取引金額(パック10進数: 6バイト占有) /

/ 集計用ワーク変数(オーバフローを防ぐため桁数を大きくとる) /
DCL WK_TOTAL_AMT FIXED DEC(15,2) INIT(0);
DCL WS_EOF_FLG CHAR(1) INIT(‘OFF’);

/ ファイル定義(VSAM KSDS) /
DCL SALES_FILE FILE RECORD
INPUT
ENVIRONMENT(
GENERIC(VSAM)
);

/ — ONユニット(例外処理・割り込み制御) — /
/ 万が一、不正なパックデータが読み込まれてCONVERSIONエラーが起きた場合のトラップ /
ON CONVERSION
BEGIN;
DISPLAY ‘【警告】データ変換エラーを検出しました。’;
DISPLAY ‘問題の顧客ID: ‘ || SR_CUST_ID;
DISPLAY ‘該当レコードをスキップし、処理を継続します。’;
/ エラー値をゼロとみなして続行するための制御 /
SR_AMOUNT = 0;
END;

ON ENDFILE(SALES_FILE)
WS_EOF_FLG = ‘ON’;

/ — ファイルオープン — /
OPEN FILE(SALES_FILE);

/ — メインループ — /
READ FILE(SALES_FILE) INTO(SALES_REC);

DO WHILE(WS_EOF_FLG = ‘OFF’);

/ FIXED DECIMAL同士の安全かつ高速な加算演算 /
/ アライメントを意識した純粋な10進数演算のため余計な変換コストが発生しない /
WK_TOTAL_AMT = WK_TOTAL_AMT + SR_AMOUNT;

/ 次レコード読み込み /
READ FILE(SALES_FILE) INTO(SALES_REC);
END;

/ — 結果出力 — /
DISPLAY ‘—————————————-‘;
DISPLAY ‘ 総売上金額: ‘ || TO_CHAR_EDIT(WK_TOTAL_AMT);
DISPLAY ‘—————————————-‘;

/ — クローズ — /
CLOSE FILE(SALES_FILE);

END PROC_MAIN;

/ 編集出力用の簡易内部関数(BUILTINのPUT仕様の代替イメージ) /
TO_CHAR_EDIT: PROC(P_VAL) RETURNS(CHAR(20));
DCL P_VAL FIXED DEC(15,2) PARAMETER;
DCL W_EDIT_AREA CHAR(20);

/ 編集機能付き代号(PICTURE句)による文字列表現への変換 /
W_EDIT_AREA = PICTURE(P_VAL, ‘ZZZ,ZZZ,ZZZ,ZZ9.99’);
RETURN(W_EDIT_AREA);
END TO_CHAR_EDIT;

END SALES_CALC;

4. シニアアーキテクトからの実践アドバイス

どうだ? コードの構造が見えてきたか。実務でこの手のバッチを触るとき、以下の3点は絶対に忘れるなよ。

1. PICTURE句とDECIMALの使い分けを極めろ
計算を行う変数は `FIXED DECIMAL(p,q)` だ。画面に出力したり、ログに残したり、あるいはフラットファイルへ文字列として吐き出すときには初めて `PICTURE` 属性(編集用文字)をかませる。計算用と編集用をごちゃ混ぜにすると、コンパイラが裏で泣くことになる。
2. ON CONVERSION の設計をサボるな
メインフレームの移行現場で一番多いトラブルは、移行元(ホストの古いデータ)に混ざっていた「ゴミデータ(スペースや不正文字)」が、移行先の厳密なPL/Iプログラムで `CONVERSION` エラーを引き起こすパターンだ。サンプルで見せたように、`ON CONVERSION` ユニットを適切に配置し、パニックを起こさずにロジック内でハンドリングできるように組むのがプロの仕事だ。
3. 桁あふれ(OVERFLOW)のケア
`FIXED DECIMAL` で計算を行う際、結果が変数の定義桁数を超えると `FIXEDOVERFLOW` 条件が発生する。デフォルトでは異常終了(ABEND)だ。金額系であれば、ワーク変数は常に余裕を持った桁数(例: `FIXED DEC(15,2)` や `(18,2)`)で受けるのが鉄則だ。

PL/Iは古い言語だなどと言いmesser(侮る)奴がいるが、IBMメインフレームのアーキテクチャとこれほど密に、かつ美しく結合した言語は他にない。ハードウェアの特性(パック10進数とアライメント)を脳裏に焼き付け、無駄な変換コストを生まない洗練されたコードを書け。

何か詰まったらいつでも相談に来い。お前の現場の成功を期待している。

タイトルとURLをコピーしました