【入門編】FIXED DECIMAL(PACKED-DECIMAL)の内部表現 – PL/Iの基本構文とデータ制御実践ガイド

こんにちは!IBMメインフレームの世界へようこそ。
JavaやCOBOLといったモダン、あるいは一般的なビジネス言語の経験がある方にとって、レガシーの代名詞とも言える「PL/I(ピーエルワン)」は、最初のうちは少し独特な暗号のように見えるかもしれません。「なんだか難解そうだな…」と身構えてしまうのも無理はありません。

でも、安心してくださいね。一つひとつの仕様を紐解いていけば、PL/Iがいかに合理的で、機械の動きに寄り添った美しい言語であるかが分かってきます。

今回は、基幹システムのデータ処理において主役級の存在である`FIXED DECIMAL`(パック10進数)の内部構造と、それが裏側でどのようにアセンブラ命令へ翻訳されているのかについて、優しく丁寧に紐解いていきたいと思います。

—

1. JavaやCOBOL出身者から見た「FIXED DECIMAL」の正体

Javaの `BigDecimal` や、COBOLの `PIC S9(9) COMP-3` を触ったことがある方なら、「あぁ、アレね」とピンと来るかもしれません。

PL/Iにおける `FIXED DECIMAL` は、まさにCOBOLの `COMP-3` と同じ「パック10進数(Packed Decimal)」をメモリ上に表現するためのデータ属性です。

たとえば、金額や数量といった「絶対に誤差が許されない数値」を扱うとき、コンピュータ内部の2進数(Binary)で計算すると、10進数の小数点を正確に扱えず、丸め誤差が発生することがありますよね。銀行の口座残高で1円のズレも許されない基幹システムにおいて、この「10進数そのままの正確さ」をハードウェアレベルで保証してくれるのが、このパック10進数なのです。

PL/Iでの書き方を見てみましょう

PL/Iでは、以下のように宣言します。

1
/ 7桁の整数、小数点以下2桁を持つパック10進数の宣言 /
DCL WK_SALARY FIXED DEC(7,2);

直感的ですよね。「合計7桁で、そのうち下2桁が小数だよ」と宣言しているだけです。では、このデータがIBMメインフレームのメモリ上で実際にどう並んでいるのか、覗いてみましょう。

—

2. メモリの覗き見:パック10進数の内部構造と「符号」の秘密

コンピュータのメモリは本来、1バイト(8ビット)の中に2進数の「0」と「1」を詰め込んでいます。しかし、パック10進数では、「1バイトの中に10進数の数字を2つ(高位ニブルと低位ニブル)詰め込む」という、ちょっとユニークなパッキングを行います。

そして、最後の1バイトの後半(右側の4ビット)には、その数値がプラスかマイナスかを表す「符号(Sign)」が陣取ります。

例:数値 `+1234.56` をメモリに格納した場合

`FIXED DEC(7,2)` の場合、全体で何バイト必要になるでしょうか?
計算式はこうです:`(桁数 + 2) / 2` (端数切捨て)
今回の場合は `(7 + 2) / 2 = 4.5` → 切り捨てて 4バイト の領域を消費します。

メモリ上(16進数)では、このように並びます。

[ 01 ] [ 23 ] [ 45 ] [ 6C ]
↑ ↑ ↑ ↑
0と1 2と3 4と5 6と [+符号]

一番最後のバイト `[ 6C ]` に注目してください。

  • `6` が数値の「6」
  • `C` が「プラス(Positive)」を意味するゾーン(IBM汎用機では一般的に `C` がプラス、`D` がマイナス、無符号の場合は `F` になります)

「最後の1桁の場所に文字の符号が同居している!」というのが、初めてメインフレームを触る人が驚くポイントですが、ハードウェア(CPU)はこの構造を前提に猛烈なスピードで計算できるように作られています。

—

3. 裏側で何が起きている? アセンブラ命令への展開プロセス

私たちがPL/Iで書いた算術演算(足し算や引き算など)は、コンパイルされるとIBMメインフレームの心臓部であるSystem/390やz/Architectureの十進演算命令(Decimal Instructions)へとダイレクトに翻訳されます。

これが、いわゆる AP、SP、MP、DP命令 です。

| 命令コード | 正式名称 | 何をするもの? |
| :— | :— | :— |
| AP | Add Decimal | パック10進数同士を加算する |
| SP | Subtract Decimal | パック10進数同士を減算する |
| MP | Multiply Decimal | パック10進数同士を乗算する |
| DP | Divide Decimal | パック10進数同士を除算する |

実務を想定したPL/Iコードとアセンブラの動き

例えば、以下のような簡単な加算処理のコードがあったとします。

1
—————————————————

  • 給与にボーナスを加算するパターンのサンプル

—————————————————
TEST_ADD: PROC OPTIONS(MAIN);

DCL BASE_PAY FIXED DEC(7,2) INIT(150000.00); / 基本給 /
DCL BONUS FIXED DEC(7,2) INIT( 50000.00); / ボーナス /
DCL TOTAL_PAY FIXED DEC(7,2); 合計給与 /

/ 基本給にボーナスを加算 /
TOTAL_PAY = BASE_PAY + BONUS;

PUT SKIP LIST (‘TOTAL:’, TOTAL_PAY);

END TEST_ADD;

この `TOTAL_PAY = BASE_PAY + BONUS;` というたった1行のPL/I文が、コンパイラによってどのように解釈されるか、頭の中でアセンブラの動きをトレースしてみましょう。

1. データのロード(あるいは直接参照):
メモリ上の `BASE_PAY` の領域を指し示し、そこに `BONUS` の領域を足し合わせる準備をします。
2. AP(Add Decimal)命令の実行:
ハードウェアレベルで `AP` 命令が発行され、メモリ上で直接、パック10進数の形式を保ったまま「15000000 + 5000000(小数点位置を無視した整数としての演算)」がミリ秒単位(あるいはそれ以上の子細なクロック)で実行されます。
3. 結果の格納:
演算結果が `TOTAL_PAY` のメモリ領域に、正しい符号(C)と共にパッキングされて書き戻されます。

Javaのように「いったんオブジェクトを生成して…」といったオーバーヘッドが一切なく、CPUの回路レベルで直接10進数をゴリゴリ計算できるため、大量のトランザクションを処理する金融や流通の基幹バッチにおいて、今なおPL/IやCOBOLのこの仕組みが現役で最強のパフォーマンスを発揮し続ける理由がここにあります。

—

4. まとめ:レガシーの仕様は「怖くない」

いかがでしたでしょうか?
`FIXED DECIMAL` とその内部表現、そしてアセンブラ命令(AP/SP等)への展開について、少しイメージが湧いてきたのではないでしょうか。

「メモリのバイトの中に数字が2つ詰め込まれていて、一番最後に符号がいる」――言葉だけ聞くとレガシー特融の呪文のようですが、データ構造とハードウェアの仕組みをセットで理解してしまえば、これほどシンプルで理にかなったデータ管理方法はありません。

バッチ改修や移行プロジェクトでこの手のデータ定義に出会っても、もう怖がる必要はありませんね。「ああ、あのパックされている奴ね、最後のバイトは符号だな」と、心の中でニヤリとしながらコードを読み解いていってください。

あなたのメインフレーム開発・移行の旅が、実り多いものになるよう応援しています!

タイトルとURLをコピーしました