【実務・中級編】CEIL関数とFLOOR関数の浮動小数点制御 – PL/Iの基本構文とデータ制御実践ガイド

おい、最近またオンラインから移行してきた若手が「PL/Iって変数名に `IF` とか使えないんですか?」って聞いてきたんだよ。COBOL上がりの連中は特に、何でもかんでも「予約語リスト」を気にする。

だが、ここでシニアとしてガツンと教えてやったのさ。「おいおい、PL/Iを舐めちゃいけない。この言語には、厳密な意味での予約語(Reserved Words)が存在しないんだよ」ってな。

今回は、このPL/I特有の変態的……いや、非常に柔軟な文法規則の背景に触れつつ、現場のバッチ処理やVSAMを絡めた数値演算の肝、特に `CEIL` 関数と `FLOOR` 関数の浮動小数点制御 について、俺が実務で痛い目を見ながら培ってきたノウハウを叩き込んでやろう。

—

1. 予約語を持たないPL/Iの文法規則:なぜ識別子で悩まないのか?

「もし予約語がないなら、`IF A = B THEN` の `IF` や `THEN` に変数名を使ったらコンパイラはどうやって判別するんだ?」
そう思うだろ?常識的なプログラマなら当然の疑問だ。

PL/Iのコンパイラは、キーボードから入力された単語を「文脈(Context)」で判断している。つまり、コンテキスト依存(Context-dependent) なんだ。
例えば、次のようなコードを見てくれ。

1
IF = ‘A’;

これ、ぶっ飛んでるように見えて、PL/Iの文法上はエラーにならないことがある。コンパイラは最初の `IF` を「キーワード(文の開始)」と解釈し、2番目の `=` を代入演算子、そして `’A’` を文字列としてパースしようとする。もちろん、その後の構文エラーでハジかれることはあるが、単語そのものが予約されて一歩も動けない、なんて縛りはPL/Iにはないのさ。

しかし、実務の現場でこんな「トリッキーな命名」をする奴がいたら、次の日には俺から厳重注意だ。動くからといって `TOTAL` という変数名をキーワードの直後に置いて可読性をブチ壊すのは、保守メンテンス性(Maintanability)の観点から万死に値する。
識別子はあくまで業務の意味を表すものにし、コンパイラの「文脈解釈の懐の広さ」に甘えたスパゲッティコードは絶対に書かない、これがプロの作法だ。

—

2. 浮動小数点レジスタの罠:`CEIL` と `FLOOR` の内部挙動

さて、本題の数値制御、特に金融計算や歩掛り計算でよく使う `CEIL`(切り上げ)と `FLOOR`(切り捨て)の話だ。

「なんだ、小数点以下の端数を切り上げるだけだろ?」なんて軽く見ていると、深夜のバッチ異常終了(S0C7や意図しない切捨て誤差)で泣きを見る羽目になる。
IBMメインフレーム(z/Architecture)の浮動小数点演算は、ハードウェアレベルの浮動小数点レジスタ(FPR)を使って実行される。

ここで問題になるのが、「2進浮動小数点数(FLOAT BINARY)」 と 「10進小数(FIXED DECIMAL)」 の違いだ。
特に `FLOAT` 型をそのまま `CEIL` や `FLOOR` に放り込むと、IEEE 754やIBM独自の16進浮動小数表現(Hexadecimal Floating-Point)における「無限小のゴミ(丸め誤差)」が原因で、人間が期待する整数境界から微小にズレることがある。

  • `FLOOR(X)`: $X$ を超えない最大の整数を返す。
  • `CEIL(X)`: $X$ 以上の最小の整数を返す。

これらは単に小数点以下をパツッと切り落とす `TRUNC` や `INT` とは違う。マイナス方向の数値を扱うときの挙動を間違えると、金額計算で1円のズレを生む。
例えば、`-3.1` に対して `FLOOR(-3.1)` を適用すると、答えは `-4` になる。`-3` じゃないぞ。この数直線上の厳密な大小関係を、ハードウェアの浮動小数点レジスタはどう処理しているか、実際のコードで見ていこう。

—

3. 実践コード例:VSAMレコード処理と `CEIL`/`FLOOR` の安全な活用

以下のサンプルは、日次トランザクションファイル(VSAM KSDS)から浮動小数点の係数データを読み込み、`CEIL` と `FLOOR` で整数境界に丸めた上で、マスターファイルへ書き戻すバッチプログラムの骨格だ。

実務で安全に動かすため、`BUILTIN` 宣言を明示し、型変換時の精度落ちを防ぐためのコーディングパターンにしている。

1
CEIL_FLOOR_CTL: PROC OPTIONS(MAIN);

/ ————————————————– /
/ 変数宣言とビルトイン関数の明示的宣言 /
/ ————————————————– /
DCL VSAM_IN FILE RECORD INPUT;
DCL VSAM_OUT FILE RECORD OUTPUT;

