ベテランが教える!PL/Iにおける `BINARY` ビルトイン関数の実務的罠と正しい型変換
おい、最近入った若手が「数値の桁あふれで夜間バッチが異常終了した」って青い顔して駆け込んできたんだよ。ログを見たら、典型的な `FIXED BINARY` と `FIXED DECIMAL` の暗黙の型変換の罠にハマってた。
メインフレームの基幹システムを支えるPL/Iにおいて、数値データの扱いはシステムの信頼性に直結する心臓部だ。特に外部ファイルやVSAMからのレコード入出力、そして計算ロジックの途中におけるデータ型の変換は、一歩間違えると致命的なデータ破損や、原因究明に何日もかかるような不可解な切り捨てエラーを引き起こす。
今回は、任意の数値を `FIXED BINARY` に変換する際に絶対になくてはならない `BINARY` ビルトイン関数 に焦点を当て、その精度指定のルール、丸め・切り捨ての挙動、そして実務の現場で生き抜くためのコーディング作法を徹底的に叩き込んでやろう。
—
1. `BINARY` ビルトイン関数の基本仕様と「精度」の正体
まず大前提として、PL/Iの `FIXED BINARY`(固定小数点二進数)と `FIXED DECIMAL`(固定小数点十進数、いわゆるパック十進数やゾーン十進数)では、メモリ上の持ち方も、コンパイラが生成する機械語命令も全く異なる。
`BINARY(expression [, precision [, scale]])` ビルトイン関数は、任意の数値を `FIXED BINARY` 型に明示的に変換するためのものだ。
ここで多くのプログラマが勘違いするのが 第2引数の「精度(Precision)」 の指定だ。
- 第2引数(precision)の意味: 符号ビットを除いた、二進数の「全ビット数」を指す。バイト数ではない。
- デフォルトの挙動: 引数を省略した場合、ソース側のデータ属性に応じたデフォルトの精度が勝手に割り振られる。これがバッチ改修時の隠れた爆弾になる。
メインフレームのワード境界と効率
IBMの370アーキテクチャの流れをくむ現行のメインフレームにおいて、演算器は半ワード(16ビット)、フルワード(32ビット)、ダブルワード(64ビット)のバイナリ演算を最も効率よく処理する。
したがって、`BINARY` 関数で精度を指定する際は、単に「入るからいいや」ではなく、15(半ワード相当)や 31(フルワード相当)、あるいは倍精度の 63 といった、CPUのアーキテクチャを意識した精度設計がプロの技量というものだ。
—
2. 切り捨てと丸め処理のリアル:データはどこへ消えるのか?
数値を `BINARY` に変換する際、元の値の小数点以下の桁数(スケール)や整数部の大きさが、指定した精度(precision, scale)に収まらない場合、コンパイラは容赦なくデータを切り捨てる(Truncation)。
ここで注意すべきは、PL/Iの標準変換では「四捨五入(Round)」ではなく「切り捨て(Truncation)」が行われるという点だ。
金額計算や金利計算のモジュールでこれをやると、監査法人からすっ飛んで怒られることになる。
四捨五入を行いたい場合は、`BINARY` 関数に丸め込む前に `ROUND` ビルトイン関数を併用するか、あらかじめ演算ロジック側で調整を入れる必要がある。このあたりの「手堅いコンビネーション」を次の実践コードで確認しよう。
—
3. 実践!VSAM入出力とONユニットを伴う堅牢なPL/Iプログラム
百聞は一見にしかずだ。実際の基幹系バッチを想定したサンプルコードを用意した。
VSAM(KSDS)から読み込んだパック十進数の売上データを、内部計算用に `FIXED BINARY` へ安全に変換し、万が一のオーバーフローに備えるための `ON FIXEDOVERFLOW` ユニットを組み込んだ実用的な構造になっている。
1
CONVEX: PROC OPTIONS(MAIN);
/—————————————————————-/
/ ワークエリアおよびファイル定義 /
/—————————————————————-/
DCL VSAM-IN-FILE FILE RECORD INPUT;
/ 入力レコード(VSAMからの生データ:パック十進数 / FIXED DECIMAL) /
DCL 1 IN-RECORD,
5 IN-CUST-ID CHAR(5),
5 IN-SALES-DEC FIXED DEC(9,2); / 9桁、小数2位のパック十進数 /
/ 内部計算用変数(FIXED BINARY) /
DCL WK-SALES-BIN FIXED BIN(31,2); / 32ビットフルワード相当のバイナリ /
DCL WK-ROUNDED-BIN FIXED BIN(31,0); / 整数部分のみの丸め後バイナリ /
/ 異常系制御用フラグ /
DCL ERR-OCCURRED BIT(1) INIT(‘0’B);
/—————————————————————-/
/ 割り込み条件(ONユニット)の定義 /
/ FIXED BINARY 変換時の桁あふれをトラップする /
/—————————————————————-/
ON FIXEDOVERFLOW
BEGIN;
DISPLAY(‘【SEVERE】固定小数点演算でオーバーフローを検知しました。’);
DISPLAY(‘該当顧客ID: ‘ || IN-CUST-ID);
ERR-OCCURRED = ‘1’B;
GOTO ERROR-ROUTINE;
END;
/ ファイルオープン /
OPEN FILE(VSAM-IN-FILE);
/ メイン処理ループ /
DO FOREVER;
READ FILE(VSAM-IN-FILE) INTO(IN-RECORD);
IF ENDFILE(VSAM-IN-FILE) THEN LEAVE;
/————————————————————/
/ BINARYビルトイン関数による型変換 /
/ 入力されたDECIMALデータを明示的にBINARY(31,2)へ変換する /
/————————————————————/
WK-SALES-BIN = BINARY(IN-SALES-DEC, 31, 2);
/ 小数点以下を四捨五入して整数化する場合のイディオム /
/ ROUND関数とBINARY関数をネストして確実に丸める /
WK-ROUNDED-BIN = BINARY(ROUND(IN-SALES-DEC, 0), 31, 0);
/ 変換後のデータを使った処理(省略) /
/ DISPLAY(‘CONVERTED BINARY SALES: ‘ || TO_CHAR相当の処理); /
END;
CLOSE FILE(VSAM-IN-FILE);
DISPLAY(‘正常終了しました。’);
RETURN;
ERROR-ROUTINE:
/ 異常終了時のクリーンアップ /
CLOSE FILE(VSAM-IN-FILE);
DISPLAY(‘異常終了処理を実行しました。ジョブを強制終了します。’);
SIGNAL ERROR;
END CONVEX;
—
4. 現場で役立つデバッグのコツとコーディング標準
このコードとテーマに関して、長年の経験から後輩たちにどうしても伝えておきたい「現場の知恵」をいくつか残しておこう。
1. 暗黙の型変換を信用するな
`WK-SALES-BIN = IN-SALES-DEC;` のように、`BINARY` 関数を省いて代入してもコンパイラは怒らないし、一見うまく動く。だが、大規模な改修で右辺の属性が変わった瞬間、意図しない精度の切り捨てやパフォーマンス低下(コンパイラが裏で生成する変換ルーチンの肥大化)を招く。型を合わせる時は必ず `BINARY(値, 精度, スケール)` を明示すること。
2. ONユニットは逃げ場ではなく最後の砦
サンプルに入れた `ON FIXEDOVERFLOW` は、予期せぬ巨大データが入ってきたときのシステムクラッシュを防ぐ防壁だ。しかし、これに頼り切るのではなく、事前に入力データのレンジチェック(条件分岐)を行うのがバッチ設計の基本中の基本である。
3. バイナリの符号と精度の罠
`FIXED BINARY(31)` と指定した場合、最上位ビットは符号(正負)に使われるため、実際に表現できる正の最大値は $2^{31}-1$ (約21億)だ。これを忘れて、データ件数や累計金額を格納してオーバーフローを起こす事故が後を絶たない。桁数が大きいことが予想される場合は、惜しまず `FIXED BIN(63)` を選択する勇気を持て。
メインフレームのPL/Iは、古い言語に見えて、メモリ管理やデータ構造の制御において極めてロジカルで強力なツールだ。仕様の隅々まで理解し、機械に無駄な苦労をさせない洗練されたコードを書けるようになってくれ。頼むぞ。
