浮動小数点の悪夢:PL/IにおけるIEEE 754の落とし穴とメインフレームの矜持
メインフレームの現場で長年培った感覚から言わせてもらえば、PL/Iの浮動小数点演算を甘く見ているエンジニアは、いつか必ず「消えない誤差」と「説明不能なABEND」の洗礼を受けることになる。
特に、レガシーマイグレーションの現場で「Javaの`double`とPL/Iの`FLOAT BINARY`は同じだから変換は容易だ」などと口にするリーダーがいれば、私は即座にプロジェクトの再考を勧める。今日は、IEEE 754準拠の仕様が、基幹システムの堅牢性にどのような火種を潜ませているのか、その深淵を覗いてみよう。
—
1. FLOAT BINARY vs DECIMAL:その選択が運命を分ける
PL/Iにおいて、`FLOAT BINARY`はハードウェア(System zの浮動小数点演算ユニット)に直結する。一方で`FLOAT DECIMAL`は、ソフトウェアエミュレーションに近い挙動をとることが多い。
基幹系で避けなければならないのは、「0.1」のような10進数で端数となる値が、2進数浮動小数点(BINARY)では無限小数として表現されるという事実だ。
/i
DCL A FLOAT BINARY(21) INIT(0.1); / 精度をケチると誤差が蓄積する /
DCL B FLOAT BINARY(21) INIT(0.1);
/ 比較演算の典型的な罠 /
IF A + A + A + A + A + A + A + A + A + A = 1.0 THEN
PUT SKIP LIST(‘成功’);
ELSE
PUT SKIP LIST(‘失敗:浮動小数点の丸め誤差により一致しない’);
このコードを実行すると、多くの環境で「失敗」が出力される。なぜか? `0.1`を2進数で表現した瞬間に末尾に微小な誤差が乗り、それを10回足し合わせることで誤差が増幅し、`1.0`との厳密な比較(`=`)が偽(False)となるからだ。
アーキテクトの視点:
マイグレーション先がJavaであれば`BigDecimal`への変更を強制すべきだが、PL/Iを維持するなら、比較時は必ず「許容誤差(イプシロン)」を用いた判定を徹底せよ。
—
2. ポインタと動的メモリ操作:ダンプ解析の現場から
基幹システムにおいて、`FLOAT`型の変数をポインタ越しに操作する際、最も恐ろしいのはアライメント違反や予期せぬ内部表現の破壊だ。
特に`DB2`から`FETCH`した`FLOAT`値が、CICSのコモンエリア(COMMAREA)で不適切なオフセットで再定義(`DEFINED`句)されていた場合、ダンプ(SNAPダンプ)には「意味不明なビット列」が残る。
/i
DCL PTR POINTER;
DCL MY_FLOAT FLOAT BINARY(53) BASED(PTR);
/ 動的に確保した領域をFLOATとして解釈させる際、アライメントに注意 /
/ 8バイト境界からズレると、System zのプロセッサは容赦なくS0C7/S0C4を叩き込む /
IF PTR -> MY_FLOAT > 1.0E-10 THEN
/ ここで計算ロジックを回す前に、NULLチェックとアライメント確認を行うのがプロの流儀 /
ダンプを解析する際、`FLOAT`の内部表現(指数部と仮数部)を16進数で読み解けないようでは、オンライン処理の突発的なABENDの原因を突き止めることはできない。`S0C7`(データ例外)は、往々にして初期化されていない浮動小数点変数がSQLの演算に巻き込まれた結果として発生する。
—
3. コンパイラオプション:最適化という名の劇薬
コンパイラオプションの`OPTIMIZE(3)`や`ARCH(12)`(最新プロセッサの活用)は、計算速度を劇的に向上させるが、浮動小数点の丸め順序を勝手に入れ替えることがある。
これはIEEE 754準拠の論理では許容されるが、長年「この順序で計算しなければならない」という暗黙の了解(あるいはバグを含んだ仕様)で動いていた旧来のバッチプログラムにとって、致命的な結果をもたらす。
- 解決策: 精度がクリティカルな演算を行うモジュールには、`LIMIT(FIXED(DECIMAL)) PRECISION`を明示し、最適化レベルを落とす勇気を持つこと。
—
4. 移行の現場で私が語る「真実」
JavaやC#への移行を進める際、私はいつもこう言う。
> 「浮動小数点の計算結果を、元のPL/Iのバッチ出力と1ビット単位で一致させようと考えるな。それは数学的妥当性ではなく、ただの『呪い』の継承だ。」
マイグレーションは、レガシーシステムが抱えていた「浮動小数点の誤差」という名の負債を清算する最大のチャンスだ。`FIXED DECIMAL`(パックデシマル)への移行を検討し、それが不可能なら、全ての計算において「桁落ち」を考慮した検証ロジックを再設計すること。
PL/Iは、ハードウェアの性能を極限まで引き出すための「凶器」である。その凶器を安全に、かつ意のままに操るためには、仕様書に書かれていること以上に、コンパイラが吐き出す機械語の裏側にある「ビットの振る舞い」を想像しなければならない。
次にあなたがダンプを見るとき、そこに並ぶ16進数がただの数字ではなく、過去の設計者の苦悩とハードウェアの物理的な制約の結実であることを思い出してほしい。それこそが、メインフレームを支える者の矜持である。
