【入門編】ZERODIVIDE条件の発生メカニズム – PL/Iの基本構文とデータ制御実践ガイド

こんにちは!メインフレームの世界へようこそ。
JavaやCOBOLといったモダン、あるいはビジネスでよく使われる言語の経験がある方にとって、歴史あるPL/I(ピーエルアイ)のコードや仕様は、ちょっと独特で難解に見えるかもしれませんよね。

「なんだか記号だらけで怖そう……」
「データ型や例外の仕組みが、他の言語と違いすぎて不安……」

そんな風に思っていませんか?でも、大丈夫ですよ。一つひとつのルールを紐解いていけば、PL/Iほどロジカルで、プログラマの意図を忠実に汲み取ろうとする優しい言語はありません。今回は、そんなPL/Iの基本データ型の一つである「固定小数点数」と、現場で冷や汗をかくことの多い「ZERODIVIDE(ゼロ除算)条件の発生メカニズム」について、実務の現場の空気感を交えながら優しく解説していきますね。

—

1. そもそもPL/Iの「固定小数点数」ってなに?

Javaを使っている方なら `int` や `long`、COBOLなら `PIC S9(9) COMP` などでお馴染みの数値データですね。PL/Iの世界でも、数値を正確に(誤差なく)扱うために「固定小数点数」が用意されています。

宣言の仕方は大きく分けて2つあります。

  • FIXED BINARY(固定小数点二進数): コンピュータが一番計算しやすい「2進数」でデータを持ちます。
  • FIXED DECIMAL(固定小数点十進数): 私たちが普段使う「10進数」のまま(正確にはゾーン10進数やパック10進数として)メモリに保持します。お金の計算などで誤差を出したくない時に重宝します。

ここで初学者が一番戸惑うのが、あの独特なカッコの指定方法ですよね。

1
DCL WK-TOTAL FIXED BINARY(31) VALUE(0); / 31ビットの2進数カウンタ /
DCL WK-PRICE FIXED DECIMAL(9,2) VALUE(0); / 全体9桁、うち小数部2桁の10進数 /

`FIXED BINARY(31)` は「符号を含めて31桁分の2進数(だいたい10進数の9〜10桁相当)」という意味ですし、`FIXED DECIMAL(9,2)` は「全体で9桁入るうち、下2桁は小数点以下ですよ」という意味です。COBOLのピクチャー句に少し似ているので、COBOL経験者なら「あぁ、なるほど」とすんなり入れたのではないでしょうか。

—

2. 恐怖の瞬間?「ZERODIVIDE」条件とは

さて、本題です。プログラムを作っていて一番やってはいけない(そしてなぜかテスト終盤によく起きる)エラーの一つが、「ゼロによる割り算(Division by Zero)」ですよね。

Javaや多くの言語では、整数同士の割算でゼロ除算をすると、容赦なく `ArithmeticException` が飛んできたり、プログラムが強制終了したりします。
では、PL/Iではどうなるでしょうか?

PL/Iには「ON条件(ON-condition)」という、例外が発生したときの「受け皿」を自分で定義できる非常にユニークで強力な仕組みがあります。もし、割り算の「除数(割る数)」がうっかり `0` になってしまった場合、PL/Iのランタイム環境はパニックを起こす前に、まずこう叫びます。

「おい!今、ZERODIVIDE(ゼロ除算)が発生したぞ!!」

これが `ZERODIVIDE` 条件の発生メカニズムです。

なぜゼロ除算が起きてしまうのか?

実務の現場でよくある原因は、以下のようなケースです。

1. マスタデータの読み込みミス: 本来入っているはずの単価や数量が、初期化漏れやデータ破損で `0` になっていた。
2. 割る数の計算結果がたまたま `0` になった: 事前チェックを怠り、変数 `A / B` の `B` が意図せず `0` を指していた。
3. マイグレーション時の仕様差異: 他言語から移行した際、暗黙の型変換や丸め誤差のせいで「実質 0」になってしまった数値で割ってしまった。

—

3. 実践!PL/Iでのコード例と挙動のイメージ

