【実務・中級編】FLOAT(BINARY/DECIMAL)のIEEE 754およびIBM形式 – PL/Iの基本構文とデータ制御実践ガイド

おい、最近あがってきたマイグレーション案件のソースコードを見たか?
オープン系への移行に伴って、メインフレームで長年動き続けてきた勘定系バッチの棚卸しをしているんだが、まあ見事に浮動小数点数の罠にハマって阿鼻叫喚の地獄絵図になっている。

「えっ、IBM汎用機(z/OS)で計算した結果と、Linux上のオープン系コンパイラで出力した金額の端数が、下位2桁で微妙にズレるんです……!」

夜中に青い顔をした後輩から連絡が来るたび、俺はこう言ってやるんだ。
「お前、本当に浮動小数点数の内部表現とハードウェアの仕様の違いを理解してコードを書いてるのか?」ってな。

PL/Iという言語は、識別子の命名規則の柔軟さ(予約語の概念がほぼなく、コンテキストで解釈される変態的な懐の深さ)に甘えていると、こういう足元をすくう致命傷を負う。特に `FLOAT`、お前だ。今日は、メインフレームの心臓部である浮動小数点数、その「IBM形式」と「IEEE 754形式」の決定的な違いと、マイグレーション時の丸め誤差の恐怖について、現場の知見を総動員して叩き込んでやる。心して聞け。

—

1. なぜ浮動小数点数でメインフレームとオープン系は袂を分かつのか

まず大前提として、金融系の金銭計算に `FLOAT` を使うこと自体が御法度だということは、百も承知だな? 金額は必ず `FIXED DECIMAL`(ゾーン10進数やパック10進数)を使うべきだ。だが、科学技術計算、金利の複利計算、あるいは海外発のパッケージ連携などで、どうしても `FLOAT` を使わざるを得ない場面に遭遇する。

ここで問題になるのが、ハードウェアの歴史的背景だ。

IBM独自形式(Hexadecimal Floating-Point)の呪縛

古き良きIBM System/360の時代から、メインフレーム(z/Architecture)のハードウェア(FPU)は、長らくIBM独自の16進浮動小数点形式を採用してきた。

  • 符号(1ビット)
  • 指数(7ビット、基数は16)
  • 仮数(24ビットまたは56ビット)

この「基数が16」というのがミソだ。16進数で表現するため、2進数に直したときの正規化の単位が4ビット刻みになる。そのため、10進数を正確に2進表現できないことに加え、16進特有の丸め誤差の癖がある。

IEEE 754形式(Binary Floating-Point)へのパラダイムシフト

一方、近年のオープン系サーバーや、近年のz/Architecture(z10以降でハードウェアサポート、あるいはソフトウェアエミュレーション)が標準としているのは、世界共通のIEEE 754形式(2進浮動小数点)だ。こちらは基数が2である。

  • 符号(1ビット)
  • 指数(8ビットまたは11ビット、基数は2)
  • 仮数(24ビットまたは53ビット)

この「基数16」と「基数2」の決定的な違いが、レコード入出力、VSAMファイルを介したデータ受け渡し、そして何より算術演算時の丸め誤差の発生パターンを完全に狂わせる。メインフレームからデータをCSV等で吐き出し、オープン系で再計算させた瞬間に数値がズレる原因は、まさにここにあるのだ。

—

2. 実践! PL/Iにおける FLOAT の定義とコンパイラオプション

PL/Iでは、`FLOAT` の精度を `DECIMAL` または `BINARY` で指定する。
ここで厄介なのは、コンパイケーション時のオプションや、ターゲットとするアーキテクチャによって、同じソースコードであっても生成される内部コードや演算結果が変わり得る点だ。

実務でよく使われる、VSAMファイルから浮動小数点データを読み込み、演算を行ってレポートに出力するサンプルコードを見せておこう。大文字ベース、適切なインデント、そして `BUILTIN` 関数の活用を徹底した、我がチームの標準フォーマットだ。

1
—————————————————————-

  • 浮動小数点数の演算と丸め誤差検証プログラム

—————————————————————-
FPTST: PROC OPTIONS(MAIN);

DCL 1 WK-REC,
5 WK-KEY CHAR(8), / 検索キー /
5 WK-FLT-DEC FLOAT DEC(6), / 10進単精度浮動小数点/
5 WK-FLT-BIN FLOAT BIN(53); / 2進倍精度浮動小数点/

DCL OUT-PRINT FILE RECORD OUTPUT;
DCL EOF-FLG BIT(1) INIT(‘0’B);

— 組み込み関数の宣言(明示的指定) —
DCL (ABS, ROUND, FLOAT) BUILTIN;

— 終了条件の監視(ONユニット) —
ON ENDFILE(VSAMIN) EOF-FLG = ‘1’B;

