【実務・中級編】FLOAT BINARYとFLOAT DECIMALの精度と丸め誤差 – PL/Iの基本構文とデータ制御実践ガイド

浮動小数点の罠: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`に置き換える勇気を持て。それが、後の世代のエンジニアに感謝される唯一の道だ。

では、健闘を祈る。何かあればいつでも相談してくれ。

タイトルとURLをコピーしました