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

おい、最近こっちのチームに配属された若手が、勘定系バッチの金額計算でまた「おかしな端数誤差が出ました」って青い顔して駆け込んできたんだよ。

「先輩、ROUND関数使ってるのに、なんで最終的な累積残高が1セント(1円)ズレるんですか!」ってね。
ハハッ、無理もない。PL/Iの`ROUND`関数や算術演算の裏側の仕組みを知らずに、ただなんとなく関数を叩いているうちは、金融系や基幹システムのメインフレーム開発ではいつまで経っても夜眠れない夜を過ごすことになる。

今日はな、PL/Iにおける「ROUND関数の精度制御と丸め誤差の回避」について、コンパイラの挙動から実務でのコーディング標準まで、骨の髄まで叩き込んでやる。耳の穴かっぽじってよく聞くんだぞ。

—

1. なぜPL/IのROUNDで誤差が出るのか?(言語仕様の罠)

まず大前提として、PL/IにはC言語やJavaのような「キーワード(予約語)の縛り」が非常に少ない。変数名に `ROUND` と書こうが、コンパイラは文脈からそれが組み込み関数(BUILTIN)なのかユーザー定義の変数なのかをきちんと判別する柔軟性を持っている。だが、その柔軟性の裏で、データ型と精度(P’…’ や FIXBIN など)の組み合わせを誤ると、意図しない中間丸めが発生する。

特にメインフレームのバッチ処理で頻繁に登場する `FIXED DECIMAL`(パック十進数:COMP-3)同士の乗除算を思い出してくれ。
例えば、利率や単価を掛け合わせたときに、小数点以下の桁数が元々の定義を超えて拡張されるよな? その際、PL/Iは自動的に一時的なワークエリア(中間結果)を作成し、その過程で暗黙の切り捨てや丸めを行う場合がある。

`ROUND(x, n)` 関数は、指定した式 `x` を小数点以下第 `n` 位(または整数部)に丸めるためのものだが、「何を基準に、どのタイミングで丸めるか」をプログラマが制御してやらないと、浮動小数点演算や十進数演算の特性による累積誤差(いわゆるスモール・チェンジ・エラー)の温床になるんだ。

—

2. 現場で使える!VSAMレコード処理とROUNDの安全な実装パターン

百聞は一見に如かずだ。実際の勘定系バッチを想定した、VSAM(KSDS)のレコードを読み込んで利息計算と端数調整を行い、出力ファイルを更新する実用的なPL/Iソースコードを見せてやろう。

大文字ベースの記述、適切なインデント、そして `BUILTIN` 宣言の徹底。これが我がチームのコーディング標準だ。

1
ACCOUNT_BATCH: PROC OPTIONS(MAIN);

/ ————————————————————- /
/ 組み込み関数の明示的宣言 /
/ ————————————————————- /
DCL ROUND BUILTIN;
DCL ABS BUILTIN;

/ ————————————————————- /
/ ファイル定義 (VSAM KSDS および 順編成ファイル) /
/ ————————————————————- /
DCL ACCT_FILE FILE RECORD SEQUENTIAL INPUT
ENVIRONMENT(BUFSZ(4096));
DCL RPT_FILE FILE RECORD SEQUENTIAL OUTPUT;

/ ————————————————————- /
/ レコード構造体定義 (COMP-3を活用した金額・利率の表現) /
/ ————————————————————- /
Dcl 1 ACCORD_REC,
5 ACC_ID CHAR(8), / 口座番号 /
5 ACC_BALANCE FIXED DEC(11,2), / 残高 (整数9桁,小数2桁) /
5 ACC_RATE FIXED DEC(5,4), / 利率 (整数1桁,小数4桁) /
5 ACC_STATUS CHAR(1); / ステータス /

Dcl 1 PRINT_REC,
$P_ID CHAR(8),
$P_FILLER1 CHAR(2) VALUE(‘ ‘),
$P_OLD_BAL CHAR(15),
$P_FILLER2 CHAR(2) VALUE(‘ ‘),
$P_NEW_BAL CHAR(15),
$P_FILLER3 CHAR(2) VALUE(‘ ‘);

/ ————————————————————- /
/ ワーク変数定義 /
/ ————————————————————- /
DCL WK_INTEREST FIXED DEC(11,4) INIT(0); / 利息計算中間ワーク /
DCL WK_ADJUSTED FIXED DEC(11,2) INIT(0); / 丸め後利息ワーク /
DCL EOF_FLAG CHAR(1) INIT(‘N’);

/ ————————————————————- /
/ ファイルオープン /
/ ————————————————————- /
OPEN FILE(ACCT_FILE) INPUT,
FILE(RPT_FILE) OUTPUT;

