【実務・中級編】PICTURE属性における’Z’(ゼロ抑制)の編集ルーチン – PL/Iの基本構文とデータ制御実践ガイド

おい、最近の若手はCOBOLやJavaばかりで、肝心のPL/Iを触る機会が減っているようだな。だが、我々が守る金融や公共の巨大な基幹システムでは、今この瞬間も何万ステップものPL/Iバッチが夜間稼働し、日本経済の血流を支えている。

今回は、基幹系システムの帳票出力やデータ連携において避けて通れないPICTURE属性における「Z(ゼロ抑制)」の編集ルーチンについて、コンパイラの内部挙動から実務でのハマりどころまで徹底的に叩き込んでやる。心して読め。

1. なぜPL/IのPICTURE句は奥が深いのか?

まず大前提として、PL/Iの設計思想に触れておこう。C言語やJavaのような言語と異なり、PL/Iには「厳密な意味での予約語(Keyword)」が存在しない。`IF`や`DECLARE`ですら、文脈によっては変数名として定義できてしまうという、初期の設計者たちの狂気すら感じる柔軟性を持っている。

そのPL/Iにおいて、データの表現形式を直感的に定義できるのが PICTURE(PIC)属性 だ。
数値データを画面やプリンタ、あるいは外部ファイルへ出力する際、そのままのゾーン decimal やパック decimal(COMP-3)のイメージで出力したらどうなる?
例えば `+00012345` なんていう、人間が見て吐き気がするような生データをエンドユーザーに見せるわけにはいかないだろ?そこで登場するのが編集(Editing)機能であり、その主役がゼロ抑制文字 `Z`だ。

2. `Z`(ゼロ抑制)の内部ロジックと編集マスクの適用プロセス

コンパイラは、PICTURE句で定義された編集マスク(例: `PIC ‘ZZZ,ZZ9.99’`)に遭遇すると、内部でどのような処理を行っているのか。ここを理解していないと、実務で「なぜか値が消えた」「スペース埋めになるはずがアスタリスクが出た」といった怪奇現象に直面したときに対応できなくなる。

ゼロ抑制の基本メカニズム

1. スキャン方向: PICTUREの編集は、原則として左から右へ進行する。
2. 抑制の伝播: `Z`は、対応する数値位置が「先行するゼロ(Leading Zero)」である場合、それをブランク(`X’40’`)に置き換える。
3. ストップ条件: 有効な数字(1〜9)、あるいは小数点(`.`)、またはゼロ抑制を解除する別の文字(`9`や“など)にヒットした時点で、それ以降の `Z` は通常の数字位置として扱われ、たとえ値がゼロであってもブランクにはならず `0` として出力される。

> ⚠️ 現場のシニアからの鉄則アドバイス
> 編集先の文字型変数の桁数が、元となる数値の有効桁数に対して小さすぎると、コンパイルエラーではなくコンパイル時の警告、あるいは実行時でのデータ例外(S0C7などの前兆)やフィールドのオーバーフロー(アスタリスク “ の連続出力)を引き起こす。この挙動を体で覚えておけ。

3. 実践!VSAMファイル出力とONユニットを伴うPL/Iプログラム

百聞は一見にしかずだ。実際のバッチプログラムを想定し、数値データを綺麗に編集してVSAM(KSDS)の代替インデックスやシーケンシャルファイルへ渡すコード例を示そう。

ここでは、入力された生データを読み込み、金額フィールドを `Z` マスクでゼロ抑制編集しつつ、万が一のデータ異常に備えて `ON CONVERSION`(データ変換エラー)を捕捉する堅牢な実装を見せる。

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

  • 顧客売上集計・帳票データ編集バッチ

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

DCL 1 IN_REC,
3 IN_CUST_ID FIXED BIN(31), 顧客ID
3 IN_NAME CHAR(20), 顧客名
3 IN_SALES_AMT DEC FIXED(11,2); 売上金額 (パック)

DCL 1 OUT_REC,
3 OUT_CUST_ID CHAR(8), 編集後顧客ID
3 OUT_NAME CHAR(20), 顧客名
3 OUT_SALES_AMT CHAR(15); 編集後売上金額 (Z編集)

DCL IN_EOF BIT(1) INIT(‘0’B); 終了フラグ

