【実務・中級編】固定小数点数の比較演算における内部挙動 – PL/Iの基本構文とデータ制御実践ガイド

お疲れ様です。今日も元気に基幹システムの夜間バッチログと格闘していることと思います。

メインフレームの現場では、日々何百万件ものレコードを処理するPL/Iプログラムが稼働しています。その中で、一見なんの変哲もない数値項目の比較処理(`IF`文など)が原因で、予期せぬデータ不一致や、最悪の場合はコンパイラの最適化に起因する難解なバグに直面したことはありませんか?

今回は、PL/Iにおける「固定小数点数(`FIXED BINARY` / `FIXED DECIMAL`)の比較演算における内部挙動」について、コンパイル時の正規化の仕組みから、VSAMやレコード入出力時の実務的な罠、そしてトラブルを防ぐためのコーディング標準まで、現場の知見を総動員して徹底的に解説します。

—

1. 固定小数点数の比較:なぜ「そのまま」比較できないのか?

まず大前提として、PL/Iはデータ型の定義が非常に柔軟(かつ厳格)な言語です。例えば、以下のような異なる精度を持つ2つの変数を比較するとしましょう。

  • `DCL A FIXED DEC(5, 0);` (5桁のパック十進数、最大 99999)
  • `DCL B FIXED BIN(15);` (15ビットの二進固定小数点数、最大 32767)

この2つを `IF A = B THEN …` で比較するとき、CPUはそのままレジスタ同士を比較しているわけではありません。PL/Iコンパイラは、比較演算を行う前に「どちらか一方、あるいは両方を共通の表現形式・精度(正規化)」に変換するコードを裏側で生成します。

内部正規化の基本ルール

1. データ型の昇格(Promotion): `FIXED DECIMAL` と `FIXED BINARY` が混在する場合、一般的に `FIXED DECIMAL` は内部的に `FIXED BINARY`(またはその逆、コンテキストによる)へ一時変換されるか、あるいはコンパイラが比較可能な一時ワークエリアを作成します。
2. スケール(小数点位置)の調整: `FIXED DEC(5,2)` と `FIXED DEC(7,0)` のような場合、小数点以下の桁数(スケール)を合わせるためのスケーリングファクターが適用されます。足りない桁は暗黙的にパディング(ゼロ埋め)されます。

この「暗黙の型変換と正規化」を理解していないと、マスターファイルとトランザクションファイルの突き合わせ(マッチング処理)で、本来一致すべきレコードがヒットしないという、夜間バッチ担当者にとって悪夢のような現象を引き起こすのです。

—

2. 実務の罠:VSAM入出力とデシマル・バイナリの混在

現場で最も多いトラブルは、COBOLから移植されたコピーブックや、外部システム連携のVSAMファイル(KSDS)を読み込む際に発生します。

たとえば、VSAMのキー項目が `FIXED BIN(31)`(フルワードのバイナリ)であるにもかかわらず、アプリケーション側のワーキングストレージで `FIXED DEC(9,0)` として受けてしまい、それをそのまま条件判定のキーに使っているケースです。

ここで厄介なのが、ゾーン10進数(DISPLAY)やパック10進数(DECIMAL)特有の正負の符号(C, D, Fなど)の解釈と、バイナリの2の補数表現の違いです。万が一、入力データにパックスペースの不正値やアンスリッドなデータが混入していた場合、比較演算そのものではなく、その前の正規化の段階で例外が発生します。

ONユニットによる例外制御の重要性

数値変換エラー(CONVERSION条件)を適切にハンドリングするためには、以下のように `ON CONVERSION` ブロックを仕込んでおくのが、ベテランSEの防衛的プログラミングの鉄則です。

1
ON CONVERSION
begin;
PUT SKIP EDIT (‘[WARN] 数値変換エラー検知: 不正なデータが検出されました。’) (A);
/ 必要に応じてエラーフラグを立て、安全に異常終了させる /
GOTO ERROR_RTN;
end;

—

ジ

それでは、実際に異なる精度の固定小数点数同士を安全に比較し、BUILTIN関数を活用して明示的に型を制御する実務的なサンプルコードを見てみましょう。

