こんにちは!メインフレームの世界へようこそ。
JavaやCOBOLといったモダン、あるいはちょっとお堅いビジネス言語をバリバリ書いてきた方にとって、IBMメインフレームの「PL/I(ピーエルワン)」は、最初のうちはちょっと変わった異国のように感じられるかもしれません。
特に、データ定義のあたりを見ると「なんだこの記号の嵐は……?」と冷や汗が出てしまいますよね。でも、安心してください。一つひとつの仕様は、当時のエンジニアたちの「限られたメモリとCPUで、いかに美しく正確な帳票やデータを処理するか」という知恵と工夫の結晶なんです。
今回は、そんなPL/Iの数あるユニークな機能の中でも、帳票出力や画面表示で避けて通れない「PICTURE属性における’Z’(ゼロ抑制)の編集ルーチン」について、一緒に優しく紐解いていきましょう!
—
そもそもCOBOLやJavaとは何が違うの? PL/Iのデータ編集の考え方
Javaなら `DecimalFormat` や `String.format`、COBOLなら `PIC Z(9)9` なんかを使って金額の頭の「0」を消したりしますよね。
PL/Iでも基本の思想は同じなのですが、最大の違いは「データそのものの持ち方(内部表現)」と「見せ方(編集)」を明確に分離しつつ、同じ `PICTURE` というキーワードでシームレスに扱える点にあります。
たとえば、データベースやファイルから読み込んだ純粋な数値(計算ができる状態のデータ)を、そのまま人間が見やすい「きれいな文字(文字型データ)」に変換するプロセスを、PL/Iのコンパイラは内部で非常に巧妙に行っています。
ここで登場するのが、今回の主役である編集文字 `’Z’`(ゼロ抑制:Zero Suppression) です。
—
ゼロ抑制(’Z’)って、内部で一体どんな仕事をしているの?
例えば、プログラム内で扱っている数値データが `00001234`(ゾーン decimal 型やパック decimal 型など)だったとします。これをそのまま画面やプリンタに出すと、
`00001234`
……うーん、なんだか昔の銀行の通帳みたいでカッコ悪いですよね。これを、
` 1,234` (頭の無駄なゼロがスペースに置き換わる)
こうすっきりと見せたいときに使うのが `Z` マスクです。
編集ルーチンの内部ロジックをイメージしよう
PL/Iのコンパイラが生成する内部の変換ルーチンは、大まかに言うと「左から右へのスキャン処理」を行っています。
1. スキャンの開始:
編集マスク(例: `Z,ZZZ,999`)の左端から順にデータを照合していきます。
2. ゼロの検出と置換:
もし数値データ側が `0` で、かつマスク文字が `Z` である場合、ルーチンはそれを「スペース(空白 ‘ ‘)」に置き換えます。
3. 有効数字との出会い:
途中で `0` 以外の数字(あるいは `Z` ではなく `9` や小数点などの必須表示文字)に出会った瞬間、「これ以降のゼロは消さずにそのまま表示するモード」へとスイッチが切り替わります。
4. 完了:
残りの桁は数値がゼロであってもそのまま(あるいは `9` の指定に従って)出力されます。
つまり、`Z` は「有効な数字が出るまで、自分のところに来たゼロをスペースに変えて隠してあげる優しいカーテン」のようなものなんです。
—
実践!PL/Iコードで見るPICTURE編集の書き方
百聞は一見に如かず。実際にPL/Iでどのように記述するのか、サンプルコードを見てみましょう。
レガシーな現場の雰囲気を出すために、コードは大文字ベースで記述しています。
1
——————————————————————
- ゼロ抑制(’Z’)および編集マスクのサンプルプログラム
——————————————————————
Z_SUPPRESSION_SAMPLE: PROC OPTIONS(MAIN);
— 1. 内部計算用の数値データ定義(パック十進数など) —
DCL WS_RAW_AMOUNT FIXED DEC(9,0) INIT(1234); 小さい金額
DCL WS_ZERO_AMOUNT FIXED DEC(9,0) INIT(0); 完全にゼロ
DCL WS_LARGE_AMOUNT FIXED DEC(9,0) INIT(123456789); 大きな金額
— 2. 編集済みの文字データ定義(PICTUREによるマスク) —
- ‘Z’ はゼロをスペースに置き換えます
- ‘9’ はゼロであってもそのまま数字を出力します
Dcl WS_EDIT_1 PIC ‘ZZZ,ZZZ,ZZ9’;
Dcl WS_EDIT_2 PIC ‘ZZZ,ZZZ,ZZ9’;
Dcl WS_EDIT_3 PIC ‘ZZZ,ZZZ,ZZ9’;
— 3. データの代入(ここで自動的に編集ルーチンが走ります) —
WS_EDIT_1 = WS_RAW_AMOUNT;
WS_EDIT_2 = WS_ZERO_AMOUNT;
WS_EDIT_3 = WS_LARGE_AMOUNT;
— 4. 結果の確認(DISPLAYステータスでコンソールに出力) —
PUT SKIP EDIT (‘金額1 (1,234 ) : [‘, WS_EDIT_1, ‘]’) (A);
PUT SKIP EDIT (‘金額2 (全ゼロ ) : [‘, WS_EDIT_2, ‘]’) (A);
PUT SKIP EDIT (‘金額3 (123,456789): [‘, WS_EDIT_3, ‘]’) (A);
END Z_SUPPRESSION_SAMPLE;
このコードの実行結果はどうなる?
コンパイルして実行すると、コンソール(SYSOUT)にはこのように出力されます。
金額1 (1,234 ) : [ 1,234]
金額2 (全ゼロ ) : [ 0]
金額3 (123,456789): [123,456,789]
注目してほしいのは `金額2`(元の値が 0 の場合) です。
マスクの右端に `9` を配置しているため、すべての桁が `Z` によってスペースに置き換えられた後でも、一番右の `9` の位置だけは「値がゼロであっても必ず `0` を表示する」というルールが適用され、結果として `[ 0]` という美しい右詰めの表示になります。もしここも `Z` にしていたら、値が完全なゼロのときは「真っ白な空白(スペースだらけ)」になってしまいます。このあたりの「最後の砦としての `9`」の使い分けが、PL/I編集のテクニックの見せ所ですね。
—
初学者がハマりやすいポイントとアドバイス
ここで、移行プロジェクトなどで初心者がよくやってしまう「あるあるトラブル」をこっそりシェアしておきますね。
1. 領域切れ(Overflow)の恐怖
編集先の `PICTURE` 桁数が、元の数値データの最大桁数よりも小さいと、コンパイルエラーになるか、実行時にデータが切り捨てられて思わぬゴミデータ(あるいはシグナル)が発生します。マスクを定義するときは、「元の数値が入る最大の大きさ + カンマやマイナス記号分の余裕」を必ず計算してくださいね。
2. 文字型から数値型への逆変換(編集解除)
今回は「数値から文字」への変換でしたが、画面から入力された `ZZ,ZZZ` な文字列を計算用に戻すときは、コンパイラがよしなにやってくれるケースもありますが、予期せぬブランク(スペース)混入でOC4(S0C7)などのシステム異常終了(いわゆるデータ例外)を引き起こすことがあります。入力値のチェックはレガシーの世界でも命綱です。
—
おわりに
いかがでしたでしょうか?
PL/Iの `PICTURE` 属性と `Z` によるゼロ抑制ルーチンは、一見すると古めかしい呪文のようですが、紐解いてみると「限られた文字幅の中でデータをいかに美しく見せるか」というエンジニアの優しさが詰まった合理的な仕組みであることが分かります。
「レガシーシステムだから難しそう……」と身構える必要は全くありません。一つひとつのコードが語るストーリーに耳を傾ければ、PL/Iはとても素直で頼もしい相棒になってくれますよ。
日々のマイグレーションや保守作業、本当にお疲れ様です。あなたのメインフレームライフが少しでも快適なものになりますように!
