【実務・中級編】FIXED DECIMALのパック10進数形式(COMP-3) – PL/Iの基本構文とデータ制御実践ガイド

こんにちは。基幹システムの現場で、日々COBOLやPL/Iの巨大なソースコードと格闘しているメインフレーム・エンジニアの皆さん。

今回は、PL/Iの基本データ型の中でも、金融・勘定系システムの屋台骨を支える「FIXED DECIMAL(パック10進数 / COMP-3)」について、メモリの内部構造から実務でのハマりどころ、そしてVSAMファイル入出力や異常系(ONユニット)の制御まで、徹底的に解説しよう。

レガシーマイグレーションの現場や、異機種間データ連携のトラブルシューティングにおいて、「なぜここでDATAEXCEPTIONが起きるのか」「なぜ符号が化けるのか」と頭を抱えた経験はないだろうか?
その謎の答えは、すべてメモリ上のニブル(4ビット)単位のデータ構造と、コンパイラの生成する機械語コードにある。さっそく、その深淵を覗いていこう。

—

1. FIXED DECIMAL(p,q) とパック10進数(COMP-3)の基本仕様

PL/Iにおける `FIXED DECIMAL(p, q)` は、IBMメインフレームのハードウェア命令(Decimal Instructions)と直接マッピングされる極めて重要なデータ型だ。COBOLで言うところの `COMP-3` (Computational-3)に完全一致する。

  • p (Precision / 精度): 全体の有効桁数(1 ~ 31)。符号は含まない。
  • q (Scale / 尺度): 小数点以下の桁数($0 \le q \le p$)。

メモリ上の格納形式(パック10進数)

パック10進数は、1バイト(8ビット)の中に2つの10進数字(ニブル:4ビット×2)を詰め込んだ形式だ。そして、最後の1バイトの下位4ビット(右側のニブル)には、必ず「符号(Sign)」が格納される。

例えば、`DCL WS-AMT FIXED DECIMAL(5, 2);` という変数を定義した場合:

  • 全体で5桁なので、バイト数は `(5 + 2) / 2 = 3.5` → 切り上げて 2バイト となりそう……だがちょっと待ってほしい。
  • パック10進数の計算式は `(p + 2) / 2` の切り捨て(整数部) またはIBMマニュアルの公式に基づく。
  • `FIXED DEC(5,2)` の場合、`(5 + 2) / 2 = 3.5` で 3バイト 消費する。

具体的なメモリイメージを見てみよう。値が `+123.45` の場合:

[ 01234 ] [ 5C ]
字节1 字节2 字节3

  • 第1バイト: `01` (十進数の 0 と 1)
  • 第2バイト: `23` (十進数の 2 と 3)
  • 第3バイト上位: `4` (十進数の 4)
  • 第3バイト下位: `5` と 符号 (`C`)

符号のビットパターン

IBMのSystem/360アーキテクチャ以降、ハードウェアは以下のゾーン/パック形式の符号を標準的に認識する。

  • 正数 (+): `C`, `A`, `E`, `F` (標準生成は `C`)
  • 負数 (-): `D`, `B` (標準生成は `D`)
  • 符号なし / ゾーン値: `F` (UNSIGNED属性やDISPLAY形式など)

もし外部から連携されてきたファイルや、古い磁気テープのダンプ(GTF/IPCSなど)を見て、最後のニブルが `F` や `B` になっていたら、それはデータ異常のサインか、あるいは符号なしとして扱われている可能性が高い。ここを見落とすと、後続の算術演算で痛い目を見る。

—

2. レコード入出力とVSAMアクセスにおける注意点

メインフレームのバッチ処理において、FIXED DECIMALの真価が発揮されるのは、BSAM/QSAMによる順編成ファイルや、VSAM(KSDS/ESDS)への入出力時だ。

PL/Iでファイルを定義する際、`ENVIRONMENT(BLOCKED)` や `RECORD` 構造体を使用するが、ここでCOBOLの `COPY句` に相当する `%INCLUDE` メンバを扱うことが多い。
データベースや外部ファイルから読み込んだ生バイナリ(RAWデータ)をそのまま `FIXED DECIMAL` 変数にマップする場合、パディング(位置合わせ) に注意しなければならない。

PL/Iのデフォルトでは、構造体メンバは自然境界(Boundary)にアライメントされることがあるが、`FIXED DECIMAL` はバイト単位で連続して配置されるため、余計なパディングバイトが入らないよう、構造体定義には `UNALIGNED` 属性を明示するのがプロの鉄則だ。

1
/ 正しい構造体定義の例 /
01 1 REC-BUFFER UNALIGNED,
03 5 ACCT-NO FIXED DECIMAL(7,0), / 4バイト /
03 5 TRAN-AMT FIXED DECIMAL(9,25), / 5バイト /
03 5 TRAN-NAME CHARACTER(20); / 20バイト /

もし `UNALIGNED` を書き忘れると、コンパイラの最適化やCPUアーキテクチャの仕様によって予期せぬパディングが挿入され、ファイル上の物理レイアウトとメモリ上のレイアウトがズレてしまう。これは夜間バッチで最も恐ろしい「位置ズレバグ」の原因になる。

—

3. 異常系制御:ONユニットによるデータ例外(DATAEXCEPTION)の捕捉

