おい、最近の若手はCOBOLから流れてきたり、いきなりJavaをやっていたりで、PL/Iの「懐の深さ」に面食らうことが多いようだな。特に、変数名のつけ方からして「え、これ予約語じゃないの?」なんて驚いている現場をよく見かける。
だが、PL/Iの真骨頂はそこじゃない。PL/Iには、他の言語にあるような厳格な予約語(Reserved Words)が存在しないという、極めてユニークな構文規則がある。変数名に `IF` や `READ` といったキーワードを使っても、コンパイラは文脈からそれを正しく解釈してしまう。まあ、そんな危なっかしいコードを書くのは現場の御法度だがな。
さて、今回はそんなPL/Iの懐の深さに甘えた結果、パフォーマンスの罠にハマりがちなビルトイン関数 `PRECISION` による精度変更について、実務の現場目線で徹底的に解説してやろう。
—
1. なぜ `PRECISION` の裏側を知る必要があるのか?
基幹システムの夜間バッチで、勘定系マスターやVSAMファイルをガリガリ処理している時、「突然の数値オーバーフロー(S0C7手前のデータ例外)」に冷や汗をかいた経験はないか?
あるいは、計算結果の桁あふれを防ぐために、安易に `PRECISION` ビルトイン関数を使って変数の精度を動的に拡張していないか?
「お、`PRECISION` 関数を使えば、宣言時の桁数を後から自由に変えられて便利だな」などと考えていたら大間違いだ。
メインフレームの限られたCPU資源とメモリを極限までチューニングしなければならない現場において、この関数が引き起こす「内部でのメモリ再割り当て(Storage Reallocation)とデータ移動のオーバーヘッド」を無視することは、システム全体への背信行為に等しい。
予約語を持たないPL/Iの代償
前述した通り、PL/Iにはコンテキスト依存の構文解析が備わっている。そのため、コンパイラは実行時、あるいは複雑な式評価の最中に、動的に変更された属性を持つデータをどのように扱うべきか、裏側で必死に帳尻を合わせている。`PRECISION(x, p, q)` を呼び出した瞬間、単に「見かけ上の桁数が変わる」のではなく、コンパイラとランタイムが裏でどれだけの重労働をしているか、シミュレーションできたことがあるか?
—
2. `PRECISION` ビルトイン関数の仕様と内部処理の闇
`PRECISION(X, P [, Q])` は、式 `X` の精度を指定された全体桁数 `P`(固定小数点の場合は小数点以下の桁数 `Q`)に変更するためのビルトイン関数だ。
1. 固定小数点数 (FIXED DECIMAL / BINARY) の場合:
精度を変更するということは、実質的に新しいデータ型のワークエリアの確保、符号と数値のパッキングのし直し、そして必要に応じた丸め処理(Rounding)が伴う。
2. 浮動小数点数 (FLOAT) の場合:
単精度から倍精度へ、あるいはその逆への変換が発生し、内部レジスタのロード/ストアが増加する。
これを何百万件もループするVSAMのレコード処理や、膨大なトランザクションをさばくレコード入出力の最中で毎回呼び出してみろ。瞬く間にCPU時間が跳ね上がり、オペレーション部隊から「今日のバッチ、窓口開始に間に合わねぇぞ!」と怒鳴り込まれるのがオチだ。
—
3. 実践コード:VSAM入出力と `PRECISION` の正しい(あるいは避けるべき)使い方
百聞は一見にしかずだ。実際のメインフレーム開発現場を想定した、VSAMファイルを読み込み、数値を加工して別ファイルに出力するバッチプログラムの骨組みを見せよう。
1
/ —————————————————————- /
/ プログラム名: VPRCCALC /
/ 概要: VSAM入力レコードの数値を PRECISION で制御するサンプル /
/ —————————————————————- /
VPRCCALC: PROC OPTIONS(MAIN);
/ 組み込み関数の明示的宣言 /
DCL PRECISION BUILTIN;
DCL FIXED BUILTIN;
/ VSAMファイルの定義 /
DCL ACCT_FILE FILE RECORD SEQUENTIAL INPUT
ENV(AIX);
DCL OUT_FILE FILE RECORD SEQUENTIAL OUTPUT;
/ レコード構造体(勘定系マスター) /
DCL 1 MASTER_REC,
5 ACC_ID CHAR(8),
5 ACC_NAME CHAR(30),
5 ACC_BALANCE FIXED DEC(9,2); キスト金額
DCL 1 OUT_REC,
5 O_ACC_ID CHAR(8),
5 O_BAL_STR CHAR(15);
DCL EOF_FLAG BIT(1) INIT(‘0’B);
/ ワーク変数:あえて動的精度変更を想定した変数 /
DCL WK_AMOUNT FIXED DEC(11,4);
/ ONユニットによる入出力例外(ファイル終了など)の制御 /
ON ENDFILE(ACCT_FILE) EOF_FLAG = ‘1’B;
ON ERROR
BEGIN;
PUT SKIP LIST(‘ 致命的エラー発生: 異常終了します ‘);
/ ここでダンプ取得やログ出力をハンドリングする /
EXIT;
END;
/ ファイルオープン /
OPEN FILE(ACCT_FILE) INPUT;
OPEN FILE(OUT_FILE) OUTPUT;
/ メイン処理ループ /
DO WHILE(^EOF_FLAG);
READ FILE(ACCT_FILE) INTO(MASTER_REC);
IF EOF_FLAG THEN LEAVE;
/ 【アンチパターン例】ループ内で毎回PRECISIONを呼び出すと /
/ メモリの再割り当てや変換オーバーヘッドが発生し、性能が劣化する /
/ ※本来は変数宣言段階で桁数を揃えておくべきである /
WK_AMOUNT = PRECISION(MASTER_REC.ACC_BALANCE 1.08, 11, 4);
/ 編集出力用への転記(簡易表現) /
O_ACC_ID = MASTER_REC.ACC_ID;
O_BAL_STR = EDIT(WK_AMOUNT) (P’ZZZ,ZZZ,ZZ9.99’);
WRITE FILE(OUT_FILE) FROM(OUT_REC);
END;
/ ファイルクローズ /
CLOSE FILE(ACCT_FILE);
CLOSE FILE(OUT_FILE);
PUT SKIP LIST(‘正常終了: VPRCCALC COMPLETED.’);
END VPRCCALC;
—
4. シニアアーキテクトからの現場の教訓(アドバイス)
このコードを見て、何か感じ取れたか?
1. ループ内での `PRECISION` の使用を避けろ
コード中の `WK_AMOUNT = PRECISION(…)` の部分は、もしデータ量が数千万件に及ぶバッチであれば、それだけで数秒〜数分の無駄なCPU時間を消費する原因になる。必要な精度は、処理ループに入る前の変数宣言時(DCL文)にあらかじめ定義しておくのが、メインフレームプログラミングの鉄則だ。動的変更が必要な場面は、本当に例外的なスキーマレスデータ(XMLやJSONをパースするような特殊なバッチ)を扱う時に限定しろ。
2. ONユニットと例外処理の連携
PL/Iの `ON ERROR` や `ON ENDFILE` は非常に強力だが、誤った精度のまま演算を行って `FIXEDOVERFLOW` 割り込みを多発させると、システム全体のスループットがガタ落ちする。例外が発生してからハンドリングするのではなく、そもそも桁あふれが起きないようなデータ設計・精度設計を行うことこそが、一流のシステムアーキテクトの仕事だ。
3. 予約語がないからこそ「美しく書く」
PL/Iはどんな書き方でもコンパイルが通ってしまうことがある。だからこそ、インデントを揃え、大文字を基本とした美しいコーディング規約をチーム全体で遵守し、後輩たちが「読めば一発で内部挙動が想像できるコード」を残してやる必要がある。
基幹システムの命は「正確性」と「速度」だ。PL/Iの言語仕様の裏側にあるコンパイラの苦悩まで想像しながら、明日からのコーディングに活かしてくれ。期待しているぞ。
