こんにちは、現場のエンジニアの皆さん。
毎日の保守や、いつ終わるとも知れないマイグレーション調査、本当にお疲れ様です。
メインフレームの現行システムを覗いてみると、COBOLの海原の中に、ひっそりと、しかし強烈な存在感を放って生き延びているPL/Iプログラムに出会うことがあります。変数名に予約語の概念がなく、かつて「何でもできる万能言語」として設計されたがゆえの自由度の高さが、何十年経った今、我々保守エンジニアの頭を悩ませる最大の原因になっていたりもしますよね。
さて、今回はそんなPL/Iのデータ制御において、避けて通れない、そして一歩間違えると「月次バッチで金額が100倍になる」という洒落にならない大事故を引き起こすPIC ‘V’(仮想小数点)について、現場の知見を総動員して徹底解説します。
—
1. なぜ「物理的」ではなく「仮想的」な小数点なのか?
金融系や基幹システムのデータベース(VSAMやDB2のテーブル)を設計する際、金銭データや金利データをどのように保持するかは極めて重要です。浮動小数点数(FLOAT)を使えば楽ですが、丸め誤差が命取りになる金融の世界ではご法度。かといって、すべての金額に実際の小数点(`.`)を物理的に持たせると、ストレージの無駄遣いになるだけでなく、レコード長の管理や外部システム(C言語やJavaなど)とのインターフェースで余計なパース処理が必要になります。
そこで登場するのが、「数値としては小数点以下の位を持っているが、データ上にはドット(.)を占有しない」という、PL/Iの PIC ‘V’(Virtual Decimal) です。
例えば、`PIC ‘9(7)V99’` と定義された10桁の領域があるとします。
- データ上の実態: `123456789` (物理的なドットは存在せず、ただの9桁の数字の羅列)
- プログラムの認識: `1234567.89` (Vの位置を境に、整数部7桁、小数部2桁として扱われる)
この「見た目は整数、頭の中では小数」という二面性こそが、Vの最大の特徴であり、同時に若手エンジニアが最初にハマる罠でもあります。
—
2. 演算時の桁合わせルールとコンパイラの裏側
PL/Iの優れた(そして恐ろしい)ところは、異なるピクチャを持つ変数同士で四則演算を行っても、コンパイラが勝手に小数点(V)の位置を揃えて演算してくれる点です。COBOLのように、わざわざ `COMPUTE` の前に `MOVE` で桁合わせを意識する必要が(基本的には)ありません。
しかし、ここに落とし穴があります。「代入時の暗黙の丸め(Truncation)」です。
演算結果を別の変数に代入する際、受取側の変数(RECEIVER)の小数部の桁数が演算結果より少ない場合、コンパイラは容赦なく下位桁を切り捨て(あるいは四捨五入ではなく切り捨てがデフォルト)ます。さらに、整数部があふれた場合は、高確率で S0C7(データ例外:不当な十進数) や、PL/Iランタイムであれば `CONVERSION` エラー(ON-unitで捕捉可能)を引き起こします。
実務でバッチ改修を行う際は、「演算の途中結果を保持するワークエリアのPIC句が、計算の精度と最大値を完全に網羅しているか」を、コンパイラ任せにせず必ず自分の目で検証しなければなりません。
—
3. 実践コード:VSAM入出力とONユニット制御を含む完全サンプル
百聞は一見に如かず。ここでは、VSAM(KSDS)から生データを読み込み、仮想小数点を持つフィールド同士で単価計算を行い、結果を別のファイルに出力する典型的なバッチプログラムの骨組みをお見せします。
あえて大文字で記述し、実務でそのまま使えるレベルのコメントと、エラーハンドリング(ON-unit)を組み込んでいます。
- プログラム名: CALCVSMP
- 概要 : 仮想小数点(PIC ‘V’)を用いた金額計算とVSAM入出力
CALCVSMP: PROC OPTIONS(MAIN);
/ — 1. 変数・ファイル宣言 — /
DCL IN_FILE FILE RECORD INPUT ENV(VSAM);
DCL OUT_FILE FILE RECORD OUTPUT;
/ 入力レコード構造(VSAMから読み込む生データ想定) /
/ 物理的な小数点はないが、内部的にVの位置を意識する /
DCL 1 IN_REC,
5 IN_CUST_ID CHAR(5), / 顧客ID /
5 IN_QTY PIC ‘9(5)’, / 数量 (整数5桁) /
5 IN_UNIT_PRC PIC ‘9(5)V99’; / 単価 (7桁,V下2桁)/
/ 出力レコード構造 /
DCL 1 OUT_REC,
%INCLUDE CUSTREC; / 外部COPY句を想定 /
/ 代入先の金額領域:整数9桁、小数2桁 /
5 OUT_TOTAL_AMT PIC ‘9(9)V99’; / 合計金額 /
DCL EOF_FLG CHAR(1) INIT(‘0’);
/ — 2. 異常系制御 (ON-unit) — /
/ データ異常(PIC違反や数値化不能)を捕捉する鉄壁の備え /
ON CONVERSION BEGIN;
DISPLAY(‘ 致命的エラー: 数値変換例外が発生しました ‘);
DISPLAY(‘該当顧客ID: ‘ || IN_CUST_ID);
DISPLAY(‘問題の単価データ: ‘ || IN_UNIT_PRC);
SIGNAL ERROR; / 異常終了へボタリング /
END;
/ — 3. ファイルオープン — /
OPEN FILE(IN_FILE), FILE(OUT_FILE);
/ 終了条件の設定 /
ON ENDFILE(IN_FILE) EOF_FLG = ‘1’;
/ — 4. メインループ — /
READ FILE(IN_FILE) INTO(IN_REC);
DO WHILE (EOF_FLG = ‘0’);
/ 【重要】仮想小数点を含む演算処理 /
/ IN_QTY(整数) と IN_UNIT_PRC(V99) の掛け算 /
/ PL/Iは自動的にVの位置を考慮して小数点を調停する /
OUT_TOTAL_AMT = IN_QTY IN_UNIT_PRC;
/ 必要に応じたビルトイン関数による丸め処理( ROUND ) /
/ 例: 小数第3位を四捨五入して第2位までにする場合 /
/ OUT_TOTAL_AMT = ROUND(IN_QTY IN_UNIT_PRC, 2); /
/ 出力ファイルへ書き出し /
WRITE FILE(OUT_FILE) FROM(OUT_REC);
/ 次レコード読み込み /
READ FILE(IN_FILE) INTO(IN_REC);
END;
/ — 5. クローズ処理 — /
CLOSE FILE(IN_FILE), FILE(OUT_FILE);
DISPLAY(‘CALCVSMP: 正常終了しました。’);
RETURN;
END CALCVSMP;
—
4. 現場のシニアから送るデバッグと改修のコツ
最後に、私がこれまで数々のレガシーシステム改修で培ってきた、PIC ‘V’ にまつわる「現場の知見」をいくつか授けましょう。
1. DUMPオプションを恐れるな
万が一、本番バッチで `CONVERSION` や `S0C7` が起きたとき、SYSUDUMPが出力されるようJCLが組まれているか確認してください。PL/Iのストレイジダンプを読む際は、変数のピクチャ定義と実際のHEXダンプ(Zone 10進なのかPacked 10進なのか)を突き合わせることで、どこでVの解釈が狂ったかが一発で分かります。
2. 外部連携時の「見えない小数点」に殺されるな
JavaやPython、あるいは最近のWebAPIなどとデータを連携する場合、相手は「物理的な小数点(`.`)」や「JSONの数値型」を期待しています。PL/I側で `PIC ‘9(5)V99’` のままバイナリで渡すと、向こうではゴミデータに見えます。外部インターフェースに絡む改修では、必ず `EDIT` ビルトイン関数などを用いて明示的に小数点を付与した文字列(CHAR)に変換してから渡すこと。これ、鉄則です。
3. `ROUND` ビルトイン関数をケチるな
自動演算に頼りすぎると、コンパイラのバージョンや最適化オプション(`OPT(2)` など)の差異によって、微小な端数処理の挙動が変わるリスクがごく稀にあります。金額計算を行う箇所では、面倒でも `ROUND( expression, precision )` を明示的にコーディングする癖をつけましょう。
PL/Iは、書き手の意図を最大限に汲み取ろうとするあまり、時にコンパイラが「良かれと思って」裏で複雑な型変換を行っています。だからこそ、我々アーキテクトや保守エンジニアが言語の挙動を完全に手玉に取らなければなりません。
今回の解説が、皆さんの日々のメインフレーム保守、そして次なるモダナイゼーションへの確かな足掛かりとなれば幸いです。それでは、次の現場でお会いしましょう。
