こんにちは!メインフレームの世界へようこそ。
JavaやCOBOLといったモダン、あるいはビジネス標準の言語をバリバリ書いてきた方にとって、IBMメインフレームの「PL/I(ピーエルワン)」という言語は、最初にソースコードを見たとき、ちょっと独特な暗号のように感じられるかもしれませんよね。
「なんだこの見たこともない記述は…怖いな…」なんて思っていませんか?
大丈夫です。一歩ずつ、蓋を開けて中身を覗いていけば、PL/Iほど合理的に、そして当時のハードウェアの限界に寄り添って作られた言語はありません。
今回は、そんなPL/Iの数ある特徴の中でも、特に初学者が「えっ、嘘フリーズした!?」と冷や汗をかきやすいPICTURE ‘V’(仮想小数点)の算術演算について、じっくりと紐解いていきたいと思います。COBOLやJavaの小数点の扱いとは一味違う、メインフレームならではのドラマを一緒に見ていきましょう!
—
1. そもそも PL/I の「PICTURE ‘V’」ってなに?
Javaなら `double` や `BigDecimal`、COBOLなら `PIC 9(3)V99` などでお馴染みの「小数点」ですが、PL/Iの世界でも似たようなことができます。
例えば、銀行の金利計算や、小数点以下の細かいレートを扱うシステムを想像してみてください。メモリ(ストレージ)の容量や通信コストが今よりもはるかに高価だった時代、「ピリオド(.)そのものをメモリ上に保存するのはもったいない!」という切実な背景がありました。
そこで登場するのが、仮想小数を表す `V` です。
1
DCL RETAIL_RATE PIC ’99V99′; / 見た目は4桁の数字だが、真ん中に小数点があるものとみなす /
この `RETAIL_RATE` という変数、実際のメモリ上にはピリオドの領域は確保されません。ただの「`9999`」という4桁の数字(ゾーン10進数やパック10進数など)として詰め込まれています。しかし、コンパイラやプログラムの頭の中では、「あ、左から2桁目と3桁目の間に、見えない小数点があるんだな」と常に意識して処理されるのです。これが 仮想小数点(Virtual Decimal) です。
—
2. 仮想小数点位置が異なる変数同士の演算、何が起きる?
さて、ここからが本題です。
もし、この「小数点の位置が違う」変数同士を足したり引いたり、あるいは掛け合わせたりしたら、コンパイラは裏側でどんな魔法(あるいは荒業)を使っているのでしょうか?
Javaであれば、異なる型や小数点であってもコンパイラやVMが上手い具合に型昇格(プロモーション)してくれますよね。COBOLでも `COMPUTE` 文を使えば、コンパイラが勝手に桁合わせの補正コードを生成してくれます。
PL/Iも同様に、賢く桁合わせをしてくれるのですが……その「お節介とも言える自動補正」が原因で、予期せぬ「桁落ち(Truncation)」やオーバーフローを引き起こすことがあるのです。
実際のPL/Iコード例で見てみましょう
百聞は一見に如かず。以下のサンプルコードを見てください。実務のバッチプログラムでよくあるワンシーンを想定しています。
1
/ ========================================================== /
/ PICTURE ‘V’ の演算挙動と桁落ちを確認するサンプルプログラム /
/ ========================================================== /
CALC_TEST: PROC OPTIONS(MAIN);
/ 変数の宣言 /
DCL DATA_A PIC ’99V9′ VALUE ‘1234’; / 実質 123.4 (整数2桁、小数1桁) /
DCL DATA_B PIC ‘9V999’ VALUE ‘56789’;/ 実質 5.6789 (整数1桁、小数3桁) /
/ 結果を受け取る変数(十分な桁数を確保したつもり…?) /
DCL RESULT_1 PIC ‘999V999’;
/ — 演算の実行 — /
/ DATA_A (123.4) と DATA_B (5.6789) を足し算する /
RESULT_1 = DATA_A + DATA_B;
/ 結果の出力(実際にはPUT SKIPなどを使います) /
PUT SKIP LIST (‘演算結果は: ‘, RESULT_1);
END CALC_TEST;
さあ、このコード、一見すると何の問題もなさそうに見えますよね。「123.4 と 5.6789 を足すんだから、答えは 129.0789 だな」と頭では計算できます。
しかし、PL/Iのコンパイラはこの計算を裏側でどう処理するでしょうか?
—
3. コンパイラが裏で行う「位置合わせ(シフト命令)」の正体
PL/Iコンパイラは、算術演算を行う際、「両者の仮想小数点の位置(Vの位置)を強制的に一致させる」という鉄則を持っています。
今回のケースを見てみましょう。
- `DATA_A` の小数点位置:右から 1桁 の場所 (`99V9`)
- `DATA_B` の小数点位置:右から 3桁 の場所 (`9V999`)
そのままでは足し算ができません(位取りがズレてしまうため、小学校の筆算で小数点を揃えないのと同じ状態になります)。
そのため、コンパイラは演算の直前に、小数点位置を揃えるための「シフト演算(位置合わせ)」の機械語命令を裏側で自動的に挿入します。
今回は、小数点以下の桁数が多い `DATA_B`(小数3桁)側に合わせて、`DATA_A` の方をシフトさせようとします。
`DATA_A` は小数1桁 (`123.4`) ですから、小数3桁にするために、右側に `0` を補う(つまり10倍、あるいは100倍するようなシフト)調整が行われます。
ここまでは良いのです。問題は、「結果を受け取る側の入れ物(`RESULT_1`)」や、「演算途中のテンポラリ領域(ワークエリア)」の属性がどう定義されているか、です。
—
4. 恐怖の「桁落ち(Truncation)」とオーバーフローの罠
もし、受け皿である `RESULT_1` の定義や、コンパイラが中間変数に割り当てた属性の「整数部」や「小数部」のサイズが足りなかったり、あるいは計算の過程で思わぬ切り捨てが発生したらどうなるでしょうか?
PL/Iの恐ろしい(そして厳しい)ところは、暗黙の型変換や代入において、オーバーフローや上位桁の切り捨てが発生しても、デフォルトではプログラムが異常終了せず、サイレント(無言)でデータを削り取ってしまうことがある点です。
データの切り捨てメカニズム
例えば、次のようなケースを考えてみます。
1
DCL X PIC ‘V99′ VALUE ’99’; / 実質 0.99 /
DCL Y PIC ’99V’ VALUE ’99’; / 実質 99.0 /
DCL Z PIC ‘V99’; / 結果受取 /
Z = X + Y;
- `X` は `0.99`
- `Y` は `99.00` (小数点位置を合わせるため、`99` の整数部がシフトされ、結果として `99.99` になる)
- これを `Z`(`PIC ‘V99’` = 小数以下2桁、整数部なし)に代入しようとすると……。
整数部にあふれた `99` はどこに行ってしまうでしょうか?
そう、きれいさっぱり切り捨て(Truncation)され、`Z` には小数点以下の `.99` しか残らない、なんていう大事故が起きます。コンパイラはエラーを出してくれないことが多いので、テスト工程で気づかないまま本番稼働してしまい、金額が合わない!と夜中に運用担当者から叩き起こされる……なんていうレガシー特有の冷や汗モノのトラブルに直結します。
—
5. 事故を防ぐためのシニア・アーキテクトからのアドバイス
JavaやCOBOLから来たばかりの頃は、この「 PICTURE 句が持つ独特のサイズ感」と「コンパイラによる勝手なシフト・切り捨て」に振り回されがちです。
でも、怖がる必要はありません。以下の鉄則を守れば、PL/Iの算術演算は怖くありませんよ!
1. 計算の途中結果・受取変数は「余裕を持った桁数」で宣言する
小数点位置(`V`)が異なる変数同士を四則演算する場合、コンパイラが作る中間ワークエリアのサイズは、オペランドの宣言に依存します。必要以上にピッタリのサイズ(PIC ’99V99′ など)で受け取ろうとせず、あらかじめ上位の桁や小数点を長めに確保した変数で受けるようにしましょう。
2. できれば `FIXED DECIMAL` や `FLOAT` を活用する
もし厳密な計算や、桁あふれのコントロールをシビアに行いたい場合は、見た目を整える `PICTURE` 句を直接計算に使うのではなく、内部的には `DCL AMOUNT FIXED DEC(15, 4);` のような純粋な算術データ型(固定小数点数)で演算を行い、画面やファイルに出力する直前のフォーマット調整として `PICTURE` を使う、という設計手法が非常に安全です。
3. コンパイラのオプション(CHECKなど)を味方につける
本番稼働前のテスト環境では、データ異常やオーバーフローを検知するコンパイラオプションやランタイムオプションを有効にし、サイレントな切り捨てが起きないようにガードを固めておきましょう。
—
いかがでしたでしょうか?
今回は、PL/Iにおける `PICTURE ‘V’` の演算挙動と、知っておかないと痛い目を見る「シフト」と「桁落ち」のメカニズムを解説しました。
最初は呪文のように見えるPLのコードも、その裏で「当時のハードウェア資源を極限まで節約しながら、最大のパフォーマンスを出そうとした先人たちの知恵」の名残だと分かると、なんだか少し愛おしく……はならないかもしれませんが(笑)、怖さは薄れたのではないでしょうか。
レガシーシステムの改修や移行でPL/Iコードと格闘する際、この記事があなたの心強いお守りになれば幸いです。それでは、次回のメインフレーム・アーキテクチャ解説もお楽しみに!
