浮動小数点の悪魔を飼い慣らせ ― PL/IにおけるFLOAT演算とマイグレーションの深淵
基幹システムを支えるPL/Iの世界において、「浮動小数点数」はしばしば爆弾として扱われます。特に、古いシステムからJavaやC#といったモダン言語へロジックを移植する際、多くのエンジニアが「なぜ計算結果が1円ずれるのか」「なぜテスト環境と本番で微差が出るのか」という壁に突き当たります。
今日は、IBMメインフレームの心臓部で蠢く`FLOAT BINARY`と`FLOAT DECIMAL`、そしてそれらが引き起こす数値精度の闇について、アーキテクトの視点から紐解いていきましょう。
1. 浮動小数点の二つの顔:IBM形式 vs IEEE 754
PL/Iのコンパイラオプション(`FLOAT(IBM)` または `FLOAT(IEEE)`)は、単なる設定ではありません。それは数値の表現形式そのものを決定づける、システムの運命線です。
- IBM形式: 16進浮動小数点。伝統的なメインフレームの標準ですが、端数処理の挙動が独特です。
- IEEE 754: 現代のデファクトスタンダード。PCやクラウド環境と親和性が高いですが、既存のメインフレーム資産と混在させると、バイナリ比較で致命的な不一致を生みます。
特にレガシー移行の際、移行先(Javaの`BigDecimal`や`double`)がIEEE 754に準拠しているのに対し、メインフレーム側がIBM形式で計算されていた場合、下位ビットの丸め誤差が累積し、数百万件のバッチ処理の最後で「合計値が合わない」という悲劇が必ず起こります。
2. 丸め誤差のメカニズムとコードによる防御
PL/Iでの計算において、`FLOAT`は「高速だが不正確」な存在です。もし、銀行の勘定系バッチで利息計算に`FLOAT`を使っているなら、今すぐ`FIXED DECIMAL`(パックデシマル)への書き換えを検討してください。
しかし、どうしても`FLOAT`を扱わねばならない場合、以下のコードのように「許容誤差範囲(Epsilon)」を定義して比較するのがアーキテクトの矜持です。
1
/ 浮動小数点演算の比較における設計指針 /
DECLARE VAL_A FLOAT BINARY(53) INITIAL(0.1 + 0.2);
DECLARE VAL_B FLOAT BINARY(53) INITIAL(0.3);
DECLARE EPSILON FLOAT BINARY(53) INITIAL(1.0E-12); / 許容誤差範囲 /
/ 直接比較は厳禁。必ず差分の絶対値で判定する /
IF ABS(VAL_A – VAL_B) < EPSILON THEN
PUT SKIP LIST('数値は等価とみなす');
ELSE
PUT SKIP LIST('致命的な精度欠損が発生');
3. 実務で見落とされる「罠」:内部符号とパックデシマル
マイグレーション時、最も現場を混乱させるのが`FIXED DECIMAL`の内部表現です。特に、メインフレーム特有の「符号反転(F/C/D)」問題は、DB2のホスト変数経由でデータを受け渡す際によく発生します。
PL/Iで`COMP-3`(パックデシマル)を扱う際、コンパイラはデータ末尾のニブル(4ビット)を符号として解釈します。外部システムへ連携する際、この符号が正しくエミュレートされないと、Java側で数値がマイナス反転したり、予期せぬアベンド(S0C7:データ例外)を引き起こしたりします。
解決策: 移行先へのデータ連携時には、必ずメインフレーム側で`CHAR`型へ編集(`PICTURE`句による編集)してから出力するか、あるいはバイナリを完全に解釈できるライブラリを中間に挟むべきです。
4. ダンプ解析が語る「ポインタの暴走」
`FLOAT`の演算中に`S0CB`(浮動小数点例外)が発生した場合、それはほぼ間違いなく不正なデータ領域へのアクセスか、初期化されていないポインタが浮動小数点レジスタ(FPR)を汚染したことが原因です。
1
/ ポインタを用いた動的メモリ操作の危険性 /
DECLARE PTR_DATA POINTER;
DECLARE FLOAT_VAR FLOAT BINARY(53) BASED(PTR_DATA);
/ PTR_DATAを適切にALLOCATEせずアクセスすれば即アベンド /
/ システム設計においては、必ずNULLチェックを挟むこと /
IF PTR_DATA ^= NULL() THEN
FLOAT_VAR = 123.456;
アベンドダンプ(SYSMDUMP)を解析する際は、PSW(プログラム状態語)だけでなく、FPR(浮動小数点レジスタ)の値を確認してください。レジスタに`417B…`のような16進数が並んでいたら、それはIBM形式の浮動小数点です。もしそれが`3FE…`のようにIEEE形式に見えるなら、コンパイラオプションとランタイムライブラリの不一致を疑うべきです。
結びに代えて:アーキテクトとしての心得
基幹システムの移行において、コードを書き換えることは簡単です。しかし、半世紀近く蓄積された「数値の癖」を紐解くことは、考古学に近い作業です。
PL/Iという言語は、ハードウェアの限界ギリギリまで性能を引き出すために作られました。その設計思想を理解せず、ただ構文だけをJavaに翻訳すれば、必ずどこかで「誤差」という名の牙が剥きます。
システムを移行する際は、まず「現状の計算式がどの浮動小数点形式を前提にしているか」をコンパイラリスト(LIST/MAPオプション)から徹底的に追いかけてください。それが、我々レガシーアーキテクトに課せられた、最も重要な仕事です。
次の記事では、CICSオンライン処理における`SHARED STORAGE`と`POINTER`の競合問題について深掘りします。お楽しみに。
