【実務・中級編】精度(Precision)のデフォルト規則とコンパイラオプション – PL/Iの基本構文とデータ制御実践ガイド

固定小数点数の「精度」に泣かされた夜はないか?

おい、最近入った若手が、こんなコード書いてモヤモヤ悩んでたんだ。「先輩、なんかバッチの金額計算で、桁あふれ(S0C7じゃない方だ)が起きたり、予期せぬ切り捨てが発生したりするんです……」ってな。

画面を覗き込んだら、案の定だよ。`DCL WK-AMT FIXED BINARY;` だの `DCL WK-QTY FIXED DECIMAL;` だの、精度を省略した変数宣言がオンパレード。コンパイラ任せのデフォルト値に運命を預けて、挙句の果てに本番データの巨大な数値に殴られたってわけだ。

メインフレームの現場で長く生き残りたいなら、データ型、特に固定小数点数(FIXED BINARY / DECIMAL)の精度(Precision)のデフォルト規則とコンパイラオプションの罠を身体に叩き込んでおく必要がある。今日はそのあたりを、実務の現場目線で徹底的に解剖してやろう。

—

1. 精度(Precision)を省略したときの「暗黙のルール」

PL/Iという言語は、プログラマが手を抜く(省略する)ことを許してくれる懐の深い言語……に見せかけて、背後で勝手に危なっかしいお節介を焼くヤローだ。

精度を省略して変数宣言したとき、コンパイラは一体どういう解釈をするのか?基本を押さえておくぞ。

FIXED DECIMAL の場合

`DCL VAR1 FIXED DECIMAL;` と書いた場合、コンパイラのデフォルトは通常 `FIXED DEC(5, 0)` だ。
整数部が5桁、小数部が0桁。つまり、最大でも `99999` までしか安全に扱えない。ここに十数桁の売上金額や口座残高をぶち込んだらどうなるか?上位桁が容赦なくブッ飛ばされるか、運悪く(あるいは運良く)CONVERSIONエラーが起きてバッチが異常終了する。

FIXED BINARY の場合

こいつはもう少し厄介だ。`DCL VAR2 FIXED BINARY;` と書いた場合、コンパイラオプションや環境(OS/VS, Enterprise PL/Iなど)によってデフォルトが変わるが、多くは `FIXED BIN(15, 0)`(半ワード:2バイト)または `FIXED BIN(31, 0)`(フルワード:4バイト)になる。
近年の Enterprise PL/I ではデフォルトが `BIN(31)` になっていることが多いが、古いソースや移植版だと `BIN(15)` で処理されて、最大 `32767` までしか入らないタイマー値やカウンターで盛大にハマる事故が後を絶たない。

—

2. コンパイラオプション(LIMITS)による挙動の変化

「うちのシステムはマイグレーションでコンパイラが変わった途端に動かなくなった」というトラブルの原因の多くは、このコンパイラオプション、特に `LIMITS` に隠されている。

Enterprise PL/I の `LIMITS` サブオプションでは、精度を省略したときのデフォルト動作を明示的に制御できる。

  • `LIMITS(FIXEDDEC(15), …)` のように指定されていれば、DECIMALのデフォルト精度を15桁に引き上げられる。
  • しかし、他人が書いた外部インクルード(COPY句や `%INCLUDE`)の構造体や、レガシーなソースをそのまま持ってきたときに、このグローバルな設定とソース内の暗黙の仮定がズレて、計算結果のスケール(小数点位置)が狂うことがある。

現場の鉄則として言っておく。「精度は絶対に省略するな。書け、面倒でも全変数に桁数を明示しろ」。これがバグを防ぐ一番の近道だ。

—

3. 実践:VSAM入出力とONユニット、そしてBUILTIN関数を駆使した堅牢なコード

口で言うだけじゃ説得力がないな。実際のバッチプログラムを想定したサンプルコードを見せてやろう。
VSAMファイルからレコードを読み込み、フィールドの属性を意識しながら計算を行い、例外(CONVERSIONやZERODIVIDE)をONユニットでエレガントに捕捉する構成だ。大文字ベースで記述している。

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

  • 修正モジュール名: CALCDEMO
  • 処理概要: VSAM入力データの固定小数点計算と例外制御のサンプル

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

— 1. 例外処理(ONユニット)の定義 ————————–

  • コンバージョンエラー(データ型不一致や桁あふれ)の捕捉

