おい、調子はどうだ?
今日もどこかのバッチジョブが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の挙動を完全に手の内に収めたとき、お前はもう「言われた通りにコードを直すだけのプログラマ」ではなく、「システム全体を俯瞰できる一流のメインフレームアーキテクト」に一歩近づいているはずだ。
次のバッチ改修では、コンパイルリストのクロスリファレンスとデータ属性を隅々まで睨みつけながら、完璧なスケール設計を見せてくれよ。期待しているぞ!
