【実務・中級編】定数定義における精度指定の罠 – PL/Iの基本構文とデータ制御実践ガイド

お疲れ様です。今日も元気に夜間バッチのJCLログと格闘していることと思います。

メインフレームの現場では、日々数千万件におよぶデータを処理する基幹バッチが稼働しています。その心臓部であるPL/Iプログラムの保守において、「なぜか本番データだとオーバーフローする」「テスト環境では通ったのに、桁あふれ(S0C7とは限らないのが厄介なところです)で異常終了する」といったミステリーに直面したことはありませんか?

その原因の多くは、実は複雑なアルゴリズムではなく、「定数定義(リテラル値)における精度指定の罠」に潜んでいます。今回は、FIXED BINARYとFIXED DECIMALの精度が暗黙のうちにどう決まり、それが式全体の計算結果やVSAMファイルへの書き込み、さらにはONユニットの制御にどのような波及効果をもたらすのか、現場のノウハウを交えて徹底的に解説しましょう。

—

1. リテラル値の「見かけ」に騙されるな:暗黙の精度の正体

PL/Iのコードを書いているとき、私たちはついつい次のような直感でコードを書きがちです。

1
DCL WK_COUNT FIXED BIN(15) INIT(0);

「`0`だから小さくて十分だろう」——ええ、その通りです。この初期値の `0` は問題ありません。しかし、これが計算式や条件判定の中にふと現れたリテラルになると話が変わります。

FIXED DECIMAL の暗黙の精度

例えば、ソースコード中に `12345` という数字を書いたとします。これを属性明示なしでそのまま使うと、コンパイラはこれを FIXED DECIMAL (5, 0) として扱います。
では、小数点を含む `123.45` はどうでしょう? これは FIXED DECIMAL (5, 2) になります。
問題は、「リテラルの文字数(符号を除く)が、そのままDECIMALの精度(全体の桁数)のデフォルトになる」という言語仕様です。

FIXED BINARY の暗黙の精度

一方、バイナリリテラルや、コンテキストによってBINARYとして評価される場合、デフォルトの精度はコンパイラの仕様(通常はFIXED BINARY(15) または (31))に依存しますが、計算の途中でリテラルが絡むと、PL/Iの複雑な「算術式のスケーリング規則(Expression Evaluation Rules)」が裏で猛威を振るいます。

特に恐ろしいのは、「何気なく書いた定数のせいで、中間結果の精度が勝手に拡張され、あるいは切り捨てられ、予期せぬ精度ロスや桁あふれを引き起こす」という現象です。

—

2. 実践コード:VSAM入出力とONユニットを伴うバッチ処理の罠

百聞は一見にしかず。実際のメインフレーム開発現場を想定し、VSAM(KSDS)のマスターファイルを読み込み、単価計算を行って別ファイルに書き出すバッチプログラムの骨子を見てみましょう。

ここに、「定数の精度指定をサボったがためにハマる」典型的な罠を仕込んであります。

1
/ —————————————————————- /
/ プログラム名: MSTRCALC /
/ 概要 : VSAMマスター読み込みと金額計算バッチ /
/ —————————————————————- /
MSTRCALC: PROC OPTIONS(MAIN);

DCL VSAM_IN FILE RECORD INPUT ENV(VSAM);
DCL VSAM_OUT FILE RECORD OUTPUT ENV(VSAM);

/ レコード構造体:すべてFIXED DECIMALで定義されていると仮定 /
DCL 1 IN_REC,
5 IN_ID CHAR(5),
5 IN_QTY FIXED DEC(7, 0), / 数量 /
5 IN_UPRICE FIXED DEC(9, 2); / 単価 /

DCL 1 OUT_REC,
5 OUT_ID CHAR(5),
5 OUT_TOTAL FIXED DEC(11, 2); / 金額合計 /

DCL EOF_FLAG CHAR(1) INIT(‘OFF’);
DCL WK_CALC_AMT FIXED DEC(13, 2); / 計算用ワーク /

/ 異常終了(CONVERSION等)をキャッチするONユニット /
ON CONVERSION
BEGIN;
DISPLAY(‘ 致命的エラー: データ変換または数値オーバーフローが発生しました ‘);
DISPLAY(‘ 処理を中断します ‘);
CLOSE FILE(VSAM_IN);
CLOSE FILE(VSAM_OUT);
SIGNAL ERROR;
END;

/ ファイルオープン /
OPEN FILE(VSAM_IN) INPUT, FILE(VSAM_OUT) OUTPUT;

/ 読み込みループ /
DO WHILE (EOF_FLAG = ‘OFF’);
READ FILE(VSAM_IN) INTO(IN_REC);
IF EOF_FLAG = ‘ON’ THEN LEAVE;

————————————————————
/ 【罠の核心】 /
/ 数量(7桁) × 単価(9.2桁) の計算 /
/ ここで「1.08」(消費税率など)を直書きしている点に注目 /
————————————————————
WK_CALC_AMT = (IN_QTY IN_UPRICE) 1.08;

/ 算術ビルトイン関数を用いた丸め処理 /
OUT_TOTAL = ROUND(WK_CALC_AMT, 2);
OUT_ID = IN_ID;

WRITE FILE(VSAM_OUT) FROM(OUT_REC);
END;

