こんにちは、基幹システムの現場を戦い抜いてきたエンジニアの皆さん。
毎日のように稼働するCOBOLやPL/Iの巨大なバッチジョブ、本当にお疲れ様です。
さて、現代でも金融や公共などのミッションクリティカルな領域で現役バリバリに稼働しているPL/Iですが、ふと後輩からこんな質問を受けたことはないでしょうか。
「先輩、数値項目を定義するときに `PIC ‘9(5)V99’` みたいに書いてるんですけど、この中の `V` って結局何をしているんですか? なんか小数点として文字が入るわけでもないし、ただのアルファベットに見えるんですが……」
……おっ、いいところに目を付けた。こういう疑問を持つ後輩は伸び代がある。
実はこの `V`(仮想小数点:Virtual Decimal Point)、PL/Iにおける固定小数点演算の心臓部であり、ここを曖昧に理解していると、ある日突然、金額計算で「数円のズレ」が発生したり、最悪の場合はコンパイラが裏で勝手に行ったスケール調整のせいで意図せぬ精度落ちやデータ損失を引き起こしたりする。
今回は、PL/Iのピクチャ属性における `V` の正体と、コンパイラが内部でどのように桁合わせ(スケールファクターの調整)を行っているのか、実務のソースコードやVSAMファイル入出力、さらにはお馴染みのONユニットの制御フローまで交えて、徹底的に解説しよう。
—
1. 仮想小数点 `V` の正体と、なぜ「予約語を持たない」PL/Iでそれが可能なのか
まず、PL/Iの言語仕様の根底にある思想について少し触れておこう。
C言語やJavaのようなモダンな言語に慣れた世代からすると、「変数名に予約語が使えないのは面倒だ」と感じるかもしれない。しかし、PL/Iには原則として厳格な「予約語(Reserved Words)」という概念が存在しない。`IF` や `DO` といったキーワードであっても、文脈次第では変数名として定義できてしまう(もちろん、そんな狂気的なコーディングをする人は現場にはいないが)。
この「コンテキスト依存」の柔軟な構文解析を行っているPL/Iコンパイラにとって、ピクチャ文字列(`PIC` 属性)の中に現れる `V` は、「データとしては実体を持たず、メモリ上にも物理的なバイトを消費しないが、コンパイラに対して小数点の位置を教えるための標識(マーク)」として機能する。
- 物理的な領域: `PIC ‘9(5)V99’` は、合計7桁の数値を保持するが、`V` のためのスペースは1バイトたりともメモリ上には割り当てられない。
- データの実際: カセットテープやDASD(直接アクセス記憶装置)上のVSAMファイル、あるいはレコード内では、バイナリやゾーン10進数として数字がただ連続して並んでいるだけである。
つまり、`V` は人間とコンパイラに対して、「ここを境に、上位が整数部、下位が小数部ですよ」と指し示すための仮想的な区切り線なのだ。
—
2. コンパイラによる内部的な桁合わせ(スケールファクター)のメカニズム
では、この `V` があることで、演算時にコンパイラは何をしているのか。ここが一番重要だ。
例えば、単価(整数部4桁、小数部2桁)と数量(整数部3桁、小数部0桁)を掛け合わせる処理を考えてみる。
- 単価変数: `PIC ‘9(4)V99’` (スケール:2)
- 数量変数: `PIC ‘9(3)’` =実質 `PIC ‘9(3)V’` (スケール:0)
これらを単純に乗算すると、数学的には小数点が「右に2つずれた」状態の結果になる。
PL/Iコンパイラは、コード生成の段階で「小数点位置(スケール)を一致させるための内部的な桁合わせコード」を自動的に埋め込む。
1. 位置合わせ(Align): 演算を行う前に、スケールの小さい方の変数を10のべき乗で自動的にシフト(スケーリング)する。
2. 演算実行(Calculate): 内部的な固定小数点レジスタで計算を行う。
3. 結果の代入(Assign & Round): 格納先の変数の `PIC` 定義に合わせて、再び小数点位置を合わせ、必要に応じて四捨五入(ROUNDオプション指定時)や切り捨てを行う。
もしこのメカニズムを理解していないと、マイグレーション時に他言語(例えばJavaの `BigDecimal` など)へロジックを移植する際、「なぜかPL/I版と計算結果の末尾が1円ずれる」といった不可解なバグに悩まされることになる。
—
3. 実践!VSAM入出力とONユニット、桁合わせを含むPL/Iサンプルコード
百聞は一見に如かず。実際のメインフレーム開発の現場を想定した、標準的なPL/Iバッチプログラムの骨格を見てみよう。
VSAM(KSDS)からレコードを読み込み、単価と数量から金額を計算、異常データ(数値エラーなど)をONユニットで捕捉しつつ処理する実用的なコードだ。
1
DCL: 顧客売上計算バッチ処理プログラム;
SALES_CALC: PROC OPTIONS(MAIN);
/ — 1. 変数宣言 — /
DCL 1 IN_REC, / VSAM入力レコード定義 /
5 CUST_ID PIC ‘9(5)’, / 顧客ID /
5 UNIT_PRICE PIC ‘9(4)V99’,/ 単価(整数4桁, 小数2桁)★Vを使用 /
5 QUANTITY PIC ‘9(3)’, / 数量(整数3桁, 小数0桁) /
5 FILLER PIC X(10); / 予備領域 /
DCL 1 OUT_REC, / 出力レコード定義 /
5 O_CUST_ID PIC ‘9(5)’,
5 O_TOTAL_AMT PIC ‘9(7)V99’,/ 金額(整数7桁, 小数2桁) /
5 O_MSG PIC X(30);
DCL EOF_FLG CHAR(1) INIT(‘OFF’);
DCL READ_COUNT FIXED BIN(31,0) INIT(0);
/ — 2. ファイル(VSAM)定義 — /
DCL SALES_FILE FILE RECORD
INPUT
ENVIRONMENT(KEYED);
DCL OUT_FILE FILE RECORD
OUTPUT;
/ — 3. ONユニット(条件制御:データ例外の捕捉) — /
/ ピクチャデータに不整合な文字(英字など)が混入していた場合のクラッシュを防ぐ /
ON CONVERSION
BEGIN;
O_MSG = ‘DATA CONVERSION ERROR OCCURRED.’;
PUT SKIP EDIT (‘ERROR CUST_ID: ‘, IN_REC.CUST_ID) (A, A);
/ 必要に応じたエラールーチンへジャンプ、または処理継続 /
GOTO ERROR_RTN;
END;
/ — 4. 処理メインループ — /
OPEN FILE(SALES_FILE), FILE(OUT_FILE);
/ 最初のレコード読み込み /
CALL READ_RECORD;
DO WHILE (EOF_FLG = ‘OFF’);
/ 桁合わせを意識した算術演算 /
/ UNIT_PRICE (V99) と QUANTITY (V0) の乗算。 /
/ コンパイラが自動的にスケールを調停し、O_TOTAL_AMT (V99) に格納する。 /
O_CUST_ID = IN_REC.CUST_ID;
O_TOTAL_AMT = IN_REC.UNIT_PRICE IN_REC.QUANTITY;
O_MSG = ‘NORMAL’;
/ 出力ファイルへ書き出し /
WRITE FILE(OUT_FILE) FROM(OUT_REC);
READ_COUNT = READ_COUNT + 1;
ERROR_RTN:
/ 次のレコード読み込み /
CALL READ_RECORD;
END;
/ — 5. 終了処理 — /
PUT SKIP EDIT (‘TOTAL PROCESSED RECORDS: ‘, READ_COUNT) (A, F(10));
CLOSE FILE(SALES_FILE), FILE(OUT_FILE);
RETURN;
/ — 内部サブルーチン:レコード読み込み — /
READ_RECORD: PROC;
READ FILE(SALES_FILE) INTO(IN_REC);
IF (END_OF_FILE_CONDITION) THEN / 擬似的なEOF判定 /
EOF_FLG = ‘ON’;
END READ_RECORD;
END SALES_CALC;
コードの解説と実務上の急所
1. `ON CONVERSION` ユニットの活用:
メインフレームの現場では、外部から流れ込んできたデータが必ずしも綺麗な数値であるとは限らない。古い電文や外部連携ファイルにゴミデータが混ざっていると、数値項目への代入時にコンバージョンエラーが発生する。このONユニットを張っておくことで、ジョブの異常終了(ABEND)を防ぎ、安全にエラーレコードを弾くことが可能になる。
2. `UNIT_PRICE QUANTITY` の裏側の挙動:
先ほど解説した通り、`UNIT_PRICE` の小数点以下2桁と、`QUANTITY` の小数点以下0桁の掛け算結果に対し、コンパイラは自動的に小数点位置を計算して `O_TOTAL_AMT` の `PIC ‘9(7)V99’` の型に綺麗にフィットさせるコードを吐き出す。プログラマが手動で100を掛けたり割ったりする必要はない。「コンパイラにスケール管理を任せる」、これがPL/Iの美学である。
—
4. ベテランから後輩へ送る、デバッグと改修の黄金律
最後に、実務でピクチャ属性や `V` に絡むトラブルに直面したとき、先輩たちがどのように調査しているか、その勘所を伝授しよう。
- ダンプリストの `HEX` 表示を確認しろ:
「計算結果がおかしい」というバグ調査の際、単に変数の中身を見るだけでなく、ストレージ・ダンプで該当領域の16進数(HEX)表現を確認すること。`V` は実体がないため、単にバイナリやゾーン10進数としてどう並んでいるかを見るだけで、コンパイラがどの桁をどう解釈しているかが一発で分かる。
- 暗黙の型変換(プロモーション)に気をつけろ:
異なる `PIC` 属性同士や、`FIXED BIN` と `FIXED DEC` を混ぜて演算させると、コンパイラが勝手に中間変数を生成し、想定外の桁あふれ(OVERFLOW)や精度落ちが起きることがある。特に大きな桁数を扱う金融計算では、演算の途中で意図せぬ丸めが発生していないか、コンパイルリストのクロスリファレンスと生成コードを必ず自分の目で追うこと。
レガシーシステムの寿命は、私たちがこうした言語の深層を正しく理解し、後世に正確なメンテナンスを行えるかどうかにかかっている。
「たかが `V`、されど `V`」。この小さなアルファベットに隠されたコンパイラの優しさと厳しさを味方につけて、今日も確実なシステム運用をやり遂げよう。
