おい、最近デバッグの途中で「なんでこんなところでデータ例外(S0C7)が起きるんだ…?」と頭を抱えていないか?
メインフレームの基幹システムで長年動いているPL/Iプログラムの改修をしていると、前任者が書いた泥臭いコードに出会うことが多い。特に、DECIMAL(固定小数点、いわゆるパック十進数)とBINARY(二進数)、そしてFLOAT(浮動小数点)が入り交じった演算で、暗黙の型変換(Implicit Conversion)に足をすくわれるトラブルは、レガシー開発の現場では「あるある」の悪夢だ。
PL/Iという言語は、変数名に予約語がない(キーワードの文脈依存性がある)という変態的な…いや、懐の深い仕様を持っている。それゆえに、書き手がいい加減なコードを書くと、コンパイラが勝手に気を利かせて型変換を行い、夜間バッチの土壇場で桁あふれや精度落ちを引き起こす。
今回は、この「暗黙の型変換」という魔物に頼らず、プログラマが意図した通りに型と精度をコントロールするためのビルトイン関数 `BINARY`、`DECIMAL`、`FLOAT` の実戦的な使い方を、VSAMファイルの入出力やONユニットのトラップ制御を交えて徹底的に叩き込んでやろう。
—
1. なぜ「暗黙の型変換」は現場の地雷なのか?
PL/Iは非常に寛容な言語だ。異なるデータ型の間で演算を行おうとすると、コンパイラが自動的に型を合わせに行ってくれる。一見すると親切だが、これが大規模バッチの現場では「サイレントキラー」になる。
例えば、VSAMファイルから読み込んだ外部形式の数値(DISPLAY形式など)や、外部システムから連携されたパック十進数(COMP-3)の領域を、内部の計算用レジスタ(BINARY)に落とし込む際、桁数や精度の定義を曖昧にしたまま演算させるとどうなるか。
コンパイラは勝手に丸め誤差を生じさせたり、最悪の場合、データ例外(S0C7)やサイズエラー(ON-conditionの `SIZE`)を引き起こしてジョブを異常終了させる。
プロなら、コンパイラの「お節介」に頼るな。「どのタイミングで、どのような精度(Precision)で型を変換するか」を、ビルトイン関数を使ってコード上で明示的に支配するのだ。これができるかどうかで、メインフレームエンジニアとしての寿命が決まる。
—
2. 3つの型変換ビルトイン関数の仕様と勘所
明示的変換を行うための主役は以下の3つだ。すべて `BUILTIN` 属性を明示して使うのが、我がチームのコーディング標準である。
1. `BINARY(expression [, p [, q]])`
- 表現を二進数(FIXED BINARY または FLOAT BINARY)に変換する。
- $p$ は全体のビット数(または精度)、$q$ は小数点位置。
- 勘所:固定小数点演算のパフォーマンスを極限まで高めたいときや、内部計算用のワーク変数に値をロードするときに使う。
2. `DECIMAL(expression [, p [, q]])`
- 表現を十進数(FIXED DECIMAL または FLOAT DECIMAL)に変換する。
- 勘所:金額や数量など、1円の狂いも許されないビジネスロジックの結果を、VSAMや外部ファイルに出力する直前のフォーマット調整に使う。
3. `FLOAT(expression [, p])`
- 表現を浮動小数点数に変換する。
- 勘所:科学技術計算や、極端に大きな数値・小さな数値を扱う統計処理の際、桁あふれを防ぐために一時的に退避させる。
—
3. 実践!VSAMアクセスとONユニットを伴うPL/Iプログラム例
百聞は一見に如かず。実際の業務バッチを想定したサンプルコードを見せよう。
このコードでは、VSAM(KSDS)から読み込んだパック十進数の売上データを、`BINARY` 関数で安全に内部計算用に変換し、消費税を計算した上で、再び `DECIMAL` 関数で厳密な精度を指定して出力用レコードに組み立てている。さらに、万が一のデータ崩壊に備えて `SIZE` 条件を捕捉する `ON` ユニットも実装している。
1
/——————————————————————–不必-/
/ プログラム名: SLM0030I (売上集計・税額計算バッチ) /
/ 概要: VSAM(KSDS)から入力された生データを読み込み、明示的型変換を /
/ 用いて安全に計算を行い、結果を別ファイルに出力する。 /
/——————————————————————–不必-/
SLM0030I: PROC OPTIONS(MAIN);
DCL / VSAM入力レコード定義 (外部形式パック十進数) /
IN_REC,
3 IN_CUST_ID CHAR(5), / 顧客ID /
3 IN_AMT_DEC FIXED DEC(11,2) COMP3;/ 売上金額(11桁,小数2桁) /
DCL / 印刷・出力用レコード定義 /
OUT_REC,
$OUT_CUST_ID CHAR(5),
$OUT_TAX_DEC FIXED DEC(11,2) COMP3;/ 税額出力領域 /
DCL / 内部計算用ワーク変数 /
WK_AMT_BIN FIXED BIN(31) BUILTIN;/ 計算用バイナリ /
WK_TAX_BIN FIXED BIN(31) BUILTIN;
DCL / ファイル定義 /
VSAM_IN FILE RECORD SEQUENTIAL INPUT,
RPT_OUT FILE RECORD SEQUENTIAL OUTPUT;
DCL EOF_FLG CHAR(1) INIT(‘0’);
/ ——————————————————– /
/ ONユニット定義: サイズエラー(桁あふれ)のトラップ /
/ ——————————————————– /
ON SIZE BEGIN;
DISPLAY(‘ 致命的エラー: 演算結果の桁あふれを検知しました。’);
DISPLAY(‘ 該当顧客ID: ‘ || IN_CUST_ID);
SIGNAL ERROR; / 異常終了へ誘導 /
END;
/ ファイルオープン /
OPEN FILE(VSAM_IN) INPUT, FILE(RPT_OUT) OUTPUT;
/ 読み込みループ /
DO WHILE (EOF_FLG = ‘0’);
READ FILE(VSAM_IN) INTO(IN_REC);
IF 読み込み完了フラグ = ‘1’ THEN / ※説明用の擬似表現 /
EOF_FLG = ‘1’;
ELSE DO;
—————————————————-
— 【明示的変換の核心】
— 1. FIXED DEC (COMP3) のデータを BINARY に変換し、
— 高速かつ安全なレジスタ演算空間へ持ち込む。
—————————————————-
WK_AMT_BIN = BINARY(IN_AMT_DEC, 31, 0);
—————————————————-
— 2. バイナリ空間で消費税率(8%)を掛け合わせる
— ※ここでは簡略化のため整数演算として処理
—————————————————-
WK_TAX_BIN = WK_AMT_BIN 8 / 100;
—————————————————-
— 【明示的変換の核心】
— 3. 計算結果のBINARYを、出力用の FIXED DEC (11,2) に
— DECIMAL関数で明示的に型と精度を指定して押し戻す。
— これにより、暗黙の丸めによる予期せぬ切捨てを防ぐ。
—————————————————-
$OUT_CUST_ID = IN_CUST_ID;
$OUT_TAX_DEC = DECIMAL(WK_TAX_BIN, 11, 2);
/ 出力ファイルへ書き出し /
WRITE FILE(RPT_OUT) FROM(OUT_REC);
END;
END;
/ ファイルクローズ /
CLOSE FILE(VSAM_IN), FILE(RPT_OUT);
DISPLAY(‘ SLM0030I 正常終了 ‘);
RETURN;
END SLM0030I;
—
4. シニアアーキテクトからの実践アドバイスとデバッグの極意
このコードを見て、「なぜわざわざ `BINARY` に落としてから計算しているんだ? そのまま `FIXED DEC` で計算すればいいじゃないか」と思ったそこの君。甘い。
メインフレームのハードウェア(IBM Zのアーキテクチャ)において、十進数演算(Decimal Arithmetic)は専用の命令(SRPやZAPなど)を使うが、ループ回数が数十万、数百万回に及ぶ大規模バッチでは、バイナリ演算(Fixed Binary)の方がCPUサイクルの消費を抑えられるケースが多い。特に複雑な係数を掛け合わせるようなロジックでは、一度 `BINARY(…, 31, 0)` などでフルワードのレジスタサイズに展開して計算する方が、桁あふれのコントロールもしやすいのだ。
現場で陥りがちな罠
1. 精度の指定ミス($p$ と $q$ の不一致)
`DECIMAL(WK_TAX_BIN, 11, 2)` と指定したとき、元となるバイナリのスケールが破綻していると、コンパイル時に警告が出なくとも、実行時に高位桁が削ぎ落とされる。ビルトイン関数を使うときは、必ず「受け皿の変数の定義」と「関数の第2・第3引数の精度」を完全に一致させろ。
2. 浮動小数点 (`FLOAT`) との混同
金額計算に `FLOAT` 関数や `FLOAT` 型をうっかり使うな。浮動態は2進数ベースの近似値計算であるため、1円未満の端数処理で必ず「1ペニーのズレ(Rounding Error)」が発生し、経理部門との監査で血を見る羽目になる。金額・数量には必ず `DECIMAL` またはスケーリングされた `BINARY` を使うこと。
レガシーシステムの寿命を延ばすも縮めるも、我々エンジニアが書くコードの「厳密性」にかかっている。コンパイラの甘い誘惑(暗黙の型変換)を断ち切り、ビルトイン関数でデータを完全に手なずけること。これが、夜間バッチを時間通りに完走させるための唯一の王道だ。
さて、コーヒーを飲んだら、次のジョブストリームの結合テスト仕様書を確認するとしようか。頼むぞ、後輩くん。
