浮動小数点の罠:FLOAT BINARYとDECIMALの「誤差」が基幹システムを食い荒らす前に
現場の諸君、今日もメインフレームのバッチジョブと格闘しているか。
大規模改修の最中、ふと見つけた「いつの間にか紛れ込んだ浮動小数点の計算ロジック」。これがどれほど恐ろしい爆弾か、君たちは理解しているだろうか。特に、金融系や計数管理系のシステムにおいて、`FLOAT BINARY`と`FLOAT DECIMAL`の挙動を甘く見ることは、即座に「計算不一致による全件再実行」という悪夢に直結する。
今日は、IBMメインフレームにおける浮動小数点の真実と、我々が守るべきコーディングの鉄則について語ろうと思う。
—
なぜ「0.1」は正確に表現できないのか
PL/Iにおいて、`FLOAT BINARY`(2進浮動小数点)はハードウェアの演算効率を極限まで高めるために設計されている。一方、`FLOAT DECIMAL`(10進浮動小数点)は、我々人間が直感的に理解する10進数の精度を維持するものだ。
ここで肝に銘じてほしい。IEEE 754準拠のシステムにおいて、10進数の小数は2進数に変換された瞬間、循環小数として無限ループを切り捨てる「丸め誤差」を内包する。
「`0.1 + 0.2` が `0.3` にならない」という有名な話だが、メインフレームのバッチ処理では、これがVSAMのキーやレコードの比較判定条件に紛れ込む。
危険なコードの例(アンチパターン)
DCL VAL_A FLOAT BINARY(21) INIT(0.1);
DCL VAL_B FLOAT BINARY(21) INIT(0.2);
/ ここでIF文を使うのが最も危険 /
IF (VAL_A + VAL_B) = 0.3 THEN
PUT SKIP LIST(‘成功’);
ELSE
PUT SKIP LIST(‘失敗:ここが実行される!’);
このコードは確実に「失敗」へ分岐する。`0.1`を2進数で表現すると、末尾に微小なゴミが付くからだ。
—
実務で生き残るための「型」の選定と制御
基幹システムにおける浮動小数点の扱いは、以下の順序で検討すべきだ。
1. 固定小数点(FIXED DECIMAL)が最強: 10進数で定義できる値なら、迷わず`FIXED DECIMAL(p, q)`を使え。これがメインフレームの正義だ。
2. どうしても浮動小数点が必要な場合: `FLOAT DECIMAL`を選択せよ。`FLOAT BINARY`よりも演算速度は落ちるが、10進数表現の精度を内部的に保持するため、比較時の誤差リスクを大幅に低減できる。
3. 比較演算は「しきい値(Epsilon)」を用いる: どうしても`FLOAT BINARY`が必要な場合は、厳密比較(`=`)を避け、差分の絶対値が許容範囲内かを確認する手法をとれ。
実践的な修正例
/ 許容誤差を定義する定数 /
DCL EPSILON FLOAT BINARY(21) INIT(0.000001);
/ 差の絶対値がEPSILON以下なら「一致」とみなす /
IF ABS((VAL_A + VAL_B) – 0.3) < EPSILON THEN
PUT SKIP LIST('一致とみなす');
---
ONユニットで予期せぬエラーを封じ込める
浮動小数点演算は、オーバーフローやゼロ除算といった「例外」を発生させやすい。`OPTIONS(MAIN)`で定義されたメインプロシージャでは、必ず`ON CONDITION`や`ON FIXEDOVERFLOW`等を適切に配置し、異常終了時のダンプ解析を容易にしておくのがプロの流儀だ。
以下は、浮動小数点演算の例外を捕捉し、安全にログを出力して処理を継続(または制御付き終了)させるためのテンプレートだ。
MAIN_PROC: PROC OPTIONS(MAIN);
/ 浮動小数点演算の例外を捕捉するONユニット /
ON CONVERSION BEGIN;
PUT SKIP LIST(‘データ変換エラーが発生しました。入力データを確認してください。’);
SIGNAL FINISH; / 安全に終了処理へ移行 /
END;
ON FIXEDOVERFLOW BEGIN;
PUT SKIP LIST(‘計算結果が許容範囲を超えました。’);
/ 適切なリカバリ処理、またはログ出力を行う /
END;
/ ここから業務処理 /
BEGIN;
/ ロジック本体 /
END;
END MAIN_PROC;
—
アーキテクトからの助言
大規模なバッチ移行や改修を行う際、最も恐ろしいのは「旧環境では動いていた(ように見えた)コード」だ。コンパイラのバージョンアップや、浮動小数点の内部演算回路の微細な違いによって、今まで発生しなかった丸め誤差が、ある日突然、金額の不一致となって顕在化する。
- 定数計算は極力避ける: ソースにベタ書きする浮動小数点数は、期待値とずれる可能性がある。
- BUILTIN関数を活用せよ: `ROUND`関数を使って、比較する前に特定の精度へ丸め込む手法も有効だ。しかし、どこで丸めるかのルールをチーム内で統一しなければ、結局は泥沼のデバッグが待っている。
「なんとなく動いている」で済ませるな。なぜその型なのか、なぜその計算順序なのか。コードの隅々まで物理的な裏付けを持つことが、我々メインフレーマーの誇りであるべきだ。
次の改修では、`FLOAT BINARY`を`FIXED DECIMAL`に置き換える勇気を持て。それが、後の世代のエンジニアに感謝される唯一の道だ。
では、健闘を祈る。何かあればいつでも相談してくれ。
