こんにちは、皆さん。日々のメインフレーム保守や、迫り来るオープン系へのマイグレーション調査、本当にお疲れ様です。
今日もJCLのRC(リターンコード)が「0000」で正常終了したログを見てホッとしているところかもしれませんが、ちょっと待ってください。夜間バッチで突如発生する「S0C7(データ例外)」や、計算結果の微妙な端数ズレ、あるいはVSAMファイルからのアンロードデータ読込時の桁あふれ……。こうしたトラブルの根底には、大抵の場合、データ定義の理解不足が潜んでいます。
特にCOBOLからPL/Iの世界に入ってきたエンジニアが最初に頭を抱えるのが、ピクチャ句(PIC)を用いた数値データの定義と、その内部挙動です。
今回は、PL/Iの真骨頂とも言える PIC ‘9’ と ‘V’ を用いた固定小数点数の定義と内部挙動 について、コンパイラの裏側の動きまで含めて徹底的に解説します。実務でそのまま使えるサンプルコードも用意したので、コーヒーでも飲みながらじっくり読んでいってください。
—
1. 予約語を持たないPL/Iの柔軟性と、ピクチャデータの罠
PL/Iの最大の特徴の一つは、「言語仕様上に予約語が極めて少ない(Contextual Keywords)」という点です。COBOLのように `VALUE` や `PICTURE` といった専用の予約語がガチガチに決まっているわけではなく、文脈によって識別子がキーワードとして解釈されます。
この自由度の高さゆえに、変数の型定義やピクチャ(PIC)の指定方法を誤ると、コンパイルは通るものの、実行時にとんでもないメモリ破壊や計算誤差を引き起こします。
その中でも、実務の金融計算や数量管理で最も頻繁に使用されるのが、仮想小数点 `V` を含む固定小数点数(Fixed-Point Decimal)です。
—
2. 仮想小数点 ‘V’ の正体と、内部での保持形式
まず大前提として、PL/Iにおけるピクチャ数値(`PIC` 属性を持つデータ)は、内部的にはゾーン十進数(DISPLAY形式)またはパック十進数(COMP-3形式)としてメモリ上に保持されます。
ここで、以下の宣言を見てください。
1
DCL WK-KINGAKU PIC ‘99999V99’ DECIMAL;
この `V` は、物理的なバイトを消費しません。「ここが小数の境界線ですよ」というコンパイラへの申し送り事項(メタ情報)に過ぎません。
例えば、実際にメモリ上に `12345.67` という数値が格納される場合、`V` の文字自体はメモリ上に存在せず、単に `1234567` という7桁の数字(および符号)として連続した領域にパックされています。
COBOLの `V` との決定的な違い
COBOLの `PIC 9(5)V99` と概念は似ていますが、PL/Iの強みは「異なる小数点位置を持つ変数同士の演算であっても、コンパイラが自動的にスケーリング(位取り合わせ)を行ってくれる」点にあります。
—
3. 自動スケーリングの仕組みと、実務でハマるポイント
例えば、以下のような演算を行ったとします。
- 変数A:`PIC ‘999V9’(整数3桁、小数1桁)`
- 変数B:`PIC ’99V99’(整数2桁、小数2桁)`
- 結果 = 変数A + 変数B
通常のプログラミング言語やアセンブラであれば、小数点の位置を自前でシフト(10倍したり100倍したり)して位を合わせる必要がありますよね。しかし、PL/Iのコンパイラは、`V` の位置を認識し、演算の直前に自動的に内部的な位置合わせ(アライメント調整)を行います。
しかし、ここに「実務の罠」があります。
自動スケーリングは非常に便利ですが、受け側の変数(代入先)の定義を誤ると、意図しない切り捨て(Truncation)やオーバーフローが発生します。
特に、VSAMファイルへの書き込み(WRITE)や、外部ファイルへのレコード出力の際、レコードレイアウト(COPY句やINCLUDEメンバ)の定義と、プログラム内のワーキングストレージの定義で `V` の位置がズレていると、データが10倍または1/10の重みになってファイルに書き込まれてしまうという、大惨事(silent data corruption)を引き起こします。
—
4. 実践コード:VSAM入出力と自動スケーリングの制御
それでは、実際のPL/Iソースコードを通じて、この挙動を確認してみましょう。
以下のコードは、VSAM(KSDS)からレコードを読み込み、仮想小数点を持つ単価と数量を計算して、別のファイルに出力する典型的なバッチ処理の抜粋です。
1
—————————————————————
- プログラム名: CALC01PL
- 概要: 仮想小数点 ‘V’ を持つ固定小数点数の演算とVSAMアクセス
—————————————————————
CALC01PL: PROC OPTIONS(MAIN);
— 1. ファイル(VSAMおよび順編成)の定義 —
DCL IN-VSAM FILE RECORD INPUT ENVIRONMENT(VSAM);
DCL OUT-FILE FILE RECORD OUTPUT;
— 2. レコード領域の定義(入力) —
DCL 1 IN-REC,
3 IN-ID CHAR(5),
3 IN-TNK PIC ‘99999V99’ DECIMAL, 単価: 整数5桁, 小数2桁
3 IN-SURU PIC ‘9999V9’ DECIMAL; 数量: 整数4桁, 小数1桁
— 3. レコード領域の定義(出力) —
DCL 1 OUT-REC,
5 OUT-ID CHAR(5),
5 OUT-KINGAKU PIC ‘9999999V99’ DECIMAL; 金額: 整数7桁, 小数2桁
— 4. ワーク変数の定義とBUILTIN関数の活用 —
DCL W-KEISAN DEC FIXED(11,3); 演算用の内部固定小数点
DCL EOF-FLG BIT(1) INIT(‘0’B);
- — 終了条件(ENDOF)のONユニット制御 —
ON ENDFILE(IN-VSAM) EOF-FLG = ‘1’B;
— ファイルのオープン —
OPEN FILE(IN-VSAM) INPUT, FILE(OUT-FILE) OUTPUT;
— メイン処理ループ —
DO WHILE(^EOF-FLG);
READ FILE(IN-VSAM) INTO(IN-REC);
IF EOF-FLG THEN LEAVE;
——————————————————-
- [重要] 仮想小数点 ‘V’ を考慮した演算
- IN-TNK (小数2桁) と IN-SURU (小数1桁) の積は
- 自動的に小数3桁の精度を持つ結果として扱われます。
——————————————————-
W-KEISAN = IN-TNK IN-SURU;
— 組み込み関数(ROUND)を用いた四捨五入処理 —
- 小数第3位を四捨五入して、出力用の小数2桁に合わせる
OUT-KINGAKU = ROUND(W-KEISAN, 2);
OUT-ID = IN-ID;
— レコードの書き出し —
WRITE FILE(OUT-FILE) FROM(OUT-REC);
END;
— ファイルのクローズ —
CLOSE FILE(IN-VSAM), FILE(OUT-FILE);
RETURN;
END CALC01PL;
—
5. ベテランからのアドバイス:デバッグとコーディング標準の心得
上記のコードを見て、「おっ」と思った後輩エンジニアは素晴らしい勘をしています。実務でこの手のプログラムを保守する際、以下の3点だけは絶対に心に留めておいてください。
① `DEC FIXED` ワーク変数との明示的な併用
ピクチャ変数同士の四則演算を直接行うと、コンパイラが決定する中間結果の精度(プレシジョン)が意図したものから外れることがあります。複雑な計算を行う場合は、上記サンプルコードのように一度 `DEC FIXED(p, q)` 型のワーク変数に受けてから処理するのが、バグを防ぐための鉄則です。
② `ROUND` ビルトイン関数の忘れない活用
PL/Iでは、桁数の異なるピクチャ変数へ代入する際、デフォルトでは「切り捨て(Truncation)」が行われます。四捨五入が必要な場合は、面倒がらずに必ず `ROUND` ビルトイン関数を明示してください。「テスト時は合っていたのに、本番の丸め誤差で1円合わない!」という、経理監査で真っ青になるお馴染みのトラブルをこれで防げます。
③ コピペ時の `V` の位置ズレに要注意
マイグレーションやリバースエンジニアリングの際、COBOLのコピーブックをPL/Iの構造体に変換ツール等で自動変換することがありますが、この時に `PIC 9(5)V99` の `V` の解釈が漏れて、ただの文字型や整数型に化けてしまう事故が稀にあります。ソースレビューの際は、必ずピクチャ定義の `V` の位置が、前後のプログラムやJCL、定義書と完全一致しているかを指差し確認するようにしてください。
—
基幹システムの命は「正確性」と「安定性」です。
目に見えない小数点 `V` ですが、その挙動を完全に手の内に入れておけば、どんな大規模な改修が来ても怖くありません。
今日の解説が、皆さんの日々の開発・保守作業の一助となれば幸いです。それでは、次のバッチウィンドウまで、しっかりシステムを守り抜きましょう!
