【実務・中級編】ABS関数の算術演算と符号ビットの操作 – PL/Iの基本構文とデータ制御実践ガイド

おい、最近入った若手が「PL/Iって変数名に `IF` とか使っても怒られないんですね、すげえ変な言語だな」って驚いてたんだが……お前さんはどうだ?

そう、PL/Iには「予約語(Keywords)」という概念がない。コンパイラは文脈(Context)からそれが命令なのか変数名なのかを判断する。だから `IF = IF + 1;` なんていう悪夢のようなコードすら(やろうと思えば)文法上は通ってしまう。これがこの言語の懐の深さであり、同時に魔窟たる所以だ。

今日はその中でも、基幹システムの数値演算、特に「ABS関数の挙動と符号ビットの操作、そして背後にあるIBMメインフレームのハードウェア命令」について、現場の泥臭い知見を交えて徹底的に解説してやる。バッチ改修やマイグレーションの現場で「なぜか数値がおかしい」「S0C7(データ例外)が飛ぶ」と夜中に冷や汗をかかないために、ここでしっかり頭に叩き込んでおけ。

—

1. 予約語を持たないPL/Iの優位性と「危険な罠」

まず大前提として、PL/Iの識別子(変数名)のルールを確認しておこう。
最大31文字(Enterprise PL/Iであればもっと長いが、レガシー互換では31文字が基本)、英数字および国別文字(`#`, `$`, `@`)が使え、先頭は必ず英字または国別文字で始まる。

そして先ほども言った通り、PL/IにはCOBOLのような「厳格な予約語」がない。
例えば、以下のようなコードを見てほしい。

DECLARE
ABS FIXED BINARY(31),
RESULT FIXED BINARY(31);

ABS = -500;
RESULT = ABS(ABS);

……おいおい、笑えない冗談だと思うだろ? これ、コンパイルエラーにならないんだよ。
1つ目の `ABS` は変数名、2つ目の `ABS` は組み込み関数(BUILTIN)名、そして最後のカッコの中は変数としての `ABS` だ。コンパイラは文脈を読んで「お、ここは関数呼び出しだな」「こここは代入先の変数だな」と賢く解釈してくれる。

だがな、人間の目はそうはいかない。 こんなコードを書いた日には、後から保守に入るプログラマ全員から呪われることになる。だからこそ、現場のコーディング標準では「組み込み関数名を変数名に流用することは厳禁」という不文律があるんだ。これを破る奴は、どんなにロジックが綺麗でも、ウチのチームでは容赦なく差し戻しだ。

—

2. ABS関数の正体:FIXED BINARYとFLOATの演算メカニズム

さて、本題の `ABS` 関数(絶対値算出)だ。
金融系の勘定系バッチや、積算・残高管理のプログラムで、マイナス残高の補正や差額計算の絶対値化に毎日のように登場する。

PL/Iの `ABS` は、引数のデータ型に応じて適切な内部処理を行う。
特にメインフレーム(z/Architecture)の基幹系で圧倒的に多く使われる `FIXED BINARY`(固定小数点二進数:半精度(15), 倍精度(31), 4倍精度(63))を扱うとき、コンパイルされたマシン語レベルで何が起きているかを知っているか?

ハードウェア命令と符号ビットのクリア

32ビットの `FIXED BINARY(31)` の場合、最上位ビット(Bit 0)が符号ビット(0がプラス、1がマイナス)になる。
素朴なアルゴリズムで絶対値を求めようとすると、「値が負(マイナス)かどうかを判定(Branch)し、負なら符号反転(-1を掛ける、あるいは2の補数をとる)」という処理になりそうだ。

しかし、IBMのハイエンドプロセッサ(System z)では、もっと効率的な命令が用意されている。それがLPR(Load Positive Register)命令だ。
LPR命令は、レジスタの値をロードする際に、自動的に符号ビットを強制クリア(または反転して絶対値化)し、条件コード(CC)を設定する。

PL/Iで `BUILTIN` 宣言された `ABS` 関数を適切に使用し、コンパイラに最適化(OPTIMIZE(2)以上)を指示しておくと、コンパイラはこのハードウェアの特性を理解して、無駄な分岐命令(Branch)を挟まない、極めて高速なマシン語コードを吐き出してくれる。

—

3. 実践:VSAM入力・ONユニット制御を伴うバッチ処理の全体像

口で言うだけではピンとこないだろう。
実際に、VSAM(KSDS)から口座マスタを読み込み、貸付金のマイナス残高を `ABS` 関数で絶対値化して、監査用ワークファイルに出力する典型的なバッチ処理のサンプルコードを用意した。

大文字ベース、適切なインデント、そして現場で必須の `ON ENDFILE` や `ON UNDEFINEDFILE` といったエラー制御(ONユニット)も組み込んである。隅々まで目を通せ。

  • 勘定系バッチ処理サンプル: 貸付残高絶対値変換プログラム


LOANABSV: PROC OPTIONS(MAIN);

  • — 組み込み関数の明示的宣言 —

DCL ABS BUILTIN;

  • — ファイル定義 (VSAM KSDS および 順編成ファイル) —

DCL ACCTFILE FILE INPUT
ENVIRONMENT(VSAM);
DCL WKFILE FILE OUTPUT
ENVIRONMENT(FB LRECL(80) BLKSIZE(800));

  • — 変数定義 —