百聞は一見に如かず。実際にゼロ除算が発生するコードと、それをどう捉えるべきかを見てみましょう。

1
/ ========================================================== /
/ ZERODIVIDE条件の発生デモプログラム /
/ ========================================================== /
ZERODIVIDE_SAMPLE: PROC OPTIONS(MAIN);

/ 変数の宣言 /
DCL WS-DIVIDend FIXED DECIMAL(9,2) INIT(100.00); / 割られる数 /
DCL WS-DIVISOR FIXED DECIMAL(9,2) INIT( 0.00); / 割る数(なんとゼロ!) /
DCL WS-RESULT FIXED DECIMAL(9,2); / 結果格納用 /

/ ZERODIVIDE(ゼロ除算)が発生したときのトラップ(ONユニット) /
ON ZERODIVIDE BEGIN;
PUT SKIP LIST(‘【警告】ゼロによる割り算を検知しました!処理を続行します。’);
/ ゼロ除算時の安全な代替値を結果に代入するなどのリカバリが可能 /
WS-RESULT = 0;
END;

PUT SKIP LIST(‘— 割り算処理を開始します —‘);

/ ここで除数が0のため、ハードウェアまたはランタイムが例外を検知し、上のONブロックへジャンプ /
WS-RESULT = WS-DIVIDend / WS-DIVISOR;

PUT SKIP EDIT (‘計算結果:’, WS-RESULT) (A, F(9,2));
PUT SKIP LIST(‘— 正常にプログラムが終了しました —‘);

END ZERODIVIDE_SAMPLE;

このコードを実行すると、`WS-DIVISOR` が `0.00` なので、割り算を実行した瞬間に `ZERODIVIDE` 条件がトリガーされます。
もし `ON ZERODIVIDE` のような捕捉(トラップ)を記述していなかった場合、PL/Iのデフォルト動作としてシステム異常終了(abend、一般的にメインフレームでは `SOC7` や `SM4` などのシステムコード、あるいは独自のU3000番台などのユーザーアベンド)を引き起こし、いわゆる「システムダンプ」を吐き出してプツンと途切れてしまいます。

—

4. システムダンプから原因を特定するアプローチ

もし、事前の備え(ONユニット)がなく、本番バッチが異常終了してしまったとき、私たちはメインフレームのシステムダンプ(SYSUDUMP / CEEDUMP)と向き合うことになります。

「ダンプなんて文字の羅列で頭がクラクラする……」と思うかもしれませんが、見るべきポイントは決まっています。

1. アベンドコードの確認

  • 言語ランタイム(Language Environment: LE)が検出し、ゼロ除算の場合は概ね `CEE3201S` などのメッセージや、PL/I固有のシグナルとして記録されます。

2. PSW(Program Status Word)やブレークポイントのアドレス

  • 「プログラムのどの一行(どの機械語命令)で割り算(D(S) や DR などの除算インストラクション)を実行したか」がプログラムのロードモジュールマップと突き合わせることで特定できます。

3. ワーキングストレージ(変数の領域)の目視

  • ダンプリスト上の該当変数の領域を16進数(スナップショット)で覗き、「あ、本当に `WS-DIVISOR` のエリアが `X’00000000’` になってる!」と確認できた瞬間は、名探偵が謎を解いたような爽快感があります(ちょっとマニアックですが、レガシーエンジニアの醍醐味です笑)。

—

おわりに

PL/Iの `FIXED` 型と `ZERODIVIDE` 条件について、少しイメージは湧きましたでしょうか?

古い言語だからといって身構える必要はありません。PL/Iは、エラーが起きたときにどう振る舞うべきかをプログラマがきめ細かくコントロールできる、非常に懐の深い言語です。

「割る数がゼロかもしれない」という不安がある場所では、あらかじめ `IF` 文でガードするか、`ON ZERODIVIDE` で優しく受け止めてあげる設計をしておけば、メインフレームは決してあなたを裏切りません。

レガシーシステムの海原へ漕ぎ出すあなたの航海が、安全で実り多いものになりますように。それでは、また次回の技術解説でお会いしましょう!

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