【実務・中級編】PIC ‘V’による仮想小数点位置の管理と演算時の挙動 – PL/Iの基本構文とデータ制御実践ガイド

おい、調子はどうだ?
今日もどこかのバッチジョブがABENDして、syslogの海に溺れかけている頃じゃないか。

今回は、PL/Iのデータ制御において、多くの若手エンジニアが一度はハマり、そしてベテランでも油断すると痛い目を見る「仮想小数点(PIC ‘V’)」について徹底的に解説しよう。

COBOLを使っていた連中なら「COMP-3のS9(4)V99だろ?」と高をくくるかもしれないが、PL/Iの世界では、この「V」が絡んだ算術演算とスケール調整の裏側を知っているかいないかで、夜間バッチの数値ズレやDECIMALFIX (S0C7) アラートへの耐性が全く変わってくる。

今日のテーマをしっかりモノにして、現場のトラブルバスターへの階段を一段上がってくれ。

1. 仮想小数点 ‘V’ とは何か?:物理的ストレージの幻想

まず大前提として、ピリオド(`.`)やカンマ(`,`)などの実在する文字とは異なり、PIC ‘V’ はメモリ上に1バイトも領域を占有しない「仮想的(Virtual)」な小数点だ。

例えば、定義された変数を考えてみよう。

1
DCL WK_AMOUNT PIC ‘9999V99’ DECIMAL FIXED;

この変数が持つメモリ上の実体は、単なる6桁のゾーン10進数(あるいはパック10進数)の数字の羅列に過ぎない。
もしこの変数に `1234.56` という値を入れた場合、メモリ上には `F1F2F3F4F5F6`(ゾーン形式の場合)というバイト列が並ぶだけで、どこにも「ドット」を表すビットは存在しないのだ。

コンパイラとハードウェア(あるいはランタイムライブラリ)は、「左から4桁目が整数部、右側2桁が小数部である」というルール(スケールファクター)を暗黙的に記憶しており、演算時に勝手にその位置を合わせ込んでいる。これがPL/Iの強力かつデリケートなところだ。

2. 算術演算時の内部シフトとスケール調整のカラクリ

現場で最も恐ろしいのは、異なる小数点位置を持つ変数同士で四則演算を行ったとき、コンパイラが裏側で何をやらかしているかを知らないことだ。

特に乗算(MULTIPLY)除算(DIVIDE)、そして代入時の暗黙のスケール調整では、小数点以下の桁数(スケール)が一致するように、内部で自動的に10の累乗によるシフト(位取り合わせ)が行われる。

加算・減算(ADD / SUBTRACT / `+` / `-`)のルール

足し算と引き算では、両者の「小数点位置」が厳密に一致するように、桁数が少ない方の値が内部でシフトされる。

  • `PIC ’99V9’` と `PIC ’99V99’` を足す場合、前者は内部で10倍されて小数第2位まで拡張されてから加算される。

乗算(MULTIPLY / “)のルール

かけ算の場合、結果のスケールは「被乗数のスケール + 乗数のスケール」になる。

  • `PIC ’99V9’`(小数1位) と `PIC ’99V9’`(小数1位) を掛け合わせると、数学的には小数第2位(`V99`)になる。
  • もしこれを `PIC ’99V9’` の変数に受けようとすると、コンパイラは勝手に丸め(ラウンド)を行うか、あるいは桁あふれ(オーバーフロー)の危険性を検知して警告を発する。

3. 実践:VSAM入出力とONユニットを伴う安全な数値演算プログラム

百聞は一見にしかず。実際のメインフレームのバッチプログラムを想定した、実用的なPL/Iソースコードを見てみよう。
VSAM(KSDS)から読み込んだ金額データに対して、仮想小数点を使った税率計算を行い、スケール調整の挙動を安全にハンドリングする例だ。

1
/ —————————————————————- /
/ PROGRAM-ID: VSRCALC /
/ REMARKS : 仮想小数点Vを用いた安全なスケール調整と算術演算のサンプル /
/ —————————————————————- /
VSRCALC: PROC OPTIONS(MAIN);

/ — 宣言部 — /
/ 入力ファイル(VSAM KSDS)のレコード定義 /
DCL 1 IN_REC,
5 IN_ID CHAR(5), / 顧客ID /
5 IN_PRICE PIC ‘9(7)V99’ DECIMAL; / 単価(小数2位) /