/ 終了条件(ENDFILE)のONユニット制御 /
ON ENDFILE(ACCT_FILE) EOF_FLAG = ‘Y’;

READ FILE(ACCT_FILE) INTO(ACCORD_REC);

DO WHILE (EOF_FLAG = ‘N’);

/ ——————————————————— /
/ 【重要】精度を維持するためのアルゴリズム /
/ 単純に ROUND(ACC_BALANCE ACC_RATE, 2) とすると /
/ 中間演算の精度落ちや予期せぬ丸めが起きるため、 /
/ 十分な小数桁を持つワーク(FIXED DEC(11,4))で受けてから /
/ 確実に ROUND 関数を通す。 /
/ ——————————————————— /
WK_INTEREST = ACC_BALANCE ACC_RATE;

/ 小数点第2位への丸め(四捨五入) /
/ 第2引数の「2」は、小数点以下第2位に丸めることを意味する /
WK_ADJUSTED = ROUND(WK_INTEREST, 2);

/ 残高への加算(ここで型と桁数が一致しているため安全) /
ACC_BALANCE = ACC_BALANCE + WK_ADJUSTED;

/ 編集出力の作成処理(省略せずに記述) /
$P_ID = ACC_ID;
$P_OLD_BAL = EDIT(ACC_BALANCE – WK_ADJUSTED, ‘ZZZ,ZZZ,ZZ9.92’);
$P_NEW_BAL = EDIT(ACC_BALANCE, ‘ZZZ,ZZZ,ZZ9.92’);

WRITE FILE(RPT_FILE) FROM(PRINT_REC);

READ FILE(ACCT_FILE) INTO(ACCORD_REC);
END;

/ ————————————————————- /
/ ファイルクローズ /
/ ————————————————————- /
CLOSE FILE(ACCT_FILE),
FILE(RPT_FILE);

RETURN;

END ACCOUNT_BATCH;

—

3. ベテランが教える!ROUND関数使用時の鉄則とデバッグのコツ

上のコードを見て、「なぜわざわざ `WK_INTEREST`(小数4桁)というクッションを挟むのか?」と疑問に思ったなら、お前はまだまだ甘い。ここにしびれるようなノウハウが詰まっているんだ。

① 中間演算の精度落ち(Precision Loss)を防げ

PL/Iのコンパイラは、演算子の両側のデータ属性から結果の属性を自動決定(ルール・オブ・プレシジョン)する。
例えば `FIXED DEC(11,2)` と `FIXED DEC(5,4)` を掛け算すると、結果のデフォルトの精度はコンパイラの仕様によって最大桁数(通常は15桁や31桁の制限内)まで引き延ばされる。だが、それをいきなり代入先や `ROUND` の引数に直で放り込むと、アセンブラレベルで予期せぬ `PACKED DECIMAL` のシフト命令や切り捨てが発生し、下位桁がドロップすることがあるのだ。
必ず「演算結果を収めるのに十分な小数桁を持つ大きめのワーク変数」を一枚挟むこと。これが鉄則だ。

② 丸めモードの挙動に注意する

PL/Iの `ROUND(x, n)` は、JISや一般的なビジネス要件で求められる「四捨五入(正確には 5 以上切り上げ、4 以下切り捨て:Round half up)」を実行する。
しかし、大量の金融データを何千万件も処理するバッチにおいて、この四捨五入の偏り(統計的バイアス)が監査で突っ込まれることがある。「銀行家ズール(JIS丸め / 偶数丸め)」が要求されるような厳密な要件の場合、単なる `ROUND` 関数だけでは対応できず、自前で端数処理ルーチン(端数の切り捨て・切り上げの判定ロジック)を組む必要があるケースも頭に入れておけ。

③ ONユニットと例外処理の連携

万が一、計算結果が変数の定義桁数(オーバーフロー)を超えた場合、PL/Iでは `FIXEDOVERFLOW` 条件(CONDITION)が発生する。
これを放置すると異常終了(ABEND: SCC3 や S0C7 など)の元だ。必要に応じて `ON FIXEDOVERFLOW` ユニットを配置し、エラーログを出力して安全にリターンコードを制御する設計を忘れないように。

—

まとめ

レガシーシステムの保守やマイグレーションの現場では、「動いているから触るな」と言われがちだが、データ構造の変更や消費税率・利率の改定といったエンハンスの際に、こうした丸め誤差のバグが必ず牙を剥く。

識別子や予約語の概念に惑わされず、PL/Iのデータ制御の根幹である「精度の伝播」と「適切なROUND関数の適用」をマスターすれば、どんな巨大なメインフレームのプログラムであっても、怖じ気づくことはなくなるはずだ。

さあ、理理整然と、かつ泥臭く、確実なコードを書き上げてくれ。期待しているぞ!

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