【テクニカル・上級編】ON OVERFLOW条件と算術例外のハンドリング – PL/Iの基本構文とデータ制御実践ガイド

メインフレームの闇と光:PL/Iにおける `ON OVERFLOW` と算術例外ハンドリングの極意

こんにちは。長年、IBMメインフレームの基幹系システムと向き合い、数々のレガシーマイグレーションプロジェクトを泥臭く、かつ優雅に乗り越えてきたシステムアーキテクトだ。

現代のJavaやC#といったスマートな言語に慣れ親しんだ若いエンジニアからよくこんな質問を受ける。
「なぜ、あんな古臭いPL/IやCOBOLを使い続けるのですか?」と。

答えは単純だ。「金と命がかかった極限の信頼性」がそこにあるからだ。金融機関の勘定系や航空会社の座席予約システムにおいて、浮動小数点や固定小数点の桁あふれ(オーバーフロー)をどうハンドリングするかは、単なるバグの話ではなく、システム全体の崩壊を防ぐ防壁そのものである。

今回は、PL/Iの数ある強力な機能の中でも、特に玄人好みの「ON OVERFLOW条件と算術例外のハンドリング」について、コンパイラの挙動、ダンプ解析、そしてモダンな移行設計の視点から、徹底的に深掘りしていこう。

—

1. 予約語を持たないPL/Iの懐の深さと、その裏に潜む罠

まず大前提として、PL/Iという言語のユニークな仕様に触れておく必要がある。JavaやC#とは異なり、PL/Iには厳密な意味での「予約語(Reserved Words)」が存在しない。
`IF` や `GO TO`、さらには今回取り上げる `OVERFLOW` さえも、コンテキストによっては単なるプログラマ定義の変数名として使用できてしまう。

この設計思想は、言語としての柔軟性を極限まで高めた一方で、一歩間違えるとコンパイラを惑わせ、意図せぬ構文解釈を引き起こす諸刃の剣となる。
特に例外条件(Condition)を扱う `ON` ユニットの記述においては、この自由度の高さがそのままトラブルシューティングの難易度に直結する。

—

2. `ON OVERFLOW` のメカニズムと算術例外のリアル

演算結果が変数の定義された精度(ピクチャ句や桁数)を超えたとき、PL/Iランタイム環境は `OVERFLOW` 条件を発生させる。ここで重要なのは、「何がオーバーフローを引き起こしたか」の正確な切り分けだ。

PL/Iにおける算術例外には、以下のようなものが存在する。

  • OVERFLOW (ZIV): 固定小数点または浮動小数点の値が表現可能な上限を超えた場合。
  • FIXEDOVERFLOW (FOV): 主に固定小数点演算において桁あふれが発生した場合(デフォルトで有効)。
  • ZERODIVIDE: ゼロによる割り算。
  • SIZE: 代入時にターゲット変数の精度を超えることで有効桁数が失われる場合(厳密な切り捨てエラー)。

多くの現場で混同されがちなのが `OVERFLOW` と `FIXEDOVERFLOW` だ。
商用バッチなどでパックデシマル(`COMP-3` 相当、PL/Iでは `DECIMAL FIXED`)を多用する基幹系では、大半が `FIXEDOVERFLOW` の領域に踏み込むことになる。しかし、浮動小数点演算や高精度な科学技術計算、あるいは複雑な金融工学モデルを組み込んだバッチでは、真の `OVERFLOW` が牙をむく。

実践的な `ON` ユニットの実装例

以下に、実務のバッチ処理で組み込むべき、堅牢な例外ハンドリングのコードパターンを示す。

1
/ ———————————————— /
/ 算術例外(オーバーフロー)を監視・制御するバッチサンプル /
/ ———————————————— /
CALC_MODULE: PROC OPTIONS(MAIN);

DCL WS_BASE_AMT DECIMAL FIXED(11,2) INIT(0);
DCL WS_RATE DECIMAL FIXED(5,4) INIT(1.0000);
DCL WS_RESULT DECIMAL FIXED(10,2) INIT(0);
DCL WS_ERR_FLG CHAR(1) INIT(‘0’);

/ OVERFLOW条件発生時の割り込み処理を定義 /
ON OVERFLOW BEGIN;
DISPLAY(‘ ERROR: 算術オーバーフローを検知しました ‘);
WS_ERR_FLG = ‘1’;
/ 必要に応じてログテーブルへの書き込みやリトライ処理を記述 /
GOTO EXCEPTION_ROUTINE;
END;

/ FIXEDOVERFLOWの個別トラップ(固定小数点の桁あふれ) /
ON FIXEDOVERFLOW BEGIN;
DISPLAY(‘ WARNING: 固定小数点桁あふれが発生しました。補正を実行します ‘);
/ 最大値へ丸めるなどのフォールバック処理 /
WS_RESULT = 99999999.99;
GOTO NORMAL_EXIT;
END;

/ メインの演算処理ループ /
DO WHILE (WS_ERR_FLG = ‘0’);
/ わざと桁あふれを起こすような巨大な数値をシミュレート /
WS_BASE_AMT = 999999999.99;
WS_RATE = 9.9999;