/ 出力用ワークおよび計算変数 /
DCL WK_TAX_RATE PIC ‘V999’ DECIMAL VALUE(.08); / 消費税率(小数3位: 0.080) /
DCL WK_SUBTOTAL PIC ‘9(9)V99’ DECIMAL; / 小計(小数2位) /
DCL WK_TAX_AMT PIC ‘9(7)V99’ DECIMAL; / 消費税額(小数2位) /

DCL EOF_FLG CHAR(1) INIT(‘OFF’);

/ オン・ユニット(条件制御):データ例外(S0C7系など)の捕捉 /
ON CONVERSION
BEGIN;
DISPLAY(‘【SEVERE】データ変換エラー(S0C7類似)を検知しました。’);
DISPLAY(‘問題のあったレコードID: ‘ || IN_ID);
SIGNAL ERROR; / 異常終了へ誘導 /
END;

/ — 処理部:ファイルオープン — /
OPEN FILE(INFILE) INPUT, FILE(OUTFILE) OUTPUT;

/ メインループ /
DO WHILE (EOF_FLG = ‘OFF’);

READ FILE(INFILE) INTO(IN_REC);

IF EOF_FLG = ‘ON’ THEN LEAVE;

————————————————————
/ 【重要】仮想小数点Vが絡む算術演算とスケール調整の実際 /
/ /
/ 単価(PIC ‘9(7)V99’ = スケール2) /
/ と /
/ 税率(PIC ‘V999’ = スケール3) /
/ を掛け合わせると、数学的結果は「スケール 2 + 3 = 5」 /
/ すなわち内部的に PIC ‘…V99999’ として計算される。 /
————————————————————

/ 1. 税額の算出(小数点位置の自動調整と丸め処理) /
/ ROUND(expression, n) 内蔵関数を使い、意図しない切り捨てを防ぐ /
WK_TAX_AMT = ROUND(IN_PRICE WK_TAX_RATE, 2);

/ 2. 税込小計の計算(異なるスケール同士の加算) /
/ IN_PRICE (V99) と WK_TAX_AMT (V99) は同スケールなのでそのまま加算 /
WK_SUBTOTAL = IN_PRICE + WK_TAX_AMT;

/ デバッグ用トレース出力(BUILTIN関数 TRIM使用) /
DISPLAY(‘CUSTOMER: ‘ || TRIM(IN_ID) ||
‘ / TAX: ‘ || TRIM(WK_TAX_AMT));

/ 出力ファイルへの書き出し処理省略(WRITE文など) /

END;

/ — 終了処理 — /
CLOSE FILE(INFILE), FILE(OUTFILE);

RETURN;

END VSRCALC;

4. ベテランが教える現場のデバッグとコーディングの極意

このコードを見て、「単に掛け算して代入しているだけじゃないか」と思ったそこのお前、甘い。実務の現場ではここで以下の罠にハマる。

罠1:暗黙の桁落ち(Truncation)とオーバーフロー

もし計算結果のスケールが、代入先変数のスケールを上回っていたり、整数部をはみ出したりした場合、PL/Iはデフォルトで左側または右側の切り捨てを行う。これが大規模バッチの「数円のズレ」を引き起こす温床だ。

  • 対策: 小数点以下の演算を行う際は、必ず `ROUND` ビルトイン関数を使用し、どの桁で丸めるかをプログラマの意思として明示すること。コンパイラ任せにしては絶対にダメだ。

罠2:ON CONVERSION(条件制御)の過信

先ほどのコードで `ON CONVERSION` を仕込んでいるが、これはあくまで「文字データの中に数値として不正な文字(スペースや英字)が混ざっていた場合」の救済措置に過ぎない。
「仮想小数点を意識せずに桁あふれしたデータ」は、コンバージョンエラーにならずに単なる上位桁の切り捨て(ハイオーダー・ドロップ)を引き起こすことがあるため、事前のデータ定義チェック(PICの有効桁数確認)が何よりも重要だ。

おわりに

メインフレームの寿命がどうこうと言われて久しいが、基幹を支える勘定系や大量データ処理の現場では、今なおPL/Iの高速な演算処理と厳密なデータ制御が不可欠だ。

「たかがV、されどV」。
仮想小数点Vの挙動を完全に手の内に収めたとき、お前はもう「言われた通りにコードを直すだけのプログラマ」ではなく、「システム全体を俯瞰できる一流のメインフレームアーキテクト」に一歩近づいているはずだ。

次のバッチ改修では、コンパイルリストのクロスリファレンスとデータ属性を隅々まで睨みつけながら、完璧なスケール設計を見せてくれよ。期待しているぞ!

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