【PL/I深層解説】浮動小数点の悪夢を回避せよ:FLOAT BINARYとDECIMALの正しい作法
メインフレームの現場で、バッチの帳票数値が「1円合わない」「極小の誤差が蓄積して閾値判定でこける」といったトラブルに遭遇したことはないだろうか。
PL/Iを扱う我々にとって、`FLOAT BINARY`と`FLOAT DECIMAL`の使い分けは、単なるデータ型の選択ではない。それは、計算精度という「ビジネスの信頼性」を左右する防波堤だ。今日は、IEEE 754とIBM形式の背景を含め、実務で絶対に外してはいけないポイントを共有したい。
—
1. なぜ「浮動小数点」はビジネスロジックの敵なのか
多くの若手エンジニアは、とりあえず楽だからという理由で`FLOAT`を使って計算を書く。しかし、メインフレームの歴史を知る者からすれば、これは地雷原を歩くようなものだ。
IBM形式 vs IEEE 754
かつてのS/360から続くIBM独自の浮動小数点形式と、現在のオープン系でも標準的なIEEE 754形式。これらは内部的な指数表現や正規化のルールが微妙に異なる。特に、マイグレーションでオープン系とメインフレーム間でデータをやり取りする際、この「解釈の揺らぎ」が致命的な誤差を招く。
教訓: 金額や在庫数など、厳密さが求められるデータには、決して`FLOAT`を使ってはいけない。必ず`FIXED DECIMAL`(いわゆるパック10進数)を使用すること。`FLOAT`はあくまで科学技術計算や、統計的な近似値計算のために存在するものだ。
—
2. 実践的なコーディング標準:精度の罠を回避する
それでも、どうしても`FLOAT`を使わざるを得ない場合があるだろう。その際、精度を制御するためのBUILTIN関数とコーディングの定石を伝授する。
実践コード例
以下のコードを見てほしい。`FLOAT BINARY`と`FLOAT DECIMAL`の挙動の違いと、精度管理の基本だ。
1
/ ————————————————————- /
/ FLOAT精度の検証用プロシージャ /
/ ————————————————————- /
CALC_PRECISION: PROCEDURE OPTIONS(MAIN);
/ 精度を指定したFLOAT DECIMAL (15桁) /
DCL VAL_DEC FLOAT DECIMAL(15) INIT(12345.6789012345);
/ 精度を指定したFLOAT BINARY (53ビット≒約16桁) /
DCL VAL_BIN FLOAT BINARY(53) INIT(12345.6789012345);
DCL RESULT_FIXED FIXED DECIMAL(15, 2);
ON UNDERFLOW BEGIN;
PUT SKIP LIST(‘警告: アンダーフローが発生しました’);
END;
/ 演算結果をFIXED DECIMALに代入する際、明示的に丸める /
/ ROUND関数を使わないと、意図しない切り捨てが発生するリスクがある /
RESULT_FIXED = ROUND(VAL_DEC 1.05, 2);
PUT SKIP EDIT(‘結果(DECIMAL): ‘, RESULT_FIXED)(A, F(15,2));
END CALC_PRECISION;
このコードのポイント
1. 精度定義の明示: `FLOAT DECIMAL(15)`のように、必ず精度を指定すること。デフォルトに依存するのは、仕様変更時に大事故を招く元だ。
2. ROUND関数の活用: `FLOAT`から`FIXED`への型変換は、コンパイラ任せにしてはいけない。`ROUND`関数で丸め処理を明示的に記述することで、計算結果の再現性を確保する。
3. ONユニットによる安全装置: 浮動小数点演算は、桁落ちやアンダーフローが発生しやすい。重要なバッチ処理では、`ON UNDERFLOW`や`ON OVERFLOW`を適切に定義し、異常終了ではなくログへの記録と安全なリカバリができる設計にしておくべきだ。
—
3. VSAMアクセスとデータ変換の罠
メインフレーム開発で最も多いのが、VSAMのレコードバッファから`FLOAT`変数へ読み込む際の変換ミスだ。
VSAM上のデータがパック10進数(`PIC S9(7)V99 COMP-3`など)である場合、これを`FLOAT`として受け取ると、その瞬間に変換誤差が発生する。たとえ数値が小さくても、一度`FLOAT`にキャストした時点で、二進数での近似表現に変換されるため、元の10進数表現とは微小な差異が生じる。
- 鉄則: 読み込みバッファは可能な限りソースデータと同じ型(`FIXED DECIMAL`)で定義せよ。
- 例外処理: もし外部システムから「IEEE形式のFLOAT」でデータが送られてきた場合は、`PLIFLOAT`コンパイラオプションの指定を確認し、データ変換時の期待値をテストコードで網羅的に検証すること。
—
4. ベテランからのアドバイス:デバッグのコツ
「計算が合わない」というバグ調査が回ってきたら、まず何を見るか。
1. コンパイラオプション: `FLOAT(BE390)`と`FLOAT(IEEE)`のどちらでコンパイルされているかを確認する。ここがズレていると、演算結果は一致しない。
2. 中間変数の確認: 一行で長い計算式を書かず、中間結果を変数に格納して`PUT DATA`でダンプを取れ。`FLOAT`の内部表現の深淵を覗くことができるはずだ。
3. 浮動小数点の比較: `IF A = B THEN` という記述は厳禁だ。必ず `IF ABS(A – B) < 0.000001 THEN` のように、許容誤差範囲(イプシロン)を持たせた比較を行うこと。
PL/Iは、書き手の意図を非常に忠実に実行する言語だ。だからこそ、その背後にあるハードウェアの挙動まで想像してコードを書く必要がある。
後輩諸君、仕様書に書かれていることだけを実装するのではなく、「この数値が計算過程でどう化けるか」を常に意識してほしい。それができるエンジニアこそが、この先10年、20年とメインフレームの現場で重宝される人材になるはずだ。
次は、ONユニットを利用したファイル排他制御のテクニックについて深掘りしようか。また現場で会おう。
