こんにちは!メインフレームの世界へようこそ。
JavaやCOBOLといったモダン、あるいは王道なビジネス言語の経験がおありでしたら、PL/I(Programming Language One)という名前を初めて聞いたとき、「なんだか古めかしい記号の塊だな…」と身構えてしまったかもしれませんよね。
でも、どうぞご安心ください。基本の考え方は他の言語と一緒です。ただ、PL/Iには「ちょっとユニークで、知れば知るほど奥が深い」独自のルールが存在します。
今回はその中から、バッチプログラムの改修やマイグレーション調査で思わすハマりがちな「算術演算における型プロモーション(データ型の自動昇格)」について、実務の現場の空気感を交えながら、優しく紐解いていきたいと思います。
—
1. JavaやCOBOLとはちょっと違う? PL/Iの「懐の深さ」
JavaやCOBOLでは、異なるデータ型同士で計算をさせようとすると、コンパイラから「型が違うよ!」と怒られたり、明示的なキャスト(変換)を書かされたりしますよね。
ところが、PL/Iは非常に「懐が深い(というか、お節介なほど親切な)」言語です。
「あ、君と君を足したいのね。じゃあ、こっちでいい感じに型を合わせて計算しておいてあげるよ!」と、コンパイラが裏で勝手にデータ型を変換してくれます。これが暗黙的データ変換(型プロモーション)です。
一見すると「なんて便利なんだ!」と思えますが、基幹システムの現場では、この“お節介”が原因で予期せぬパフォーマンス低下や、思わぬ精度落ちを引き起こすことがあるのです。
—
2. 予約語がない!? PL/Iの自由度とデータ宣言のお話
型プロモーションの話に入る前に、PL/Iのちょっと変わった特徴に触れておきましょう。
実は、PL/IにはJavaの `int` や `String` のような「固定の予約語」がほとんどありません。
例えば、変数名として `IF` や `THEN` さえ、文脈によってはプログラマが自由に使うことすらできてしまいます(※もちろん、混乱の元なのでわざわざそんな書き方はしませんが!)。
そのため、PL/Iのデータ宣言は、私たちが普段使う「属性」をペタペタと貼り付けていく感覚で行います。
例えば、こんな風に書きます。
1
DCL WK-TAX-RATE FIXED DEC(5,4) INIT(0.08); / 税率:小数部が4桁の固定小数点 /
DCL WK-TOTAL-AMT FIXED BIN(31) INIT(0); / 合計金額:32ビットの二進整数 /
この「小数点をきっちり管理する 10進FIXED(パック十進数など)」と、「マシン語で高速に処理される 2進FIXED(バイナリ)」が混ざり合ったとき、コンパイラの中で何が起きているのでしょうか?
—
3. コンパイラはどの型に合わせる? 型昇格の優先順位
異なる型同士(例えば、`FIXED DEC` と `FIXED BIN`、あるいは浮動小数点数 `FLOAT`)が1つの算術式に同居したとき、PL/Iコンパイラは一定のルール(優先順位)に従って、どちらか一方の型へもう一方を格上げ(プロモーション)します。
大まかな優先順位のイメージは以下の通りです。
1. FLOAT(浮動小数点数):一番えらい(表現力が最も広い)
2. FIXED DEC(固定小数点数・10進):ビジネス計算の主役
3. FIXED BIN(固定小数点数・2進):マシン語処理のスピードスター
もし「10進数の固定小数点」と「2進数の整数」で計算を行ったらどうなるでしょう?
コンパイラは、精度落ちを防ぐために、より表現力の高い方へ自動的に型を合わせようとします。これが、意図しないコストを生む原因になります。
—
4. 【実例】パフォーマンス低下を招く「暗黙的変換」の罠
百聞は一見に如かず。実際のPL/Iコードを見てみましょう。
以下の例は、メインフレームのバッチ処理でよく見かける、売上計算のワンシーンです。
1
/ —————————————————————- /
/ 算術演算における型プロモーションのサンプルプログラム /
/ —————————————————————- /
TEST-CALC: PROC OPTIONS(MAIN);
/ データ宣言 /
Dcl Wk-Price Fixed Dec(9,2) Init(1234.56); / 単価:パック十進数 /
Dcl Wk-Qty Fixed Bin(15) Init(100); / 数量:2進整数 /
Dcl Wk-Result Fixed Dec(11,2) Init(0); / 金額:結果格納用 /
/ — ここに注目! — /
/ Fixed Dec と Fixed Bin の混ぜるな危険な演算 /
Wk-Result = Wk-Price Wk-Qty;
Put Skip List (‘計算結果: ‘, Wk-Result);
END TEST-CALC;
上記のコードで、`Wk-Price`(Fixed Dec)と `Wk-Qty`(Fixed Bin)を掛け合わせています。
この瞬間、コンパイラは裏側で次のような処理を行っています。
1. 2進数である `Wk-Qty` を、一度 10進数の形式(Fixed Dec)に暗黙的に変換(キャスト)する。
2. その上で、両者パック十進数同士として掛け算を実行する。
「あれ、ちゃんと計算できるなら問題ないのでは?」と思いましたよね?
実は、この変換が毎秒何万件も回るような巨大なループ処理の中で発生すると、CPUに余計な負荷(データ形式の変換コスト)がかかり、バッチ処理全体のパフォーマンス低下(CPU時間の高騰)を招いてしまうのです。
—
5. 安心・安全にパフォーマンスを守るための回避策
では、こうした無駄な暗黙的変換を防ぎ、レガシーシステムでも軽快に動くコードを書くにはどうすればよいのでしょうか?
答えはとてもシンプルです。「演算に参加するデータ型をあらかじめ自分で揃えておくこと」です。
先ほどの例であれば、数量側も最初から `Fixed Dec` で宣言するか、どうしても `Fixed Bin` で扱いたい場合は、意図を明確にするためにビルトイン関数(組み込み関数)等で型を明示してあげるのがプロの技です。
1
/ 修正版:データ型を統一して無駄な変換を防ぐスマートな書き方 /
Dcl Wk-Price Fixed Dec(9,2) Init(1234.56);
Dcl Wk-Qty-Dec Fixed Dec(5,0) Init(100); / 数量もあらかじめDecで統一 /
Dcl Wk-Result Fixed Dec(11,2) Init(0);
/ 型が一致しているため、コンパイラの余計な暗黙的変換が発生しない! /
Wk-Result = Wk-Price Wk-Qty-Dec;
このように、データ型を綺麗に揃えてあげるだけで、コンパイラは余計な変換コードを生成する必要がなくなり、マシン語レベルでダイレクトに効率の良い演算を行ってくれるようになります。
—
おわりに
PL/Iの型プロモーション、いかがでしたでしょうか?
「コンパイラが勝手にやってくれるから楽ちん!」と油断していると、大規模なマイグレーションの際や、大量データを扱うバッチのチューニングで思わぬ足枷になってしまうことがあります。
でも、今日から「お、この変数は型が違うから裏で変換が走るな」「じゃあ、あらかじめ型を合わせておこう」という視点を持っていただければ、もう怖くありません。
一つずつ、その独特な仕様の背景にある優しさを紐解いていけば、PL/Iはとても頼もしい相棒になってくれますよ。
あなたのメインフレームライフが、少しでも快適なものになりますように!