DCL IO_EOF BIT(1) INIT(‘0’B);
DCL ERR_FLG BIT(1) INIT(‘0’B);

  • — レコード構造体 (VSAM入力) —

DCL 1 ACCT_REC,
5 ACC_NO CHAR(8), 口座番号
5 ACC_NAME CHAR(20), 顧客名
5 ACC_BAL FIXED BIN(31), 貸付残高(符号付)
5 ACC_STATUS CHAR(2); ステータス

  • — ワークレコード構造体 (出力) —

DCL 1 WK_REC,
%INCLUDE WK_LAYOUT; 外部レイアウトcopybook

  • ↑ 展開イメージ: ACC_NO, ACC_NAME, ABS_BAL(編集用)等
  • ===============================================================
  • 異常系制御 (ONユニット)
  • ===============================================================

ON ENDFILE(ACCTFILE)
IO_EOF = ‘1’B;

ON UNDEFINEDFILE(ACCTFILE) BEGIN;
PUT SKIP LIST(‘【致命的エラー】ACCTFILEのオープンに失敗しました。’);
ERR_FLG = ‘1’B;
SIGNAL FINISH;
END;

ON ERROR BEGIN;
PUT SKIP LIST(‘【システムエラー】予期せぬ例外が発生しました。ABENDを回避します。’);
DISPLAY(‘ABEND CODE: ‘ || ONCODE());
CLOSE FILE(ACCTFILE);
CLOSE FILE(WKFILE);
STOP;
END;

  • ===============================================================
  • 処理開始
  • ===============================================================

PUT SKIP LIST(‘ LOANABSV 処理開始 ‘);

OPEN FILE(ACCTFILE) INPUT;
OPEN FILE(WKFILE) OUTPUT;

  • 初回レコード読み込み (VSAMキー順アクセス)

READ FILE(ACCTFILE) INTO(ACCT_REC);

DO WHILE (^IO_EOF & ^ERR_FLG);

  • ———————————————————–
  • 【メインロジック】
  • 貸付残高がマイナス(借越等)の場合、ABS関数で絶対値化する
  • コンパイラはここで効率的な算術演算コードを生成する
  • ———————————————————–

IF ACC_BAL < 0 THEN DO;

  • ABS関数による絶対値取得 (内部でLPR命令相当の最適化)

WK_REC.ABS_BAL = ABS(ACC_BAL);

  • 監査ログ出力等の特殊処理をここに挟む場合がある

END;
ELSE
DO;
WK_REC.ABS_BAL = ACC_BAL;
END;

WK_REC.ACC_NO = ACCT_REC.ACC_NO;
WK_REC.ACC_NAME = ACCT_REC.ACC_NAME;
WK_REC.ACC_STAT = ACCT_REC.ACC_STATUS;

  • ワークファイルへの書き出し

WRITE FILE(WKFILE) FROM(WK_REC);

  • 次レコード読み込み

READ FILE(ACCTFILE) INTO(ACCT_REC);

END;

  • 後処理

CLOSE FILE(ACCTFILE);
CLOSE FILE(WKFILE);

PUT SKIP LIST(‘ LOANABSV 正常終了 ‘);

END LOANABSV;

—

4. 現場のシニアからのアドバイス:デバッグとマイグレーションの勘所

このコード、一見すると何てことのない単純なバッチ処理に見えるだろう。だが、レガシーシステムの保守やオープン系へのマイグレーション(例えばEnterprise PL/Iから別言語、あるいは最新コンパイラへの移行)の現場では、こういうコードが思わぬ落とし穴になる。

1. データ型の暗黙の変換に気をつけろ
もし `ACC_BAL` が `FIXED DECIMAL`(パック十進数:COMP-3)だったらどうなる?
`ABS` 関数はそのまま使えるが、内部で十進数から二進数への一時的なレジスタ展開、あるいは符号ニブル(最下位バイトのゾーン/符号部分、例えば `C` や `D`、`F`)のマスク操作が発生する。大量データを扱うバッチでこれをやると、CPU時間を無駄に食いつぶす原因になる。算術演算のデータ型は極力統一しろ。

2. オーバーフローの恐怖(S0C7の隣人)
`FIXED BINARY(31)` の表現できる最大値は `+2,147,483,647`、最小値は `-2,147,483,648` だ。
お気づきか? マイナス側の絶対値(`ABS(-2147483648)`)を計算しようとした瞬間、プラス側の表現可能範囲(`+2147483648`)を1だけオーバーフローする!
この極限値が入力データに紛れ込んでいると、ハードウェア例外(データ例外や固定小数点オーバーフロー割込み)が即座に発生し、バッチが阿鼻叫喚のドン底に突き落とされる。
実務では、入力データの境界値チェックを厳しく行うか、必要に応じて `FIXED BINARY(63)`(倍ワード)への拡張を検討するのがアーキテクトとしての腕の見せ所だ。

予約語がない自由度の高さに胡乱な目を向けず、コンパイラの挙動とメインフレームのハードウェア仕様を熟知した上でコードを書く。これぞ、生き残るメインフレームエンジニアの流儀だ。
次の改修では、お前が書いたコードの裏側でどんなマシン語が動いているか、少しだけ想像を巡らせてみてくれ。

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