— ファイル定義 (SYSIN / SYSPRINT または VSAM) —
DCL SYSIN FILE RECORD INPUT;
DCL OUTFILE FILE RECORD OUTPUT;

— データ変換エラー(S0C7等の手前)を捕捉するONユニット —
ON CONVERSION
BEGIN;
PUT SKIP LIST(‘【警告】数値変換エラーを検出しブランク置換します。’);

  • 異常値が入ってきた場合のフェールセーフ処理

OUT_SALES_AMT = ‘ CONVERSION-ERR’;
GOTO ERROR_RECOVERY;
END;

— ファイルオープン —
OPEN FILE(SYSIN) INPUT, FILE(OUTFILE) OUTPUT;

— メインループ —
DO WHILE(^IN_EOF);

READ FILE(SYSIN) INTO(IN_REC);
IF IN_EOF THEN LEAVE;

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

  • 1. 顧客IDのゼロサプレス編集 (数値から文字への暗黙的/明示的変換)

———————————————————–
OUT_CUST_ID = EDIT(IN_CUST_ID, ‘ZZZZZZZ9’);

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

  • 2. 売上金額のゼロサプレス及びカンマ編集
  • 例: 0000012345.67 -> ” 1,234.57″

———————————————————–
OUT_SALES_AMT = EDIT(IN_SALES_AMT, ‘ZZZ,ZZZ,ZZ9.92’);

ERROR_RECOVERY:
— 編集後レコードを書き出し —
WRITE FILE(OUTFILE) FROM(OUT_REC);

END;

— ファイルクローズ —
CLOSE FILE(SYSIN), FILE(OUTFILE);

PUT SKIP LIST(‘正常終了:売上データの編集処理を完了しました。’);
RETURN;

END SALES_EDIT;

4. コードの勘所とバグを防ぐためのノウハウ

上のコードを見て、「なぜわざわざ `EDIT` ビルトイン関数を使っているのか?」と疑問に思った奴は鋭い。
PL/Iでは、変数の宣言時にPICTURE属性を付与していれば、代入文(`OUT_SALES_AMT = IN_SALES_AMT;`)を書くだけで、コンパイラが勝手に編集ルーチンをインライン展開してくれる。

しかし、あえて `EDIT` ビルトイン関数(またはプロシージャ)を意識することや、宣言時の挙動を理解しておくべき理由がある。

① 宣言時のPICTUREと代入時の暗黙的変換

もし変数宣言を以下のように行っている場合:
1
DCL OUT_SALES_AMT PIC ‘ZZZ,ZZZ,ZZ9.92’;

この変数に対して `OUT_SALES_AMT = IN_SALES_AMT;` と代入するだけで、コンパイラは自動的にパッキングデシマルから文字編集済みデータへの変換コードを生成する。これがPL/Iの強力なところだ。

② 浮動小数点や端数処理(ROUND)との組み合わせ

実務で一番多いトラブルが、「四捨五入(端数処理)のタイミング」だ。PICTUREのマスクに収める際、桁数が足りない部分の丸めはどうなるか?
PL/Iでは、代入時に自動で四捨五入(またはコンパイラのオプションに依存)されるが、厳密な金融計算を行う場合は、`ROUND` ビルトイン関数を明示的に挟んでからPICTURE変数に渡すのが、夜間バッチを絶対に止させないベテランの作法だ。

1

  • 明示的に小数点以下2桁で四捨五入してから編集へ渡す

OUT_SALES_AMT = ROUND(IN_SALES_AMT, 2);

5. おわりに:レガシーコードに向き合う誇り

「古い言語だから」「メインフレームだから」といって侮るなかれ。PL/Iの持つデータ制御の精密さ、そしてコンパイラによる最適化の度合いは、現代の言語であっても容易に真似できない完成度を誇っている。

PICTUREの `Z` 編集一つとっても、そこにはデータ長、符号の扱い、文字コード(EBCDIC)の特性、そしてハードウェアの浮動小数点演算の歴史が詰まっている。
次にバッチの改修や、オープン系へのマイグレーション調査を行うときは、単に「動けばいいや」ではなく、コンパイラが裏でどのような編集ルーチンを組み立てているのかを頭の中でイメージしながらコードに向き合ってほしい。

お前たちのその手腕が、日本の基幹インフラを動かしているのだ。誇りを持ってコードを書け。

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