PL/Iの「FIXED DECIMAL」とJavaの「BigDecimal」:マイグレーションの落とし穴を越えるために
こんにちは!メインフレームの世界へようこそ。これまでJavaや他のモダンな言語でバリバリ開発してきた皆さんにとって、PL/Iのコードは一見すると「呪文」のように見えるかもしれませんね。
特に、基幹システムのデータ処理で必ずぶつかる壁が「数値データ」の扱いです。今回は、PL/Iの`FIXED DECIMAL`という、一見地味ですが最強のパワーを秘めたデータ型と、それをJavaへ移行する際の「数値のズレ(丸め誤差)」という難問について、現場の知見を交えて紐解いていきましょう。
—
1. PL/Iの基本構造:まずは深呼吸
まずはPL/Iの骨格を見てみましょう。Javaでいうところのクラスやメソッドに近い構成です。
/i
/ プログラムの入り口は必ずPROCEDURE OPTIONS(MAIN) /
SAMPLE_PROG: PROCEDURE OPTIONS(MAIN);
/ ここで変数を宣言します /
DCL AMOUNT FIXED DECIMAL(15, 2);
AMOUNT = 123.45;
PUT LIST(‘金額は:’, AMOUNT);
END SAMPLE_PROG;
`DCL`(DECLAREの略)で変数を宣言する、これがPL/Iの基本です。`FIXED DECIMAL(15, 2)`とは、「全体で15桁、そのうち小数点以下が2桁」という固定小数点数を意味します。
—
2. なぜ「FIXED DECIMAL」が特別なのか?
Java経験者の皆さんは、お金の計算をする時、`float`や`double`を使わず、`java.math.BigDecimal`を使いますよね。それは、2進浮動小数点数では「0.1」を正確に表現できず、計算が狂うからです。
PL/Iの`FIXED DECIMAL`は、最初から「10進数」として物理的にメモリに配置されます。これを「パック10進数」と呼びます。
- PL/Iの視点: 10進数で定義したものは、どこまで行っても10進数。計算結果が「1.00」であれば、それは完璧に「1.00」として扱われます。
- Javaの視点: `BigDecimal`は極めて優秀ですが、演算を行うたびに「丸めモード(RoundingMode)」を意識する必要があります。
この「厳密さの哲学」の違いが、マイグレーション時に「あれ?合計金額が1円合わないぞ?」という悲劇を招く原因になるんです。
—
3. マイグレーションで起きる「誤差」の正体
現場でよくある失敗談を一つ。
PL/Iで計算した結果をJava側で受け取る際、「計算の途中で端数をどう扱うか」のルール(丸め方)をすり合わせていないケースが非常に多いです。
PL/Iの暗黙的な挙動
PL/Iは、代入や演算時に、宣言された精度に合わせて自動的に「切り捨て(Truncation)」を行います。
/i
/ 3桁の変数を宣言 /
DCL SHORT_VAL FIXED DECIMAL(5, 0);
/ 123.99を代入すると、警告なしに123になる /
SHORT_VAL = 123.99;
一方、Javaの`BigDecimal`で同じことをすると、`RoundingMode`を指定しない限り例外(ArithmeticException)が発生するか、あるいは意図しない精度で計算が進んでしまいます。
対策:Java側に「PL/Iの作法」を持ち込む
移行する際は、以下のルールをJavaの共通クラスで徹底するのが鉄則です。
1. スケールの固定: 全ての金額計算で`setScale(2, RoundingMode.DOWN)`のように、PL/Iと同じ切り捨てルールを明示的に適用する。
2. 型変換の検証: PL/Iから受け取ったバイナリデータ(パック10進数)をJavaのBigDecimalに変換する際、自作の変換ユーティリティを通し、精度が欠落していないか必ずログを吐くこと。
—
4. 最後に:怖がる必要はありません
「レガシーシステムはブラックボックスだ」と言われることがありますが、PL/Iは非常に論理的な言語です。`FIXED DECIMAL`が何桁で、小数点以下が何桁なのかを丁寧に追いかけていけば、Javaのコードに書き換えることは決して難しくありません。
大切なのは、「計算のたびに丸めが発生していないか?」「PL/Iはここで切り捨てているが、Javaはどう計算しているか?」という、数値に対する「解像度」を上げることです。
もし現場で「計算が合わない!」と頭を抱えたら、まずはPL/Iの`DCL`定義をじっくり眺めてみてください。そこに、答えのすべてが隠されていますから。
これからも、この深いメインフレームの世界を一緒に楽しんでいきましょう!次回は、配列(ARRAY)と構造体(STRUCTURE)のメモリレイアウトについて深掘りする予定です。お楽しみに!