— VSAMファイルのオープン(実際は環境に合わせて記述) —
OPEN FILE(VSAMIN) INPUT, FILE(OUT-PRINT) OUTPUT;

DO WHILE(^EOF-FLG);
READ FILE(VSAMIN) INTO(WK-REC);
IF EOF-FLG THEN LEAVE;

——————————————————–

  • 【解説】
  • FLOAT DEC(6) は単精度、FLOAT BIN(53) は倍精度(L方式)。
  • これらを混在して演算すると、コンパイラによって暗黙の
  • 型変換(プロモーション)が発生し、意図しない丸め誤差が
  • 発生する。ここでは明示的に精度をそろえて演算する。

——————————————————–

CALL PROCESS_CALCULATION(WK-FLT-DEC, WK-FLT-BIN);

END;

CLOSE FILE(VSAMIN), FILE(OUT-PRINT);
RETURN;

—————————————————————-

  • 内部サブルーチン:演算処理

—————————————————————-
PROCESS_CALCULATION: PROC(P_DEC, P_BIN);
DCL P_DEC FLOAT DEC(6) VALUE;
DCL P_BIN FLOAT BIN(53) VALUE;

DCL W_RESULT FLOAT BIN(53);
DCL W_EDIT PIC ‘—,—,–9.99’;

  • 演算実行:IBM形式とIEEE形式の差異が影響するポイント

W_RESULT = P_BIN FLOAT(P_DEC, 53);

  • 画面・レポート出力用の編集(丸め処理の明示)

W_EDIT = ROUND(W_RESULT, 2);

PUT FILE(OUT-PRINT) EDIT (W_EDIT) (SKIP, A);

RETURN;
END PROCESS_CALCULATION;

END FPTST;

—

3. マイグレーション時の罠:ONユニットと例外処理

メインフレームからオープン系(例えばEnterprise PL/I on Linuxなど)へ移行する際、最もエンジニアを悩ませるのが浮動小数点例外(Floating-Point Exception)の挙動だ。

メインフレームのハードウェアは、オーバーフローやアンダーフロー、ゼロ除算が発生した際、コンパイラオプションやシステム設定に応じて独自の割り込みコードを発生させる。PL/Iではこれを `ON` ユニットで捕捉するのが定石だ。

1
— 浮動小数点アンダーフロー・オーバーフローの捕捉 —
ON OVERFLOW
BEGIN;
PUT SKIP LIST(‘ 警告: 浮動小数点オーバーフローを検知しました ‘);

  • 必要に応じたフォールバック処理を記述

END;

ON ZERODIVIDE
BEGIN;
PUT SKIP LIST(‘ エラー: ゼロ除算が発生しました ‘);
GOTO ERROR_ROUTINE;
END;

しかし、ここで注意が必要なのは、IEEE 754形式を採用するオープン系環境では、ハードウェアレベルで「非数(NaN)」や「無限大(Infinity)」といった概念がデフォルトでハンドリングされる点だ。
メインフレームのIBM形式では異常終了(ABEND: S0C7やS0C8など)になっていたケースが、オープン系では「NaNのまま伝播し、気づいた時には出力結果が全部ゴミデータになっていた」というSilent Failure(サイレント・フェイル)を引き起こす。

マイグレーション時には、単にソースコードをコンパイル通すだけでなく、この例外処理の挙動がどう変わるかをテスト仕様書に盛り込まなければならない。

—

4. ベテランからの現場の教訓:トラブルを防ぐための3ヶ条

最後に、俺が数々のレガシーシステム改修現場で生き残ってきた、浮動小数点数にまつわる「鉄の掟」を授けよう。

1. 金銭計算に `FLOAT` は絶対に使うな。
どうしてもという理由がない限り、すべて `FIXED DECIMAL` に置き換えろ。これが最大の予防策だ。
2. 外部ファイル(VSAM/QSAM)を介したバイナリデータのやり取りを疑え。
メインフレームで出力した `FLOAT` のレコードを、そのままPC側でバイナリリードしてはいけない。必ずテキスト(CSVやXML、あるいはJSON)形式に変換してインターフェースするか、コンパイラの浮動小数点互換オプション(例: `FLAG(I)` やアーキテクチャレベルの指定)を検証しろ。
3. 丸め(ROUND)はシステム任せにするな。
「コンパイラが勝手にやってくれる」という甘い考えは捨てろ。`ROUND` ビルトイン関数を明示的に使い、どの桁で四捨五入(あるいは切り捨て)を行うかをコード上で担保しろ。

PL/Iという言語は、プログラマの意図を忠実に実行する頼もしい相棒だが、ハードウェアの仕様の違いまでは隠しきれない瞬間がある。
「動いているから触らない」ではなく、内部表現の仕組みまで見通せるホンモノのシステムアーキテクトになってくれ。期待しているぞ。

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