/ ここでFIXEDOVERFLOWまたはOVERFLOWが発生する可能性 /
WS_RESULT = WS_BASE_AMT WS_RATE;

LEAVE; / ループ脱出 /
END;

NORMAL_EXIT:
DISPLAY(‘処理は正常に(または補正されて)完了しました。結果: ‘ || WS_RESULT);
RETURN;

EXCEPTION_ROUTINE:
DISPLAY(‘異常終了シーケンスへ移行します。ロールバックを実行。’);
/ 埋め込みSQLによるロールバック処理等 /
EXEC SQL ROLLBACK;
SIGNAL ERROR; / 強制的にアベンドを発生させ、SYSUDUMPを取得 /

END CALC_MODULE;

このコードのポイントは、単にエラーを検知して止めるだけでなく、`SIGNAL ERROR` や `GOTO` を用いて制御フローを安全なディザスタリカバリールーチンへと導いている点だ。

—

3. 動的メモリ(ポインタ)操作とポインタアライメントの罠

基幹系PL/Iプログラムでは、大量のレコードを効率よく処理するために、`ALLOCATE` 文とポインタ(`POINTER`)を用いた動的ストレージ管理が頻繁に行われる。

ここで `OVERFLOW` などの算術例外がポインタ演算やベース変数のオフセット計算(例: `BASED` 構造体の参照)に絡むと、一気にデバッグの難易度が跳ね上がる。
特に、CICSオンラインやDB2のホスト変数との間でやり取りされるポインタが不正なアドレスを指した状態で算術演算を行うと、S0C4(Protection Exception) や S0C7(Data Exception) といった致命的なメインフレーム特有のアベンドを引き起こす。

  • パックデシマルの内部符号反転バグ:

外部ファイルやDB2から取得したデータが、正しくゾーン/パック形式を保っていない状態で算術演算の左辺・右辺に入ると、ハードウェアレベルの十進数演算命令(ZAPやAPなど)が例外をスローする。PL/I側で `ON CONVERSION` や `ON ERROR` を張っていない場合、即座にジョブは異常終了し、SYSUDUMPの海に放り出されることになる。

—

4. コンパイラオプションによる最適化とダンプ解析の急所

IBM Enterprise PL/I コンパイラを使用する際、パフォーマンスを極限まで引き出すために `OPTIMIZE(2)` などの最適化オプションを指定することが多い。
しかし、ここにアーキテクト泣かせの罠がある。

高度な最適化が有効な状態では、コンパイラは「発生しないはずのオーバーフロー」を前提としてコードを再配置・インライン展開する。 その結果、ダンプ解析時にレジスタの値とソースコードの行番号が一致しなくなり、「どの変数のどの計算で `OVERFLOW` が起きたのか」を特定するのに膨大な時間がかかることになる。

ダンプ解析時の鉄則

1. CEE3DMP(Language Environment Dump)の活用:
SYSUDUMPの生データからレジスタを追うのは時間の無駄だ。LE(Language Environment)のサービスを活用し、例外発生時のスタックトレースとアクティブな変数の値を確実に抽出する。
2. OFFSET オプションの確認:
コンパイル時に `OFFSET` や `LIST` を出力させ、最適化によってどの機械語命令(Machine Instruction)のどのオフセットで割り込みが発生したかを、コンパイラリストと突き合わせる。

—

5. マイグレーション(Java / C#化)における設計の勘所

もし、あなたがこのレガシーなPL/IシステムをJavaやC#へリライト、あるいはマイグレーションするプロジェクトのテックリードであるならば、以下の違いに絶対に留意しなければならない。

  • Javaの `BigDecimal` との挙動の差:

PL/Iの `DECIMAL FIXED` は、デフォルトでは桁あふれ時にハードウェア割り込み(またはランタイム割り込み)が発生するが、Javaの `BigDecimal` は明示的に `MathContext` やオーバーフローチェックを行わない限り、そのまま精度落ちやメモリ溢れ、あるいは予期せぬ丸め誤差を引き起こす。

  • 例外処理の非同期性:

PL/Iの `ON` ユニットは一種のシグナルハンドラであり、宣言型に近いスコープ制御を持つ。これをJavaの `try-catch` に単純置換しようとすると、スパゲッティコードになるか、あるいはクリティカルな例外を取りこぼす原因になる。

レガシー移行において「動いていたコードの意味」を理解せずに機械的(ツール任せ)に変換すると、本番稼働後に「目に見えない計算誤差の蓄積」という最も厄介な爆弾を抱えることになる。

—

結びにかえて

PL/Iの `ON OVERFLOW` に代表される例外ハンドリング機構は、単なる古臭い構文ではない。それは、ハードウェアの特性を熟知し、予期せぬデータ異常からシステムを守り抜くために先人たちが築き上げた「防壁の哲学」そのものだ。

メインフレームのコードを触る機会が減ってきた現代だからこそ、こうした言語仕様の奥底にある設計思想を理解しているエンジニアの価値は計り知れない。
バッチの夜間バグに悩むアーキテクトや、移行先の設計図を引くエンジニアの羅針盤となれば幸いだ。

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