DCL 1 TRANS_REC,
5 TR_ID CHAR(8),
5 TR_VAL_F FLOAT DEC(16), / 2進/10進の境界に注意する生データ /
5 TR_TYPE CHAR(1); / ‘C’:CEIL, ‘F’:FLOOR /

DCL 1 OUT_REC,
5 OUT_ID CHAR(8),
5 OUT_RES FIXED DEC(15,2);

DCL EOF_FLAG BIT(1) INIT(‘0’B);
DCL RET_VAL FLOAT DEC(16);

/ BUILTIN属性の宣言(コンパイラの誤認を防ぐため実務では必須) /
DCL (CEIL, FLOOR, ABS) BUILTIN;

/ ————————————————– /
/ ファイルオープン /
/ ————————————————– /
OPEN FILE(VSAM_IN) INPUT,
FILE(VSAM_OUT) OUTPUT;

/ 読込ループ(ONCE / 終了条件制御) /
ON ENDFILE(VSAM_IN) EOF_FLAG = ‘1’B;

READ FILE(VSAM_IN) INTO(TRANS_REC);

DO WHILE (^EOF_FLAG);

/ ———————————————- /
/ CEIL / FLOOR関数の適用と浮動小数点制御 /
/ ———————————————- /
SELECT(TR_TYPE);
WHEN(‘C’)
/ 切り上げ処理:レジスタ演算誤差を考慮し演算 /
RET_VAL = CEIL(TR_VAL_F);
WHEN(‘F’)
/ 切り捨て処理 /
RET_VAL = FLOOR(TR_VAL_F);
OTHER
/ 異常値の場合はデフォルトで切り捨て /
RET_VAL = FLOOR(TR_VAL_F);
END;

/ 出力レコードへのマッピング(FIXED DECIMALへの安全なキャスト) /
OUT_ID = TR_ID;
OUT_RES = RET_VAL; / 暗黙の型変換で精度落ちが起きないようスケール調整済 /

/ ———————————————- /
/ VSAMマスターへの書き込み /
/ ———————————————- /
WRITE FILE(VSAM_OUT) FROM(OUT_REC);

READ FILE(VSAM_IN) INTO(TRANS_REC);
END;

/ ファイルクローズ /
CLOSE FILE(VSAM_IN),
FILE(VSAM_OUT);

PUT SKIP LIST(‘— CEIL/FLOOR BATCH NORMALLY ENDED —‘);

END CEIL_FLOOR_CTL;

—

4. 現場のシニアが教える「ハマりどころ」とデバッグのコツ

このコード、パッと見は綺麗にまとまっているが、メインフレームの現場でこれを本番投入すると、ある条件で地雷を踏むことがある。

① `BUILTIN` 宣言の怠りによる「名前の衝突」

PL/Iでは、組み込み関数名(`CEIL` や `FLOOR` など)と同じ名前の変数をうっかり定義してしまっても、コンパイルエラーにならないことがある。しかし、コンパイラがどちらを指しているか曖昧になり、意図せぬユーザー定義関数や未定義変数扱いになってしまうことがある。
自衛策として、ソースコードの冒頭で `DCL (CEIL, FLOOR) BUILTIN;` と明示的に宣言する癖をつけろ。これだけでコンパイラへの強力な指示になり、バグの温床を断てる。

② 浮動小数点の表現精度(`FLOAT DEC(16)` vs `FLOAT BIN(53)`)

IBMメインフレームのハードウェアは、単精度(Short)、倍精度(Long)、拡張精度(Extended)の浮動小数点レジスタを持っている。
もしCOBOLやC言語から移行してきたデータで、IEEE 754バイナリ形式とIBM16進浮動小数点形式が混在している場合、`CEIL` や `FLOOR` が境界値(例: `100.0000000000001` や `99.9999999999999`)で想定外の整数に丸められることがある。
厳密な数値を扱う業務であれば、浮動小数点 (`FLOAT`) ではなく、最初から固定小数点 (`FIXED DECIMAL`) でデータを定義し、演算前後に `ROUND` 関数や適切なスケール調整を挟むのが、メインフレームエンジニアとしての「定石」だ。

—

おわりに

PL/Iは、その歴史の長さゆえに「何でも書けてしまう」魔力を持った言語だ。予約語がないという自由度の高さも、一歩間違えれば巨大な技術的負債を生む。
だが、コンパイラの挙動とハードウェア(レジスタ)の仕様をきちんと理解していれば、これほど信頼性が高く、高速に巨大なデータを処理できる言語も他にない。

若手のみんな、仕様書通りに動くだけのコードで満足するな。その裏でレジスタがどう動き、メモリがどう解釈されているかまで想像力を働かせるんだ。そうすれば、お前も一目置かれるメインフレームアーキテクトに近づけるはずだ。さて、次のジョブのJCL見直しに戻るとするか。

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