【実務・中級編】FIXED DECIMAL(PACKED-DECIMAL)の内部表現 – PL/Iの基本構文とデータ制御実践ガイド

おい、最近入った若手が「PL/Iの`FIXED DECIMAL`って、COBOLのCOMP-3と同じでしょ?」なんて軽いノリでコードを書いているのを見て、思わず背筋が凍ったんだよね。

甘い。甘すぎる。
メインフレームの基幹システムを支える上で、データの内部表現、特にパック10進数の構造とそれがアセンブラレベルでどう動くかを知らないのは、目隠しして時速100キロのブルドーザーを運転するようなものだ。

今回は、IBMメインフレームにおける `FIXED DECIMAL`(いわゆるパック10進数)の真髄と、それがコンパイルされて裏でどんなアセンブラ命令を叩いているのか、俺の実務経験を総動員して徹底的に叩き込んでやる。心してついてきな。

—

1. PL/Iの識別子哲学と、実は予約語を持たない言語仕様

本題に入る前に、PL/Iという言語のユニークなスタンスについて少し触れておこう。
C言語やCOBOLには、数々の「予約語(Reserved Words)」が存在する。例えば `IF` や `READ` などを変数名に使ったらコンパイルエラーになるあれだ。

しかし、PL/Iの言語仕様には「予約語という概念が存在しない」。
驚くだろ? `IF = 5;` なんてコードを書いたとしても、コンパイラは文脈(Context)からそれが変数なのか構文キーワードなのかを完璧に判断しやがる。
「じゃあ何でもありじゃん!」と思うかもしれないが、実務のコーディング標準ではそんな危なっかしい真似は絶対にしない。識別子(変数名)は業務的意味がひと目でわかるように命名し、コンパイラを混乱させるようなトリッキーな書き方は厳禁だ。

その代表格が、今回取り上げるデータ型宣言、`FIXED DECIMAL` である。

—

2. FIXED DECIMAL(PACKED-DECIMAL)の内部表現の真実

メインフレーム(z/Architecture)の世界では、数値を効率よく、かつ誤差なく扱うために「パック10進数(Packed Decimal)」がハードウェアレベルでサポートされている。

`DCL WS-AMT FIXED DEC(5,2);` という変数を定義したとしよう。
括弧の中の `(5,2)` は、全体で5桁、うち小数点以下が2桁という意味だ。では、こいつはメモリ上でどう表現されているか?

構造の基本:1バイトに2桁+符号

パック10進数は、1バイト(8ビット)の中に2つの10進数(0〜9)を詰め込む。そして最後の下位4ビット(右側のニブル)には符号(Sign)が入る。

  • 数字部分: 0から9までの値を4ビット(2進数で0000〜1001)で表現。
  • 符号部分: 最終バイトの下位4ビットに、プラスなら `C` または `F`(EBCDICの規定による)、マイナスなら `D` が入る。

例えば、`+123.45` という値は、メモリ上では以下のように3バイト(6ニブル+符号)で保持される。

[ 0123 ] [ 45 ] [ +C ] -> 合計 3バイト

もしこれが `FIXED DEC(7)` のような整数型であれば、桁数に応じて必要バイト数が決まる。計算式は `(桁数 + 2) / 2` の切り捨て だ。`7桁` なら `(7+2)/2 = 4バイト` となる。この物理的なサイズ感を頭に叩き込んでおかないと、VSAMファイルのレイアウト定義や、BMSマップとのデータ受け渡しで痛い目を見るぞ。

—

3. 演算時に展開されるアセンブラ命令(AP, SP, MP, DP)

さて、ここからがアーキテクトとしての本領発揮だ。
PL/Iで書かれた次のような加算処理を想像してほしい。

1
WS-TOTAL = WS-TOTAL + WS-NEW-PRICE;

この時、コンパイラ(IBM Enterprise PL/I Compiler)は何を生成しているか?
浮動小数点数(FLOAT)のようにレジスタへロードしてゴニョゴニョ計算するのではなく、なんと十進演算命令(Decimal Instructions)と呼ばれるアセンブラ命令を直接吐き出すのだ。

具体的には以下の4つの命令が主役になる。

1. `AP` (Add Decimal): 加算。メモリ上のパック10進数同士を直接足し合わせる。
2. `SP` (Subtract Decimal): 減算。
3. `MP` (Multiply Decimal): 乗算。
4. `DP` (Divide Decimal): 除算。

これらはすべて「メモリ対メモリ(Storage-to-Storage)」の命令だ。つまり、CPUの汎用レジスタを経由せず、メインメモリ上で直接計算が行われる。
メインフレームがビジネス計算(お金の計算)において誤差を出さず、圧倒的な強さを誇る理由はまさにここにある。IEEE 750系の浮動小数点数で起こる「0.1の足し算で丸め誤差が出る」なんてトラブルとは無縁な世界なのだ。