万が一、外部ファイルから読み込んだ `FIXED DECIMAL` の領域に、不正なゾーン文字やパック10進数として無効なビットパターン(例:下位ニブルに `C` でも `D` でもなく `9` が入っているなど)が含まれていた場合、それを算術演算や代入で使用した瞬間に `DATAEXCEPTION`(S0C7アボートの原因) が発生する。

メインフレームのバッチ基盤では、S0C7でジョブを即座に異常終了させるだけでなく、PL/Iの `ON` ユニットを使って優雅に(あるいはログを残して安全に)トラップすることが可能だ。

1
/ DATAEXCEPTIONを捕捉するONユニットの実装例 /
ON DATAEXCEPTION
BEGIN;
DISPLAY(‘ 致命的エラー: パック10進数データ例外を検知しました ‘);
DISPLAY(‘不正なデータが検出されたため、デフォルト値で続行または安全に終了します。’);
/ 必要に応じてダンプ採取やエラーフラグのセットを行う /
GOTO ERROR-ROUTINE;
END;

実務では、レガシーデータの移行期において、どうしてもゴミデータや仕様書漏れのコードが混入しがちだ。コンパイラオプションで `STRPTR` や `CHECK(DECIMAL)` などを有効にしつつ、クリティカルな入出力処理の周辺では上記のような `ON` ユニットを張り巡らせておくことが、システム運用の品質を大きく左右する。

—

4. 実践的PL/Iコード例:FIXED DECIMALの操作とビルトイン関数

それでは、ここまでの知識を踏まえた実践的なPL/Iプログラムのコードを見てみよう。
このサンプルでは、FIXED DECIMAL変数の定義、`UNALIGNED` の使用、算術演算、そして文字列表現への変換(`PUT` やビルトイン関数)を行っている。

1
PROCESS XREF, MACRO, FLAG(I,I);
FINDBLK: PROC OPTIONS(MAIN);

/ —————————————————- /
/ ワーク変数の定義 /
/ —————————————————- /
DCL 1 WS-TRANSACTION UNALIGNED,
03 WS-ID FIXED DECIMAL(5,0) INIT(12345), 口座ID (3バイト) /
03 WS-BALANCE FIXED DECIMAL(11,2) INIT(9876543.21); 残高 (6バイト) /

DCL WS-EDIT-AREA CHARACTER(15); / 編集出力用バッファ /
DCL RC FIXED BINARY(31) INIT(0);

DISPLAY(‘=== PL/I FIXED DECIMAL (COMP-3) 制御サンプル開始 ===’);

/ 内部のパック10進数データをそのまま文字列表現にEDIT変換 /

  • 組み込み関数(BUILTIN)を活用して安全にフォーマットする

WS-EDIT-AREA = EDIT(WS-BALANCE) (P’ZZZ,ZZZ,ZZ9.99CR’);

DISPLAY(‘口座ID: ‘ || TRIM(TO_CHAR_SUB(WS-ID)));
DISPLAY(‘残高 (編集後): ‘ || WS-EDIT-AREA);

/ 算術演算のテスト:利息の計算 (精度とスケールの自動調整) /
BEGIN;
DCL WS-INTEREST FIXED DECIMAL(9,4);

  • 利率 0.0125 を掛け合わせる。PL/Iは演算時の精度を自動的に拡張・調整する。

WS-INTEREST = WS-BALANCE 0.0125;
DISPLAY(‘計算された利息: ‘ || EDIT(WS-INTEREST) (P’ZZZ,9.9999’));
END;

DISPLAY(‘=== 処理正常終了 ===’);
RETURN;

END FINDBLK;

コードのポイント解説

1. `UNALIGNED` の徹底: 構造体 `WS-TRANSACTION` に `UNALIGNED` を指定することで、余計なパディングを排除し、COBOLの `01` レコードレイアウトと1:1で互換性を保っている。
2. ピクチャー句(`EDIT` / `P’…’`): パック10進数で保持された数値を人間が読みやすいように編集出力する際、PL/Iの `EDIT` 編集機能は非常に強力だ。`CR` や `DB` などのクレジット/デビット表示も直感的に記述できる。
3. 演算時の精度伝播: `FIXED DECIMAL` 同士、あるいはリテラルとの演算において、コンパイラは中間結果の精度(Precision)を自動計算する。予期せぬ桁落ち(Overflow / FixedOverflow)を防ぐためにも、重要な計算の受け皿となる変数の定義桁数(pとq)は、十分な余裕を持って設計する必要がある。

—

おわりに:ベテランからのメッセージ

PL/Iの `FIXED DECIMAL` は、単なる数値型ではない。それはIBMメインフレームのハードウェアの歴史と、省メモリ・高精度計算の知恵が詰結した芸術品だ。

現代のオープン系言語(JavaやPythonなど)の浮動小数点演算に慣れた若いエンジニアからすると、「なぜ桁数や小数の位置をそこまで厳密に意識しなければならないのか」と疑問に思うかもしれない。しかし、金融機関で1円の狂いも許されない勘定系システムにおいて、このパック10進数が持つ「2進化10進数(BCD)による丸め誤差のなさ」は、今なお代替不可能な絶対の強みである。

マイグレーションやリビルドのプロジェクトであっても、この内部構造の理屈を身体で理解しているか否かで、トラブルシューティングのスピードは天と地ほどの差となって現れる。
ぜひ、日々の保守・開発の中で、ダンプリストやコンパイラのクロスマップを丹念に読み解く習慣をつけてほしい。君たちの健闘を祈る!

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