お疲れ様です。今日も元気にオンラインのログやバッチのabend(アベンド)コードと格闘していることと思います。
さて、今回はPL/Iにおける数学ライブラリ、特に `LOG`(自然対数)および `EXP`(指数関数)関数の内部挙動と精度保証について話をしよう。
基幹システムの現場で、「なんで金融計算や金利計算のモジュールで微妙な丸め誤差が出るんだ?」とか、「マイグレーション時にCICSやバッチで数学関数の結果が微妙に変わったぞ」と頭を抱えた経験はないだろうか。
PL/IはCOBOLと違って、古くから科学技術計算をも視野に入れた懐の深い言語だ。変数名に予約語の概念がなく、文脈で解釈する柔軟性を持っている(例えば `IF IF = THEN THEN …` なんて悪夢のようなコードも理論上書けてしまうアレだ)。しかし、その裏側で実行される数学関数は、IBM Z(Mainframe)のハードウェア命令や、Language Environment(LE)の屈強な実行時ライブラリ `SCEELKED` と密接に結びついている。
今回は、この超越関数が裏で何をやっていて、どうすれば安全かつ正確にメインフレーム上で扱えるのか、実務の勘所を交えて徹底的に解説しよう。
—
1. PL/I数学関数の裏側:SCEELKEDとハードウェア命令の連携
私たちがPL/Iのソースコード上で何気なく `Y = EXP(X);` と書いたとき、コンパイラ(Enterprise PL/I)はそれを単なるインライン展開にはせず、Language Environment(LE)のランタイムルーチンへの外部参照(VCON)として解決する。
具体的には、以下のような仕組みで処理が進む。
1. ビルトイン関数の宣言(BUILTIN)
PL/Iでは `LOG` や `EXP` はデフォルトでビルトイン関数として認識されるが、明示的に `DCL (LOG, EXP) BUILTIN;` と宣言することがコーディング標準の鉄則だ。これにより、コンパイラはこれがユーザー定義のサブルーチンではなく、最適化された内部関数であることを認識する。
2. 浮動小数点数(FLOAT)の精度
メインフレームの浮動小数点には、IBM独自の「16進浮動小数点(HFP)」と、IEEE 754準拠の「2進浮動小数点(BFP)」が存在する。昨今のモダナイゼーションや他プラットフォームとの連携ではBFP(`FLOAT DEC(16)` や `FLOAT BIN(53)` など)を使う機会が増えているが、レガシーな資産ではHFP(`FLOAT(53)` 等)が現役だ。
3. SCEELKEDルーチンの呼び出し
実行時、LEの数学サービス(EEED… や EEEL… といった内部シンボルを持つルーチン)が呼び出される。これらは、IBM Zのハードウェア命令(DFP命令やベクトル命令、あるいは古き良きFPアーキテクチャ)を駆使し、テイラー展開やパデ近似、そして最終的な丸め処理(Rounding)を極限まで高速かつ正確に行うようにアセンブラで最適化されている。
—
2. 実務で直面する罠:ドメインエラーとONユニット制御
数学関数を扱う上で最も恐ろしいのは、ドメインエラー(Domain Error)、すなわち「定義域外の値の入力」だ。
例えば、`LOG(0)` や負の数に対する `LOG(X)` を実行した場合、即座にジョブがU4038などのLEアベンドで沈むか、あるいは静かに `NaN`(Not a Number)や無効な浮動小数点例外を発生させる。
ここでPL/Iの真骨頂である ONユニット(ON-Unit) による例外処理の出番だ。メインフレームのバッチ処理において、不正データによって一巻の終わりになるのを防ぐため、`CONDITION` や `FIXEDOVERFLOW`、さらには浮動小数点例外をキャッチする機構を組み込む必要がある。
—
3. 実践:LOGとEXPを用いた安全な計算モジュールの実装例
百聞は一見に如かず。実際に、VSAMから読み込んだデータに対して `LOG` と `EXP` を適用し、その結果をレコードとして出力する堅牢なPL/Iプログラムのサンプルコードを見てほしい。大文字記述、適切なインデント、そして現場で即座に使えるコメントを付与している。
1
MATHCALC: PROC OPTIONS(MAIN);
/————————————————————–/
/ 概要: VSAM入力ファイルから数値を読み込み、LOG/EXP関数を用いて /
/ 統計的変換を行った上で、出力ファイルに書き出す。 /
/ ※ビルトイン関数の明示的宣言とONユニットによる例外対策 /
/————————————————————–/
DCL LOG BUILTIN;
DCL EXP BUILTIN;
/ ファイル定義 /
DCL INFILE FILE RECORD INPUT;
DCL OUTFILE FILE RECORD OUTPUT;
/ 入出力レコード構造体 /
DCL 1 IN_RECORD,
5 IN_ID CHAR(8),
5 IN_VAL FLOAT DEC(16); 倍精度浮動小数点(IEEE BFP推奨)
DCL 1 OUT_RECORD,
5 OUT_ID CHAR(8),
5 OUT_LOG FLOAT DEC(16),
5 OUT_EXP FLOAT DEC(16),
5 OUT_MSG CHAR(32);
DCL EOF_FLAG BIT(1) INIT(‘0’B);
DCL ERROR_FLAG BIT(1) INIT(‘0’B);
/————————————————————–/
/ 異常系(浮動小数点例外・条件)のトラップ /
/————————————————————–/
ON CONDITION(ZERODIVIDE)
BEGIN;
OUT_MSG = ‘ERROR: ZERO DIVIDE DETECTED’;
ERROR_FLAG = ‘1’B;
END;
ON ERROR
BEGIN;
DISPLAY(‘CRITICAL ERROR IN MATH PROCESSING. CHECK SYSOUT.’);
ERROR_FLAG = ‘1’B;
GOTO WRITE_ERROR_LOG;
END;
/ ファイルオープン /
OPEN FILE(INFILE) INPUT, FILE(OUTFILE) OUTPUT;
/ 読み込みループ /
DO WHILE(^EOF_FLAG);
READ FILE(INFILE) INTO(IN_RECORD);
IF EOF_FLAG THEN LEAVE;
ERROR_FLAG = ‘0’B;
OUT_MSG = ‘NORMAL ‘;
/———————————————————-/
/ 数学関数の呼び出し前バリデーション(ドメインエラー回避) /
/———————————————————-/
IF IN_VAL <= 0 THEN
BEGIN;
OUT_LOG = 0;
OUT_EXP = 0;
OUT_MSG = 'WARNING: VALUE <= 0 FOR LOG ';
END;
ELSE
BEGIN;
/ 内部でSCEELKEDの数学ライブラリが呼び出される /
OUT_LOG = LOG(IN_VAL);
/ オーバーフロー対策として入力値の範囲をチェック /
IF IN_VAL > 700 THEN DO; / 64bit浮動小数点の限界付近 /
OUT_MSG = ‘WARNING: EXP OVERFLOW RISK ‘;
OUT_EXP = 0;
END;
ELSE DO;
OUT_EXP = EXP(IN_VAL);
END;
END;
WRITE_ERROR_LOG:
IF ^ERROR_FLAG THEN
WRITE FILE(OUTFILE) FROM(OUT_RECORD);
END;
/ ファイルクローズ /
CLOSE FILE(INFILE), FILE(OUTFILE);
DISPLAY(‘MATHCALC NORMAL END.’);
RETURN;
END MATHCALC;
—
4. ベテランからのアドバイス:保守・移行時のこつ
1. コンパイラオプションの確認
マイグレーション時に `FLOAT(HEX)` から `FLOAT(IEEE)` へ移行する場合、結果の数値の丸め方(Rounding Mode)や下位ビットの挙動が微かに変わることがある。金融系の厳密な突合(リコンシリエーション)を行うバッチでは、移行前後のテストで必ず数値の完全一致(あるいは許容誤差内か)を確認してほしい。
2. パフォーマンスの意識
ループの内側で何百万回も `LOG` や `EXP` を呼ぶ設計になっている場合、CPU使用量(ビジー時間)が跳ね上がる。もし入力値のパターンが限定されているなら、事前計算(ルックアップテーブル方式)や近似値計算への置き換えをアーキテクチャ設計段階で検討する勇気を持とう。
PL/Iの数学ライブラリは非常に強力で信頼性が高いが、その「黒魔術的とも言える自動最適化」の裏側を理解しているか否かで、トラブルシューティングのスピードが天と地ほど変わってくる。
今日の知見を、君の現場でのコードレビューや改修作業にぜひ役立ててほしい。質問があればいつでも声をかけてくれ。
