みなさん、こんにちは!基幹システムの現場でメインフレームやPL/I(ピーエルアイ)と日々格闘しているシステムアーキテクトです。
JavaやCOBOLといったモダン、あるいは従来型のビジネス言語の経験がある方にとって、突然目の前に現れるPL/Iのコード、特に「なんだこの見慣れない数字や型は…?」というデータ宣言は、最初の大きな壁ですよね。
「メインフレームのデータ型って、なんだか怖そう…」
「COBOLのコンピュート項目とも、Javaの`float`や`double`とも違うみたいだけど大丈夫?」
そんな不安を抱えていらっしゃる方もご安心ください。蓋を開けてみれば、PL/Iの浮動小数点数も、私たちが普段使っているコンピュータの基礎(IEEE 754規格)に基づいている可愛い相棒に過ぎません。今日は、その中でも特に頭を悩ませがちな `FLOAT BINARY(21)` と `FLOAT BINARY(53)` について、内部の動きから丸め誤差のカラクリまで、そっと優しく紐解いていきましょう!
—
1. そもそもPL/Iの「FLOAT BINARY」って何?
JavaやC言語なら `float` や `double`、COBOLなら `COMPUTATIONAL-1` や `COMPUTATIONAL-2` と書くところを、PL/Iでは次のように宣言します。
1
DCL X FLOAT BINARY(21); / 単精度(32ビット相当) /
DCL Y FLOAT BINARY(53); / 倍精度(64ビット相当) /
ここで「あれ?」と思いますよね。なぜわざわざ `BINARY(二進数)` なのか、そしてなぜ `(21)` や `(53)` なんて中途半端な数字がついているのでしょうか?
実はこれ、「有効桁数をビット単位で指定している」んです。
- `FLOAT BINARY(21)`: 二進数で21桁分の精度を保証してくださいね、という意味です。これは、コンピュータの世界で一般的な単精度浮動小数点数(32ビット:IEEE 754 Single Precision)にぴったり合致します。
- `FLOAT BINARY(53)`: 二進数で53桁分の精度です。これは倍精度浮動小数点数(64ビット:IEEE 754 Double Precision)の領域です。
COBOLしか触ったことがない方だと「10進数で何桁か」で考えがちですが、メインフレームの浮動小数点演算はハードウェア(IBM Zプロセッサ)のCPU回路が直接処理するため、この「2進数ベース(Binary)」の指定が非常に効率的なんです。
—
2. なぜ「21」と「53」なのか?(IEEE 754の裏側)
ここで、ちょっと意地悪な疑問が湧きませんか?「なんで21と53なの? きりのいい20とか50じゃダメなの?」って思いますよね。
これには、IEEE 754という世界共通の浮動小数点規格における「仮数部(小数点の細かい部分を保持する箱の大きさ)」のビット数が深く関わっています。
コンピュータの中では、数字を 「符号(プラスかマイナスか)」、「指数(小数点の位置)」、そして 「仮数(数字の本体)」 の3つに分けて保持しています。
1. 単精度 (`FLOAT BINARY(21)`) の場合
- 全体の箱:32ビット
- そのうち仮数部:23ビット(ただしIEEE 754の正規化表現により、先頭の「1」を省略する暗黙のビット技術があるため、実質的な精度は 24ビット相当 になります。PL/Iの言語仕様上、21〜24ビットの指定は単精度として扱われます)
2. 倍精度 (`FLOAT BINARY(53)`) の場合
- 全体の箱:64ビット
- そのうち仮数部:52ビット(隠しビットを含めて 53ビット相当 の精度)
つまり、「ハードウェアが持っているレジスタの箱の大きさに、ぴったり合わせた数字が21と53なのだ」とイメージしてください。先人たちの緻密な最適化の歴史を感じられて、ちょっとロマンチックではありませんか?
—
3. 「丸め誤差」はなぜ起きる?(身近な例え話)
さて、ここからが実務で一番ハマるポイント、「丸め誤差(Precision Loss)」のお話です。
「プログラムで計算したはずなのに、最終的な残高が 0.01 円合わない!」
レガシーシステムの移行現場で、こういうバグ調査ほど冷や汗をかくものはありませんよね。
これはPL/Iがバグっているわけでも、メインフレームがボケているわけでもありません。「人間が使う10進数」と「コンピュータが使う2進数」の言葉の壁が原因です。
💡 ちょっとした例え話
私たちが普段使っている10進数では、「1 ÷ 3」 は `0.333333…` と無限に続きますよね。これを途中でパツッと切り捨てると、3倍しても「0.999999…」になってしまい、元の「1」に戻りません。
これと同じ現象が、コンピュータの中の2進数でも起きています。
例えば、私たちが画面に入力する 「0.1」という10進数の小数。これを2進数に直すと、なんと 無限小数(循環小数) になってしまうのです!
- 10進数の `0.1` = 2進数の `0.00011001100110011…`(延々と続く)
有限の箱(FLOAT BINARY(21) や (53))にこの数字を詰め込もうとすると、どこかの桁で強制的に「スパッ」と切り落とす(あるいは丸める)しかありません。これが丸め誤差の正体です。
—
4. 実務で役立つ!PL/Iコード例と注意点
では、実際にPL/Iでどのように変数を宣言し、計算時の注意点をどう防ぐのか、実際のバッチプログラムを想定したコードで見てみましょう。
1
/ ————————————————– /
/ 浮動小数点数(FLOAT BINARY)の宣言と演算サンプル /
/ ————————————————– /
TEST_PROG: PROC OPTIONS(MAIN);
/ 変数の宣言 /
DCL WK_SINGLE FLOAT BINARY(21); / 単精度変数(32ビット) /
DCL WK_DOUBLE FLOAT BINARY(53); / 倍精度変数(64ビット) /
DCL WK_RESULT FLOAT BINARY(53); / 計算結果保持用 /
/ 値の代入 /
WK_SINGLE = 1.23456789; / 単精度への代入(精度に注意!) /
WK_DOUBLE = 1.2345678901234567; / 倍精度への代入 /
/ 演算処理 /
/ ※注意: 単精度同士の計算結果をさらに単精度に入れると、
誤差が雪だるま式に膨らむ(累積誤差)ことがあります。 /
WK_RESULT = WK_DOUBLE 2.0D0; / 倍精度リテラル ‘D0’ を使用 /
/ 画面やログへの出力(PUT LIST) /
PUT SKIP EDIT (‘SINGLE VALUE = ‘, WK_SINGLE) (A, E(15,7));
PUT SKIP EDIT (‘DOUBLE VALUE = ‘, WK_DOUBLE) (A, E(23,15));
END TEST_PROG;
🛠️ 実務家からのワンポイントアドバイス
1. 「とりあえずFLOAT BINARY(53)にしておけ」は処世術?
現代のメインフレーム(IBM z14以降など)では、ハードウェアの浮動小数点演算は単精度でも倍精度でも、内部的には高速に処理されます。もし金融計算などで高い精度が求められる、あるいは誤差を最小限に抑えたい場合は、迷わず `FLOAT BINARY(53)`(倍精度) を選択してください。(※本当にお金の計算をするなら、浮動小数点ではなく `FIXED DECIMAL` を使うべきですが、科学技術計算や統計、比率計算などの文脈ではFLOATが使われます)
2. リテラルの書き方に気をつける
PL/Iでは、定数(リテラル)を書くときに指数部を表現できます。
- `1.23E0` または `1.23E+0` = 単精度 (FLOAT BINARY(21) 相当)
- `1.23D0` または `1.23D+0` = 倍精度 (FLOAT BINARY(53) 相当)
倍精度の変数に単精度リテラルを代入すると、一度精度が落ちてから代入されることがあるため、リテラルのサフィックス(`D`を使うなど)にも気を配ると完璧です。
—
おわりに
いかがでしたでしょうか?
`FLOAT BINARY(21)` と `FLOAT BINARY(53)`。一見すると呪文のようなレガシーな記述に見えますが、その中身は現代のパソコンやスマホでも使われている「IEEE 754国際標準」とまったく同じ魂を持っています。
「ビット数で精度を精密にコントロールできる、実はすごく合理的な仕組みなんだな」と分かれば、もう怖くありませんよね。
マイグレーションやソースコード解析の現場でこの記述に出会ったら、「おっ、ここはハードウェアの仕様にぴったり合わせたシビアな計算をしているんだな」と、心の中でニヤリとしながら読み解いていってください。あなたのレガシーシステムの旅が、実り多きものになるよう応援しています!