1
//
/ プログラム名: CMPCMP01 /
/ 概要 : 異なる精度を持つ固定小数点数の比較演算と正規化デモ /
//
CMPCMP01: PROC OPTIONS(MAIN);

/ — 変数宣言 — /
/ マスター側の数量(パック十進数: 7桁、小数点以下なし) /
DCL M_QTY FIXED DEC(7, 0) INIT(1500);

/ トランザクション側の数量(バイナリ: 15ビット、約4桁相当) /
DCL T_QTY_BIN FIXED BIN(15) INIT(1500);

/ 比較用の高精度変数(小数点ありのマスター) /
DCL M_RATE FIXED DEC(5, 2) INIT(12.50);
DCL T_RATE FIXED DEC(7, 4) INIT(12.5000);

/ — メイン処理 — /
PUT SKIP EDIT (‘=== 固定小数点数 比較演算テスト開始 ===’) (A);

/ 1. FIXED DEC と FIXED BIN の比較 /
/ コンパイラは暗黙的に型を合わせて比較しますが、明示的なキャストが安全です /
IF M_QTY = T_QTY_BIN THEN
PUT SKIP EDIT (‘[判定OK] 数量が一致しました: M_QTY=’, M_QTY) (A, F(7));
ELSE
PUT SKIP EDIT (‘[判定NG] 数量が不一致です。’) (A);

/ 2. 小数点桁数(スケール)が異なるDECIMAL同士の比較 /
/ M_RATE(5,2) と T_RATE(7,4) の比較 /
/ 内部的にはスケールの大きい方に合わせて桁合わせ(パディング)が行われます /
IF M_RATE = T_RATE THEN
PUT SKIP EDIT (‘[判定OK] レートが一致しました: M_RATE=’, M_RATE(5,2)) (A, F(5,2));
ELSE
PUT SKIP EDIT (‘[判定NG] レートのスケール差異による不一致が発生しました。’) (A);

/ 3. 【推奨】BUILTIN関数(BINARY / DECIMAL)を使った明示的型合わせ /
/ 暗黙の変換に頼らず、ビルトイン関数で型と精度をそろえてから比較する /
IF M_QTY = FIXED(T_QTY_BIN, 7, 0) THEN
PUT SKIP EDIT (‘[判定OK] 明示的キャストによる比較成功。’) (A);

RETURN;

ERROR_RTN:
PUT SKIP EDIT (‘[ABEND] 異常終了ルーチンを通過しました。’) (A);
signal ERROR;

END CMPCMP01;

—

4. ベテランからのアドバイス:保守・改修時のコーディング標準

大規模改修の際、「動いているから触らない」というのは禁物ですが、「安易にデータ定義の型(`DEC` から `BIN` など)を変更する」のも大ケガの元です。以下の鉄則をチーム内で共有してください。

1. 暗黙の型変換を過信しない: 異なる精度やデータ型の比較を行う場合は、コードの意図を明確にするために `FIXED` や `BINARY` などのBUILTIN関数を用いて明示的にキャスト(型変換)を行いましょう。コンパイラ任せにすると、コンパイラのバージョンアップや最適化オプション(`OPT(2)` など)の変更時に思わぬバグを踏む原因になります。
2. 計算結果の桁あふれ(OVERFLOW)に備える: 比較の前段階として四則演算が入る場合、中間項目の精度が不足していると、ハイオーダー桁が切り捨てられて「一致すべきものが一致しない」という幻のような現象に悩まされます。中間ワークは十分な精度(例: `FIXED DEC(15, 4)` など)を持たせてください。
3. テストデータ作成の勘所: 境界値テストでは、最大値・最小値だけでなく、「内部的なバイナリ表現と十進表現でパディング方法が変わる境界(例: 32767 と 32768)」を意識したテストケースを必ず盛り込みましょう。

基幹システムの命であるデータ整合性。PL/Iの内部挙動を完全に手の内に収め、後輩たちから「さすがですね」と言われる頼れるアーキテクトを目指していきましょう。今日のバッチ処理も無事に走り切ることを祈っています!

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