ただし、注意点もある。
`DP`(除算)や `MP`(乗算)を行う際、桁あふれ(Overflow)やゼロ除算が発生すると、ハードウェア割り込み(System Abend、例えば 0C7デス など)が即座に発生する。これがバッチ夜間運転で起きたら…冷や汗ものだろ? だからこそ、データ定義とオーバーフロー対策は厳重に行う必要がある。

—

4. 実践PL/Iコード:VSAM入出力とONユニットによる例外制御

百聞は一見に如かず。実際の業務バッチを想定したサンプルコードを見せよう。
このコードでは、VSAM(KSDS)ファイルを読み込み、`FIXED DECIMAL` 型の金額フィールドに対して安全な演算を行い、データ例外(データ不整合)を `ON-UNIT` でトラップする堅牢な構造にしている。

1
—————————————————————-

  • プログラム名: CALC01P
  • 概要: VSAMマスターからデータを読み込み、FIXED DECで計算を行う

—————————————————————-
CALC01P: PROC OPTIONS(MAIN);

— ファイル定義 (VSAM KSDS) —
DCL MSTR-FILE FILE RECORD
INPUT
ENV(Buffered);

— レコードレイアウト定義 —
DCL 1 MSTR-REC,
5 MSTR-ID CHAR(5), 顧客ID
5 MSTR-NAME CHAR(20), 顧客名
5 MSTR-SALES FIXED DEC(9,2), 売上金額 (パック10進数)
5 MSTR-TAX FIXED DEC(7,2); 消費税 (パック10進数)

— ワーク変数定義 —
DCL WK-TOTAL-SALES FIXED DEC(11,2) INIT(0);
DCL EOF-FLAG CHAR(1) INIT(‘NO’);

— データ例外(パック不正など)を捕捉するONユニット —
ON CONVERSION
BEGIN;
DISPLAY(‘【致命的エラー】データ異常を検知しました。’);
DISPLAY(‘不正なパックデータが混入しています。処理を中断します。’);
CLOSE FILE(MSTR-FILE);
SIGNAL FINISH;
END;

— ファイルオープン —
OPEN FILE(MSTR-FILE);

— メイン処理ループ —
DO WHILE(EOF-FLAG = ‘NO’);

— レコード読み込み —
READ FILE(MSTR-FILE) INTO(MSTR-REC);

IF / ファイル終了条件(ENDFILE条件)の判定 /
(SUBSTR(MSTR-ID,1,1) = HIGH(1)) THEN DO;
EOF-FLAG = ‘YES’;
;
END;
ELSE DO;
— ここでハードウェア命令 AP (加算) が展開される —
— MSTR-SALES と MSTR-TAX をメモリ上で直接合算する —
WK-TOTAL-SALES = WK-TOTAL-SALES + MSTR-SALES + MSTR-TAX;
END;

END;

— 結果出力 (BUILTIN関数の TRIM を活用) —
DISPLAY(‘総売上金額(税込み): ‘ || TRIM(TO_CHAR_FUNC(WK-TOTAL-SALES)));

— クローズ —
CLOSE FILE(MSTR-FILE);

RETURN;

END CALC01P;

コードの解説と現場の知見

1. `FIXED DEC(9,2)` の実用:
売上金額や税金といった金銭データには、必ず `FIXED DECIMAL` を使うこと。浮動小数点(`FLOAT`)をマネー計算に使ったら、監査法人から一発で指摘を受けるか、最悪の場合、巨額の端数ズレで大チョンボになる。
2. `ON CONVERSION` ユニットの重要性:
もしVSAMファイルに、他システムから流入したゴミデータ(ゾーン10進数の混入や、パッチであてられた不正な符号バイト)が含まれていた場合、前述の `AP` 命令実行時にハードウェア例外(Abend S0C7)が起きる。
このコードのように `ON CONVERSION` を仕掛けておくことで、システムが突然クラッシュするのを防ぎ、優雅にエラーメッセージを出して安全にリターンすることができる。これぞプロのアーキテクトの技だ。
3. BUILTIN関数の活用:
PL/Iには強力な組み込み関数(BUILTIN)が用意されている。文字列表現への変換やトリミングにはこれらを適切に使い、パフォーマンスの劣化を防ぐこと。

—

5. おわりに:レガシーの底力をなめるな

どうだ? `FIXED DECIMAL` というたった一つのデータ型をとってみても、裏側ではEBCDICの符号規則、メモリのニブル単位の配置、そしてアセンブラの十進演算命令(`AP`, `SP` 等)が密接に絡み合っているのが分かったはずだ。

「古い言語だから」「よく分からないから」と敬遠してコピペコーディングを続けていると、いつか本番環境でS0C7の洗礼を受けることになる。
メインフレームのアーキテクチャとPL/Iの言語仕様を深く理解し、CPUに無駄な負荷をかけず、安全で正確無比なロジックを組むこと。それが、今この時代にレガシーシステムを守る我々エンジニアの誇りであり、腕の見せどころなのだ。

さて、理論はここまでだ。次のバッチ改修では、自分が書いたコードが裏でどんなアセンブラ命令に翻訳されているか、コンパイラリスト(Listing)を出して自分の目で確かめてみな。いいエンジニアへの近道は、そこから始まる。

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