メインフレームからの脱却:PL/Iの `FIXED DECIMAL` がJavaの海で溺れるとき
長年、IBMメインフレームの心臓部で鼓動を刻んできたPL/Iプログラムたち。彼らは極めて厳格で、同時に極めて寛容です。特に `FIXED DECIMAL`(パック10進数)の扱いは、汎用機アーキテクチャの真骨頂と言えるでしょう。しかし、この「10進演算の神聖さ」をJavaの `BigDecimal` へ安易に写像しようとすると、システムは静かに崩壊へのカウントダウンを始めます。
今日は、マイグレーションという名の「外科手術」に挑むアーキテクトのために、現場で血を流さないための知見を共有します。
—
1. FIXED DECIMALの呪縛:ハードウェア演算とソフトウェア演算の乖離
PL/Iの `DCL VAR FIXED DECIMAL(15, 2)` は、単なる数値型ではありません。これはIBM z/Architectureのパック10進数命令(AP, SP, MP, DPなど)に直結したメモリレイアウトを意味します。
罠:スケールの切り捨てと丸めモード
Javaの `BigDecimal` はデフォルトでは `HALF_UP` ですが、PL/Iの演算はコンパイルオプション(`FIXEDOVERFLOW` や `TRUNC`)や、そもそもハードウェアの内部演算の切り捨てルールに依存します。
/i
/ PL/I側での演算例 /
DCL A FIXED DEC(5, 2) INIT(10.55);
DCL B FIXED DEC(5, 2) INIT(2.00);
DCL C FIXED DEC(5, 2);
/ ここでの除算結果の精度は、コンパイラが自動的に判断する /
C = A / B;
これをJavaの `BigDecimal.divide()` にそのまま置き換えてはいけません。PL/Iの演算結果の精度(Intermediate Result)は、オペランドの精度によって動的に決定されます。マイグレーション時には、「PL/Iのコンパイラが生成する演算中間結果の精度決定ルール」をJava側で忠実に再現するラッパー関数群を定義する必要があります。さもなくば、端数処理のわずかな誤差が、数十年分のDBデータとの不整合を招きます。
—
2. 内部符号(Sign)の反転バグという悪夢
メインフレームのパック10進数は、最後のニブル(4ビット)が符号を表します。`X’C’`が正、`X’D’`が負、`X’F’`は符号なしとして扱われます。
CICS上のオンライン処理で、外部システムや古いファイルから読み込んだデータに「符号なしの `X’F’`」が混入していた場合、PL/Iは寛容にもこれを正数として処理しますが、Javaで `BigDecimal` に変換する際に適切にハンドリングしなければ、データ破損(あるいは `NumberFormatException`)の引き金となります。
実践的対策:構造体のバイナリダンプ解析
アベンド(S0C7等)が発生した際、ダンプリストからその変数がどのようなバイナリ値を持っているか確認してください。
/i
/ ダンプ解析時、符号の整合性チェックに使える定石 /
DCL WRK_VAL FIXED DEC(5, 0);
/ もしこの変数にX’123F’(符号なし)が入っていたら、
PL/Iは動くが、他システム連携時に化ける /
移行設計では、データロードのパイプラインで「PL/Iの符号ビット」を解析し、Java側で `BigDecimal` の符号を明示的に正規化するフィルターを挟むのが定石です。
—
3. ポインタ操作と動的メモリという「聖域」
PL/Iの強みであり、同時に移行の悪夢となるのが `BASED` 変数とポインタを用いた動的メモリ操作です。
/i
DCL PTR POINTER;
DCL BASED_REC CHAR(100) BASED(PTR);
/ メモリ上のオフセットを直接計算してアクセス /
PTR = ADDR(BUFFER) + OFFSET;
/ このようなコードがCICSのCOMMAREA処理に散りばめられている場合、
単純なJavaクラスへの変換は不可能です /
これをJavaへ移行する際、最も信頼性が高いのは「バイト配列を模したメモリ空間をJavaの `ByteBuffer` で再現し、オフセット計算を完全にシミュレートする」手法です。クラスのメンバ変数に変換しようとしてはいけません。それはメインフレームの設計思想に対する冒涜であり、バグの温床になります。
—
4. 移行アーキテクトへの提言
マイグレーションは「言語の書き換え」ではなく「計算モデルの移植」です。以下の三点を肝に銘じてください。
1. コンパイラ・リストの精読: PL/Iのコンパイラリスト(`LIST`オプション)を出力し、中間演算の精度がどうなっているか、機械語レベルで理解しているか確認してください。
2. ユニットテストの限界: 単純な単体テストではなく、本番のEBCDICダンプデータを用いた「リプレイ・テスト」を導入してください。Javaでの出力結果と、現行メインフレームの出力結果がビット単位で一致するまで妥協してはいけません。
3. エッジケースのABEND解析: S0C7(データ例外)は、単なるバグではなく「現行システムの制約」のシグナルです。その例外を握りつぶさず、Java側で例外クラスとして定義し、メインフレーム側のロジックを忠実にトレースさせるべきです。
メインフレームのコードは、先人たちが何十年ものトラブルを乗り越えてきた「生存の記録」です。それをJavaへ書き換えるということは、その歴史的背景を再構築する作業に他なりません。
コードの海を泳ぐ際、PL/Iの厳格さはときに窮屈に感じるかもしれません。しかし、その厳格さこそが、基幹システムの信頼性を担保してきた唯一無二の防御壁なのです。その壁を壊すなら、Javaという新しい環境の中に、同等以上の強固な防御壁を築き上げてください。それが、我々レガシー移行アーキテクトの矜持です。
