こんにちは!世界最高峰のIBMメインフレームシステムアーキテクトにして、PL/Iの言語仕様を隅々まで知り尽くしたレガシー移行スペシャリスト、〇〇(←執筆者の名前を想像してください)です。
「PL/I」と聞いて、少し身構えてしまう方もいらっしゃるかもしれませんね。JavaやCOBOLといったモダンな言語に慣れ親しんだ皆さんにとって、メインフレームの世界は未知の領域に思えるかもしれません。でも、ご安心ください。一つ一つ丁寧に紐解いていけば、PL/Iは決して「怖い」言語ではありません。むしろ、その堅牢性と柔軟さで、今も世界中の基幹システムを支え続けている、非常に頼れる存在なんですよ。
今回は、PL/Iの中でも特に、COBOL経験者なら「おっ、これ似てる!」と感じるであろう「PICTURE(ピクチャ)属性」に焦点を当てていきます。特に数値の表示形式を制御する`’9’`と`’Z’`、そして符号付き数値の編集ルールについて、現場でのリアルな知見も交えながら、優しく丁寧に解説していきますね。
PL/Iプログラムの基本構造:まずは骨格から触れてみよう
本題に入る前に、PL/Iプログラムがどんな形をしているのか、軽く触れておきましょう。COBOLでいうところの「IDENTIFICATION DIVISION」や「PROCEDURE DIVISION」の骨格のようなもの、と思っていただければイメージしやすいでしょうか。
PL/Iのプログラムは、通常、以下の構造をとります。
//
/ PXXXXXXX: メインフレームでの一般的なパッケージ名(ライブラリ名)/
//
PXXXXXXX: PACKAGE OPTIONS(MAIN);
//
/ MAIN_PROG: メイン処理部。OPTIONS(MAIN)はここがエントリーポイント/
//
MAIN_PROG: PROCEDURE OPTIONS(MAIN);
/ ここに変数の宣言(DCL文)や処理ロジックを記述します /
/ 例: DCL WS_AMOUNT FIXED DECIMAL(7, 2); /
/ WS_AMOUNT = 123.45; /
/ PUT SKIP LIST(‘Hello, PL/I World!’); /
END MAIN_PROG; / メイン処理部の終わり /
END PXXXXXXX; / パッケージの終わり /
ご覧のように、`PACKAGE`と`PROCEDURE OPTIONS(MAIN)`が主要なブロックを形成します。`OPTIONS(MAIN)`が付いているのが、そのプログラムの「顔」、つまり最初に実行されるエントリーポイントになるんです。COBOLの`PROCEDURE DIVISION`がそのまま実行されるような感覚に近いですね。
この骨格の中に、これから学ぶPICTURE属性を使った変数の宣言や、実際の処理を記述していくことになります。
PICTURE属性とは?COBOL経験者ならニヤリとするはず!
さあ、いよいよ本題のPICTURE属性です!
COBOLを触ったことのある皆さんなら、「PIC句」と聞けばピンとくるはずですよね? PL/Iにも、数値や文字列の表示形式を定義するための`PICTURE`属性、通称「PIC句」が存在します。そして、その使い方はCOBOLと驚くほど似ています。
PL/Iでは、変数を宣言する際に`DCL`文を使います。この`DCL`文で、変数の名前、型、そして必要に応じて`PICTURE`属性を指定するわけです。
DCL 変数名 PICTURE ‘編集文字列’;
この「編集文字列」が、今回の主役である`’9’`や`’Z’`、そして符号に関する文字たちになります。
‘9’ の世界:どんな時も数字を「強制表示」する威力
まずは、基本中の基本である`’9’`から見ていきましょう。
`’9’`は、その桁に必ず数字を表示させる、という役割を持っています。例えるなら、どんな数字が来ても、その桁には必ず「数字が座る席」を用意するようなものです。もしその桁に何も表示すべき数字がない場合(例えば、`123`という数字を`’99999’`で表示しようとした時)、先行する空いた桁には強制的にゼロが埋められます。
DCL WS_NUM_PIC9 PICTURE ‘99999’; / 5桁の数字を強制表示 /
WS_NUM_PIC9 = 123;
/ 結果: ‘00123’ /
WS_NUM_PIC9 = 0;
/ 結果: ‘00000’ /
WS_NUM_PIC9 = -45; / マイナスの値を入れても、’9’だけでは符号は表示されません /
/ 結果: ‘00045’ /
/ 注意!PICTURE属性で符号を表示しない場合、代入時に符号は無視されます。/
/ これは後述の符号編集文字を使わないと危険な挙動にも繋がります。 /
ご覧の通り、`’9’`は先行するゼロも律儀に表示します。これが「強制表示」たる所以です。経理系のシステムで、帳票にきっちり桁数を揃えてゼロ埋めしたい場合などによく利用されますね。
‘Z’ の世界:先行ゼロを「抑制」する魔法
次に`’Z’`です。これはCOBOLの「ゼロ抑制」と同じ考え方になります。
`’Z’`は、その桁が先行ゼロである場合に限り、空白に置き換えるという魔法を持っています。つまり、数字の有効桁より前のゼロは表示しない、というわけです。例えるなら、「0が来たら透明になる魔法の席」といったところでしょうか。
DCL WS_NUM_PICZ PICTURE ‘ZZZ99’; / 先行ゼロは抑制し、下2桁は強制表示 /
WS_NUM_PICZ = 123;
/ 結果: ‘ 123’ (先頭の’Z’がゼロを抑制し空白に) /
WS_NUM_PICZ = 0;
/ 結果: ‘ 00’ (下2桁の’99’は強制表示。’ZZZ’はゼロを抑制) /
WS_NUM_PICZ = 5;
/ 結果: ‘ 05’ /
WS_NUM_PICZ = 1000;
/ 結果: ‘ 1000’ (有効な数字なので’Z’も数字を表示) /
WS_NUM_PICZ = -7;
/ 結果: ‘ 07’ (ここでも、’Z’だけでは符号は表示されません) /
`’Z’`と`’9’`を組み合わせることで、表示形式を柔軟に制御できることがお分かりいただけるでしょうか? 帳票の金額欄などで、数字の頭に余計なゼロを表示させたくない場合に非常に重宝します。
符号を制する!PL/Iの符号編集文字 ‘S’ と ‘-‘
ここが、COBOL経験者の方も「なるほど!」と唸るかもしれないポイントです。PL/Iの`PICTURE`属性では、数値の符号を明示的に表示させるための特別な編集文字が存在します。代表的なものとして`’S’`と`’-‘`を見ていきましょう。
‘S’:常に符号を表示する番人
`’S’`は、数値の符号(プラス`+`またはマイナス`-`)を常に表示します。この`’S’`は、PICTURE文字列の先頭か末尾に1つだけ置くことができます。
DCL WS_SIGNED_S_FRONT PICTURE ‘S999’; / 先頭に符号 /
DCL WS_SIGNED_S_BACK PICTURE ‘999S’; / 末尾に符号 /
WS_SIGNED_S_FRONT = 123;
/ 結果: ‘+123’ /
WS_SIGNED_S_FRONT = -123;
/ 結果: ‘-123’ /
WS_SIGNED_S_FRONT = 0;
/ 結果: ‘+000’ /
WS_SIGNED_S_BACK = 45;
/ 結果: ‘045+’ /
WS_SIGNED_S_BACK = -45;
/ 結果: ‘045-‘ /
`’S’`を使うと、正の値にも必ず`+`が表示されるのが特徴です。厳密な帳票や、システム間連携のデータ形式で符号を明示する必要がある場合に使われます。
‘-‘:マイナスの場合のみ表示する賢者
`’-‘`は、数値がマイナスの場合にのみ符号`’-‘`を表示し、正の値またはゼロの場合は空白に置き換えます。これもPICTURE文字列の先頭か末尾に置くことができますが、`’Z’`と同じように、先行ゼロ抑制の役割も兼ね備えているのが特徴です。
DCL WS_SIGNED_MINUS PICTURE ‘-ZZZ9’; / 先行ゼロ抑制とマイナス符号 /
WS_SIGNED_MINUS = 123;
/ 結果: ‘ 123’ (プラスなので符号は空白) /
WS_SIGNED_MINUS = -123;
/ 結果: ‘- 123’ (マイナスなので符号を表示) /
WS_SIGNED_MINUS = 0;
/ 結果: ‘ 00’ (ゼロなので符号は空白) /
WS_SIGNED_MINUS = -5;
/ 結果: ‘ -5′ /
`’-‘`は、`’Z’`と同じく先行ゼロ抑制の機能も持つため、一般的には`’Z’`と組み合わせて使われることが多いです。会計帳票などで、マイナスの金額だけを分かりやすく表示したい場合に非常に便利ですよね。
実践!PL/Iコードで挙動を確認
ここまで学んだ`’9’`, `’Z’`, `’S’`, `’-‘`の挙動を、実際のPL/Iコードで確認してみましょう。
//
/ P_PIC_SAMPLE: PICTURE属性の挙動を確認するサンプルプログラム /
//
P_PIC_SAMPLE: PACKAGE OPTIONS(MAIN);
MAIN_PROG: PROCEDURE OPTIONS(MAIN);
//
/ 変数宣言部 /
/ テスト用の数値データ /
//
DCL TEST_NUM_P FIXED DECIMAL(5) INIT(123); / 正の数 /
DCL TEST_NUM_M FIXED DECIMAL(5) INIT(-45); / 負の数 /
DCL TEST_NUM_ZERO FIXED DECIMAL(5) INIT(0); / ゼロ /
DCL TEST_NUM_LARGE FIXED DECIMAL(5) INIT(12345); / 大きな数 /
DCL TEST_NUM_SMALL FIXED DECIMAL(5) INIT(7); / 小さな数 /
//
/ PICTURE属性を持つ変数(編集後の格納先) /
//
DCL WS_PIC_99999 PICTURE ‘99999’; / 5桁強制表示 /
DCL WS_PIC_ZZZ99 PICTURE ‘ZZZ99’; / 先行ゼロ抑制、下2桁強制表示 /
DCL WS_PIC_S999 PICTURE ‘S999’; / 先頭に符号強制表示 /
DCL WS_PIC_999S PICTURE ‘999S’; / 末尾に符号強制表示 /
DCL WS_PIC_MINUSZZ9 PICTURE ‘-ZZZ9’; / 先行ゼロ抑制、マイナス符号 /
DCL WS_PIC_MINUS_Z_BACK PICTURE ‘ZZZ9-‘; / 末尾にマイナス符号 /
//
/ 各PICTURE属性の挙動をPUT文で出力してみます /
//
PUT SKIP LIST(‘— PICTURE ”9” の挙動 —‘);
WS_PIC_99999 = TEST_NUM_P;
PUT SKIP LIST(‘123 -> PICTURE ”99999”: ”’ || WS_PIC_99999 || ””);
WS_PIC_99999 = TEST_NUM_ZERO;
PUT SKIP LIST(‘0 -> PICTURE ”99999”: ”’ || WS_PIC_99999 || ””);
WS_PIC_99999 = TEST_NUM_M;
PUT SKIP LIST(‘-45 -> PICTURE ”99999”: ”’ || WS_PIC_99999 || ”’ (符号無視に注意!)’);
PUT SKIP(2) LIST(‘— PICTURE ”Z” の挙動 —‘);
WS_PIC_ZZZ99 = TEST_NUM_P;
PUT SKIP LIST(‘123 -> PICTURE ”ZZZ99”: ”’ || WS_PIC_ZZZ99 || ””);
WS_PIC_ZZZ99 = TEST_NUM_SMALL;
PUT SKIP LIST(‘7 -> PICTURE ”ZZZ99”: ”’ || WS_PIC_ZZZ99 || ””);
WS_PIC_ZZZ99 = TEST_NUM_ZERO;
PUT SKIP LIST(‘0 -> PICTURE ”ZZZ99”: ”’ || WS_PIC_ZZZ99 || ””);
WS_PIC_ZZZ99 = TEST_NUM_LARGE;
PUT SKIP LIST(‘12345 -> PICTURE ”ZZZ99”: ”’ || WS_PIC_ZZZ99 || ”’ (桁あふれに注意!)’);
WS_PIC_ZZZ99 = TEST_NUM_M;
PUT SKIP LIST(‘-45 -> PICTURE ”ZZZ99”: ”’ || WS_PIC_ZZZ99 || ”’ (符号無視に注意!)’);
PUT SKIP(2) LIST(‘— PICTURE ”S” の挙動 —‘);
WS_PIC_S999 = TEST_NUM_P;
PUT SKIP LIST(‘123 -> PICTURE ”S999”: ”’ || WS_PIC_S999 || ””);
WS_PIC_S999 = TEST_NUM_M;
PUT SKIP LIST(‘-45 -> PICTURE ”S999”: ”’ || WS_PIC_S999 || ””);
WS_PIC_S999 = TEST_NUM_ZERO;
PUT SKIP LIST(‘0 -> PICTURE ”S999”: ”’ || WS_PIC_S999 || ””);
WS_PIC_999S = TEST_NUM_P;
PUT SKIP LIST(‘123 -> PICTURE ”999S”: ”’ || WS_PIC_999S || ””);
WS_PIC_999S = TEST_NUM_M;
PUT SKIP LIST(‘-45 -> PICTURE ”999S”: ”’ || WS_PIC_999S || ””);
PUT SKIP(2) LIST(‘— PICTURE ”-” の挙動 —‘);
WS_PIC_MINUSZZ9 = TEST_NUM_P;
PUT SKIP LIST(‘123 -> PICTURE ”-ZZZ9”: ”’ || WS_PIC_MINUSZZ9 || ””);
WS_PIC_MINUSZZ9 = TEST_NUM_M;
PUT SKIP LIST(‘-45 -> PICTURE ”-ZZZ9”: ”’ || WS_PIC_MINUSZZ9 || ””);
WS_PIC_MINUSZZ9 = TEST_NUM_ZERO;
PUT SKIP LIST(‘0 -> PICTURE ”-ZZZ9”: ”’ || WS_PIC_MINUSZZ9 || ””);
WS_PIC_MINUS_Z_BACK = TEST_NUM_P;
PUT SKIP LIST(‘123 -> PICTURE ”ZZZ9-”: ”’ || WS_PIC_MINUS_Z_BACK || ””);
WS_PIC_MINUS_Z_BACK = TEST_NUM_M;
PUT SKIP LIST(‘-45 -> PICTURE ”ZZZ9-”: ”’ || WS_PIC_MINUS_Z_BACK || ””);
END MAIN_PROG;
END P_PIC_SAMPLE;
実行結果(想定される出力)
— PICTURE ‘9’ の挙動 —
123 -> PICTURE ‘99999’: ‘00123’
0 -> PICTURE ‘99999’: ‘00000’
-45 -> PICTURE ‘99999’: ‘00045’ (符号無視に注意!)
— PICTURE ‘Z’ の挙動 —
123 -> PICTURE ‘ZZZ99’: ‘ 123’
7 -> PICTURE ‘ZZZ99’: ‘ 07’
0 -> PICTURE ‘ZZZ99’: ‘ 00’
12345 -> PICTURE ‘ZZZ99’: ‘
ERROR ###’ (桁あふれに注意!)
-45 -> PICTURE ‘ZZZ99’: ‘ 45’ (符号無視に注意!)
— PICTURE ‘S’ の挙動 —
123 -> PICTURE ‘S999’: ‘+123’
-45 -> PICTURE ‘S999’: ‘-045’
0 -> PICTURE ‘S999’: ‘+000’
123 -> PICTURE ‘999S’: ‘123+’
-45 -> PICTURE ‘999S’: ‘045-‘
— PICTURE ‘-‘ の挙動 —
123 -> PICTURE ‘-ZZZ9’: ‘ 123’
-45 -> PICTURE ‘-ZZZ9’: ‘ -45’
0 -> PICTURE ‘-ZZZ9’: ‘ 00’
123 -> PICTURE ‘ZZZ9-‘: ‘ 123 ‘
-45 -> PICTURE ‘ZZZ9-‘: ‘ 45-‘
出力結果に関する補足:
- `PICTURE ‘99999’`に`-45`を代入した場合、符号は無視され`00045`となります。これは`PICTURE`変数が符号編集文字を含まない場合、内部的に数値の絶対値が編集されるためです。COBOLの`PIC 9(n)`と同じ感覚ですね。
- `PICTURE ‘ZZZ99’`に`12345`を代入した場合、定義された桁数(5桁)を超えているため、PL/Iのコンパイラや実行環境によってはエラーが発生したり、予期しない結果になったりします。多くの場合は“や`#`のようなエラー文字で埋め尽くされます。これはCOBOLのオーバーフローと同じ感覚で、桁あふれには注意が必要です。
現場のPL/Iシステムアーキテクトが語る!PIC属性の落とし穴と教訓
PICTURE属性は非常に強力で便利ですが、メインフレームの現場ではいくつか注意すべき点があります。特に他言語からPL/Iに触れる方が陥りやすい落とし穴について、実体験を交えてお話ししますね。
落とし穴1:PICTURE変数は「見た目は数字、中身は文字」
これが一番重要かもしれません。JavaやCOBOLの感覚で、
DCL AMOUNT_DISP PICTURE ‘99999’;
DCL TAX_DISP PICTURE ’99’;
AMOUNT_DISP = 100;
TAX_DISP = 8;
TOTAL_DISP = AMOUNT_DISP + TAX_DISP; / これは危険! /
といったコードを書いてしまうと、思わぬバグに繋がることがあります。
PL/Iの`PICTURE`属性を持つ変数は、あくまで文字型(CHARACTER型)として扱われます。内部的には「’00100’」や「’08’」といった文字列が格納されているんです。
そのため、上記のように`PICTURE`変数同士で直接足し算のような数値演算を行うと、PL/Iコンパイラは内部的にこれらの文字列を数値に変換しようとします。この変換が暗黙的に行われるため、パフォーマンスの低下を招いたり、変換ルールによっては予期しない結果になったりするリスクがあるんです。
教訓:
「数値演算は数値型で、表示編集はPICTUREで」を徹底しましょう。
DCL AMOUNT_RAW FIXED DECIMAL(5) INIT(100); / 内部的な数値データ /
DCL TAX_RAW FIXED DECIMAL(2) INIT(8); / 内部的な数値データ /
DCL TOTAL_RAW FIXED DECIMAL(7); / 内部的な数値データ /
DCL AMOUNT_DISP PICTURE ‘99999’; / 表示用 /
DCL TAX_DISP PICTURE ’99’; / 表示用 /
DCL TOTAL_DISP PICTURE ‘9999999’; / 表示用 /
TOTAL_RAW = AMOUNT_RAW + TAX_RAW; / 数値型同士で計算!これが正しい /
AMOUNT_DISP = AMOUNT_RAW; / 計算結果を表示用に編集 /
TAX_DISP = TAX_RAW;
TOTAL_DISP = TOTAL_RAW;
PUT SKIP LIST(‘TOTAL_DISP: ”’ || TOTAL_DISP || ””); / ‘0000108’ /
このように、内部的な計算には`FIXED DECIMAL`(固定小数点数)や`BINARY FIXED`(2進数固定小数点数)といった純粋な数値型を使い、表示が必要な時にだけ`PICTURE`変数に代入して編集する、という使い分けが非常に重要になります。これはCOBOLの`COMP-3`や`PACKED-DECIMAL`と`DISPLAY`の使い分けにも似ていますね。
落とし穴2:小数点位置の暗黙のルールと符号の有無
COBOLでは`PIC S9(n)V9(m)`のように`V`で小数点位置を表現しますが、PL/Iの`PICTURE`属性では`V`は小数点位置を明示的に示す編集文字ではありません。PL/Iで小数点を含む数値を`PICTURE`で編集する場合、`’.’`(ピリオド)を編集文字として使います。
例:`DCL PRICE_DISP PICTURE ‘999.99’;`
また、`PICTURE`属性で符号編集文字(`S`や`-`)を指定しない限り、負の値を代入しても符号は表示されず、絶対値が編集されます。これは前述のコード例でも触れましたが、特にCOBOLからPL/Iへのマイグレーションを行う際、COBOLの`PIC 9(n)`(符号なし数字)と`PIC S9(n)`(符号付き数字)の違いを意識せずにPL/Iの`PICTURE`を設計してしまうと、出力結果が変わってしまう可能性があります。
教訓:
- 小数点が必要な場合は`’.’`を明示的に指定する。
- 符号が必要な場合は`’S’`や`’-‘`などの符号編集文字を必ず指定する。
PL/Iの`PICTURE`は、COBOLのそれと似ていますが、細かいところで独自のルールがあります。公式マニュアルを紐解くことももちろん大切ですが、実際に動かしてみるのが一番の理解への近道ですよ。
まとめ:PL/IのPICTURE属性は怖くない、むしろ強力な味方!
いかがでしたでしょうか? PL/Iの`PICTURE`属性における`’9’`と`’Z’`による数値編集、そして符号付き数値の編集ルールについて、COBOLとの比較や現場の知見を交えながら解説してきました。
- `’9’`:常に数字を強制表示し、先行ゼロも埋める。
- `’Z’`:先行ゼロを空白に抑制する。
- `’S’`:常に符号(`+`または`-`)を表示する。
- `’-‘`:マイナスの場合のみ符号(`-`)を表示し、正の場合は空白(先行ゼロ抑制機能も持つ)。
これらの編集文字を組み合わせることで、どんな表示形式にも対応できる、非常に柔軟で強力な機能であることがお分かりいただけたかと思います。
最初は少し戸惑うかもしれませんが、一度コツを掴んでしまえば、PL/Iの`PICTURE`属性はあなたの強力な味方になってくれるはずです。メインフレームの基幹システムがなぜこれほど長く稼働し続けているのか、その一端に触れることができたのではないでしょうか。
PL/Iの世界は奥深いですが、一つずつ紐解いていけば、きっとその魅力に引き込まれるはずです。これからも一緒に、メインフレームの深い知見を学んでいきましょう!
それでは、また次回の記事でお会いしましょう!