CLOSE FILE(VSAM_IN);
CLOSE FILE(VSAM_OUT);

RETURN;

END MSTRCALC;

このコードに隠された「恐ろしい仕様」

上記のコードで、`WK_CALC_AMT = (IN_QTY IN_UPRICE) 1.08;` という計算を行っています。
一見、何の問題もないように見えますよね?

しかし、ここで右辺に出現する `1.08` というリテラル値。前述の通り、これはコンパイラによって FIXED DEC(3, 2) として解釈されます。
PL/Iの乗算の精度規則では、オペランドの精度から結果の精度を決定します。

  • `IN_QTY` (7, 0) と `IN_UPRICE` (9, 2) の積は、精度 `(16, 2)` になります。
  • さらにそれに `1.08` (3, 2) を掛け合わせるとどうなるか……。

コンパイラや処理系、あるいは中間結果のプッシュダウンの仕様によっては、中間バッファの桁数制限(DECIMALの場合は最大15桁または31桁)に抵触するか、あるいは小数点以下の桁数が膨れ上がり、代入先の `WK_CALC_AMT`(13, 2)に入りきらなくなって最高位桁でオーバーフローを起こすのです。

テストデータが小さいうちはこのバグは絶対に顕在化しません。本番の本物のビッグデータ(例えば数量が数百万個、単価が高いレコード)が流れてきた瞬間、突然 `ON CONVERSION` が発火してバッチが異常終了します。あるいは、最悪の場合、ONユニットが定義されていなければ、静かに下位桁が切り捨てられたり、S0C7に近いデータ破損を引き起こしたりします。

—

3. レコード入出力とVSAMアクセスにおける実務上の防衛策

メインフレームの移行プロジェクトやレガシー保守において、他言語(COBOLなど)からPL/Iへ移行したエンジニアが最もハマるのがこの「リテラルの精度」です。COBOLではピクチャー句で厳密に受けても、計算式中のリテラルはコンテキストに合わせてよしなに扱われることが多いですが、PL/Iは言語仕様として式の精度の伝播が非常にシビアです。

VSAMへのアクセスや、外部ファイルとの間でレコード構造体(`1 IN_REC` のようなグループ項目)をやり取りする際、以下のコーディング標準をチーム全体で徹底してください。

① マジックナンバー(直書きリテラル)を絶対に計算式に入れない

計算に使う係数や定数は、必ずあらかじめ適切な属性を明示した変数として定義(DCL)し、初期値を設定してから式に組み込んでください。

1
/ 良い例:定数は必ず属性を明示してDCLする /
DCL TAX_RATE FIXED DEC(3, 2) INIT(1.08);
DCL WK_CALC_AMT FIXED DEC(15, 2); / 余裕を持った桁数設計 /

WK_CALC_AMT = (IN_QTY IN_UPRICE) TAX_RATE;

これだけで、コンパイラが勝手にリテラルの精度を推論して予期せぬ中間精度を生み出すリスクを完全にシャットアウトできます。

② 明示的な型キャスト(ビルトイン関数の活用)

どうしても動的な計算が必要な場合や、異なる精度・基数(FIXED BINARY と FIXED DECIMAL)を混在させる場合は、`BINARY()` や `DECIMAL()` などのビルトイン関数を使って、意図的に精度をキャスト(変換)する技術がプロの技です。

1
/ 精度を明示的にコントロールする例 /
WK_CALC_AMT = DECIMAL( (IN_QTY IN_UPRICE) , 15, 2) TAX_RATE;

このように中間結果を一度 `DECIMAL(15, 2)` に丸める(あるいは精度を固定する)ことで、後続の演算への影響を完全に制御下に置くことができます。

③ ONユニットによるフェイルセーフの義務化

万が一、予期せぬ桁あふれやデータ不整合(スペース混入の数値項目など)が発生した際、黙ってゴミデータを書き込ませないために、ファイル入出力や算術演算を行うプログラムでは必ず `ON CONVERSION` や `ON ERROR` を適切に配置してください。
ただし、単に異常終了させるだけでなく、上記のコード例のように「どのファイル・どのタイミングでエラーになったか」をDISPLAY文でJCLのジョブスプールに出力し、原因特定を容易にするログ設計がベテランの仕事というものです。

—

4. まとめ:先輩から後輩へ伝えたいエンジニアリングの哲学

メインフレームのPL/Iプログラムは、何十年も前に書かれたレガシーコードであっても、今なお企業の基幹データを支え続けています。動いているコードに手を入れるとき、私たちは「動いているから大丈夫」という幻想を捨てなければなりません。

特に今回取り上げた「定数定義の精度指定」のような細部へのこだわりは、単なるコーディング作法ではなく、「本番障害を起こさないための防衛プログラミング」そのものです。

  • リテラルを計算式にそのまま直書きしない。
  • 中間変数の精度(整数部・小数部)は、最大値を想定して余裕を持たせる。
  • 異種データ型や異なる精度が混ざる式では、ビルトイン関数で明示的にキャストする。

この鉄則を守るだけで、夜中に突然「S0C7だ!」「計算がおかしい!」と運用担当者から叩き起こされるリスクを劇的に減らすことができます。

さあ、今日も堅牢で美しいPL/Iコードを書き上げ、無事に夜間バッチを完走させましょう!

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