ON CONVERSION BEGIN;
DISPLAY(‘【重度警告】数値変換エラーが発生しました。修正が必要です。’);
GOTO ERROR_RTN;
END;

— 2. 変数宣言(精度を完全に明示している点に注目) ———-
DCL 1 VSAM-IN-REC,
5 IN-CUST-ID CHAR(8), — 顧客ID —
5 IN-UNIT-PRC FIXED DEC(9,2), — 単価: 9桁、小数2桁 —
5 IN-QTY FIXED BIN(31); — 数量: 4バイト2進数 —

DCL WK-TOTAL-AMT FIXED DEC(11,2) INIT(0); — 計算結果用 —
DCL 4K-TAX-RATE FIXED DEC(3,3) INIT(0.080); — 税率: 0.080 —
DCL IO-STATUS CHAR(2);

— 3. VSAMファイルオープン(模擬) ————————–
OPEN FILE(CUSTFILE) INPUT;

— 4. メイン処理ループ ————————————–
DO FOREVER;
READ FILE(CUSTFILE) INTO(VSAM-IN-REC);
IF ENDFILE(CUSTFILE) THEN LEAVE;

— 数量と単価の掛け算(異なるデータ型間の演算) —

  • FIXED BIN と FIXED DEC の混合演算では、PL/Iが内部で自動的に型変換を行う。
  • その際、暗黙の精度ルールに頼らず、BUILTIN関数で安全に丸めるか、
  • 明示的な変数で受けることが極めて重要。

WK-TOTAL-AMT = IN-UNIT-PRC FLOAT(IN-QTY);

  • ※ここではあえてFLOATを挟む例を示したが、実務の金額計算なら
  • FIXED DEC同士、あるいはDECIMAL()ビルトイン関数で精度を制御するべき。

— 小数点以下の端数処理に ROUND ビルトイン関数を使用 —

  • 第2引数で「小数点以下何桁に丸めるか」を明示する

WK-TOTAL-AMT = ROUND(WK-TOTAL-AMT (1 + 4K-TAX-RATE), 2);

DISPLAY(‘顧客ID: ‘ || IN-CUST-ID || ‘ 請求額: ‘ ||
TRIM(CHAR(WK-TOTAL-AMT)));

END;

CLOSE FILE(CUSTFILE);
RETURN;

— 5. エラー処理ルーチン ————————————–
ERROR_RTN:
DISPLAY(‘異常終了ルーチンを通過しました。ダンプを確認してください。’);
SIGNAL ERROR;

END CALCDEMO;

コードのここがポイントだ

1. 精度の完全明示
`IN-UNIT-PRC FIXED DEC(9,2)` や `IN-QTY FIXED BIN(31)` のように、整数部と小数部、あるいはビット数を完全にコード上で固定している。これにより、コンパイラオプションの変更や異なる環境へのマイグレーションを行っても、予期せぬ精度の変動を防ぐことができる。
2. `ROUND` ビルトイン関数の活用
金融系や基幹システムのバッチにおいて、四捨五入や切り捨てのルールは死活問題だ。演算結果をそのまま代入するのではなく、`ROUND(値, 2)` のように明示的に丸め処理を入れることで、コンパイラのデフォルトのスケール調整による思わぬ桁落ちを防いでいる。
3. `ON CONVERSION` による安全網
レガシーなVSAMや外部ファイルには、時と場合によってゴミデータ(スペースや非数値文字)が混入していることがある。ONユニットを張っておくことで、突然のS0C7やU4038でバッチ全体が泥沼にハマるのを防ぎ、ログに痕跡を残してクリーンに異常終了させることが可能だ。

—

先輩からのメッセージ:動くからいい、ではなく「意図して動かす」

PL/Iは古い言語だと言われる。だが、金融・流通・公共のバックボーンを支えるメインフレーム上で、今なおこれほど強力で厳密なデータ制御を行える言語はそうそうない。

「コンパイラが勝手にやってくれるだろう」という甘えは、数年後の大規模改修のときに必ず自分(あるいは後輩)の首を絞めることになる。
変数宣言のカッコの中にある数字一つひとつに、設計者の意図とビジネスロジックの重みが詰まっている――その気概を持って、今日のコードから `FIXED DEC` や `FIXED BIN` の精度をすべて見直してみなさい。

頼むぞ、次の基幹系を支えるのはお前たちなんだからな。

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