こんにちは!メインフレームの世界へようこそ。
JavaやCOBOLといったモダンな言語や業務系言語の経験がある方にとって、IBMの汎用機(メインフレーム)で動く「PL/I(ピーエルアイ)」という言語は、最初にそのソースコードを見たとき、ちょっとしたカルチャーショックを受けるかもしれませんね。
「えっ、変数名に予約語がないってどういうこと?」「`PIC ‘S999’` って何だか呪文みたい……」
そんな風に不安になっていませんか?
大丈夫です、怖がる必要は全くありませんよ。一つずつ蓋を開けて中身を見ていけば、PL/Iがいかに合理的で、当時のエンジニアたちの知恵が詰まった優しい言語であるかが分かってきます。
今回は、PL/Iのデータ制御のキモである「PIC ‘S’による符号付き数値の内部表現とゾーン10進数」について、JavaやCOBOLの感覚も交えながら、ゆっくり紐解いていきましょう。
—
1. 他言語とはココが違う!PL/Iのデータ定義の雰囲気
Javaなら `int` や `double`、COBOLなら `PIC S9(4) COMP` のように書くところを、PL/Iではピクチャ(PIC)句を使ってデータの見た目や持ち方を定義します。
ここで最初に驚くのが、PL/Iには「厳格な予約語(Keywords)」という概念がほとんどないという点です。例えば、`IF` や `DO` といった制御構文の名前さえも、文脈によっては変数名として使えてしまいます(コンパイラが前後の文脈を読んで賢く判断してくれます)。そのため、変数名を決める時も「この単語は予約語だから使えないんだっけ……」と悩む必要がありません。とても自由度が高いんです。
そんな自由なPL/Iですが、メインフレームの基幹データを扱う上では、メモリ(ストレージ)のバイト単位の構造を正確に理解しておく必要があります。その代表格が、今回スポットを当てるゾーン10進数(Zone Decimal)です。
—
2. ゾーン10進数ってなに?(COBOLのDISPLAY形式の仲間です)
Javaプログラマの皆さん、「数値はメモリ上で2進数(バイナリ)として保持されるもの」と思っていませんか?
しかし、銀行の通帳や基幹システムのマスターファイルを開いたとき、金額データなどが人間にとって「文字」のまま(EBCDICコードとして)記録されているのを見たことはありませんか?
メインフレームの世界では、人間が見てすぐに分かる文字データ形式のまま計算を行ったり保持したりすることがよくあります。これがゾーン10進数です。COBOLで言うところの `USAGE DISPLAY` に相当します。
例えば、数値の `123` というデータをメモリ上に置くとき、EBCDICコードでは次のように1バイトずつ文字として表現されます。
- `1` は EBCDIC で `F1`
- `2` は EBCDIC で `F2`
- `3` は EBCDIC で `F3`
前半の `F` の部分を「ゾーン部」、後半の `1, 2, 3` の部分を「数値部(ディジット部)」と呼びます。
「あれ? じゃあマイナスの数値を表すときはどうするの?」という疑問が湧きますよね。そこで登場するのが、今回の主役である `PIC ‘S…’` です。
—
3. PIC ‘S’ が隠し持つ「符号」の秘密
PL/Iで符号付きの数値を定義するとき、次のように書きます。
1
DCL WK_SEISAN_SU PIC ‘S999’ ; / 3桁の符号付き数値 /
この `S` は Sign(符号) を意味しています。
「3桁だから3バイト使うのかな?」と思いきや、ゾーン10進数(EBCDIC)の場合、一番右側の文字の「ゾーン部(上の4ビット)」の場所を間借りして、プラスかマイナスかを表現するという、実に省スペースで巧妙な仕組みになっています。
具体的に見てみましょう。
- プラスの `+123` を格納する場合
- `1` → `F1`
- `2` → `F2`
- `3` の部分が、プラスを表すゾーンコード(EBCDICなら `C` など)に置き換わり、`C3` になります。
- メモリ上の並びは `F1 F2 C3` となります。
- マイナスの `-123` を格納する場合
- `1` → `F1`
- `2` → `F2`
- `3` の部分が、マイナスを表すゾーンコード(EBCDICなら `D` など)に置き換わり、`D3` になります。
- メモリ上の並びは `F1 F2 D3` となります。
つまり、`PIC ‘S999’` と書いた場合、必要なメモリサイズは3バイトのまま変わりません。「符号のためにわざわざ1バイト余分に増やさなくていい」という、メインフレーム全盛期の貴重なメモリを節約するための先人の知恵なんです。
—
4. 実務で役立つ!PL/Iコード例と注意点
それでは、実際にこのゾーン10進数と符号付ピクチャ項目を使った簡単なPL/Iのサンプルプログラムを見てみましょう。マイグレーションやバッチ改修の際によく見かけるパターンの縮小版です。
1
/ ————————————————– /
/ ゾーン10進数と符号の扱いを確認するサンプルプログラム /
/ ————————————————– /
TEST_SAMPLE: PROC OPTIONS(MAIN);
/ 変数の宣言 /
DCL W_INP_AMT PIC ‘S9999’ INIT(‘-1234’); / 入力金額(符号付き4桁) /
DCL W_OUT_MSG CHAR(20); / 編集用メッセージ /
/ コンソールへ初期値を出力してみる /
PUT SKIP LIST (‘— ゾーン10進数の符号テスト開始 —‘);
PUT SKIP EDIT (‘格納された値: ‘, W_INP_AMT) (A, F(6));
/ 符号を反転させる演算を行ってみる /
W_INP_AMT = -W_INP_AMT; / マイナスを掛け算してプラスにする /
PUT SKIP EDIT (‘符号反転後の値: ‘, W_INP_AMT) (A, F(6));
PUT SKIP LIST (‘— テスト終了 —‘);
END TEST_SAMPLE;
ここで実務上のちょっとした罠(注意点)
JavaやC言語の感覚でいると、ピクチャ項目同士の演算や、バイナリデータ(FIXED BINARYなど)との混同で思わぬところで足をすくわれます。
1. 文字コード依存に注意する
ゾーン10進数はEBCDICコード体系の文字列表現に強く依存しています。もしこのデータをオープン系(ASCII環境)へそのまま移行する場合、ゾーン部の符号ビット(`C` や `D`)が文字化けしたり、符号が正しく認識されなくなったりするトラブルが非常によく起きます。移行の際は、必ずバイナリ(コンピュート形式)に変換するか、ASCII用の符号付き表現にマッピングし直す配慮が必要です。
2. オーバフローと桁あふれ
`PIC ‘S999’` の変数に `1000` などの4桁以上の値を代入しようとすると、コンパイルエラーや実行時エラーになるか、上の桁がシレッと切り捨てられて思えない数値(ゴミデータ)に化けることがあります。レガシーシステムではこの「サイレントな切り捨て」が原因のバウンド障害が恐れられていますので、桁数定義は厳密に行いましょう。
—
おわりに
いかがでしたでしょうか?
「PIC ‘S’による符号付き数値の内部表現」と言われると難しそうに聞こえますが、要するに「一番右側の文字の頭文字をちょっと借りて、プラスかマイナスかをこっそり記憶させている、EBCDICならではの省エネ技術」なんです。
レガシーシステムの言語仕様は、現代の言語に比べると泥臭く見えるかもしれません。しかし、当時のハードウェアの制約(メモリが数メガバイトしかなかった時代)の中で、データをいかに効率よく、かつ人間が目視で確認しやすく扱うかという、エンジニアたちの執念と工夫の結晶です。
「なんだ、そういうことか!」と仕組みさえ分かってしまえば、PL/Iのコードを読むのも少し楽しくなってきませんか?
メインフレームの荒海に漕ぎ出すあなたの背中を、少しでも押すことができたなら幸いです。それでは、次回のレガシー探訪もお楽しみに!
