【COBOL 読み2-9-2】小数点での比較の危険性〜神経質な細やかさ

🧩 今日の学び
・COBOLの普通の小数(PIC …V…COMP-3)は 固定小数点。0.10.1 + 0.2 も正確で、ズレない。ここはCOBOLの強み。
・ズレるのは COMP-1 / COMP-2(2進浮動小数点=float)を使ったとき。2進では 0.1 を正確に表せず、中身が「だいたい0.1」になる。
・浮動小数点を扱うなら「見た目」でなく「中身(型)」を疑う。整数スケールで回し、比較は = ではなく >= で逃げ道を作るのが安全。

COBOLの普通の小数はズレない

なるお)係長ー、昨日ちょっと調べてたら「小数ループは使い方に注意」 「精度誤差が発生するからきちんと考えておくのが大事」みたいなのが出てきたんですけど、わかります?

係長)お前でも調べることがあるんだな。どら、どんなサイトみてんだ?

な)いやー、なんか「0.1ずつ増やすとズレる」とかなってて、ズレる系男子とかありますの?黒縁メガネにぴっちり七三分けが作ったんだから、ビシッとしててほしいですよねー。

係)黒縁七三分け関係あるのかそれ… まあ、その心配は半分ハズレだ。

COBOLの普通の小数は、むしろビシッと正確なんだよ。お前の言う黒縁七三分けのイメージ、そっちが正解だ。

な)え、ズレるんじゃないんすか?

係)逆だ。これを実行してみろ。

* X は普通の小数(固定小数点)
01 X PIC 9V9.

PERFORM VARYING X FROM 0 BY 0.1 UNTIL X > 1
    DISPLAY "X = " X
END-PERFORM
* 出力結果

X = 0.0
X = 0.1
X = 0.2
X = 0.3
X = 0.4
X = 0.5
X = 0.6
X = 0.7
X = 0.8
X = 0.9
X = 1.0

係)どうだ。0.1ずつ、ピッタリ1.0まで、1ミリもズレてないだろ。

0.1 + 0.2 もやってみるか。

01 A PIC 9V99 VALUE 0.1.
01 B PIC 9V99 VALUE 0.2.
01 C PIC 9V99.

COMPUTE C = A + B.
DISPLAY "0.1 + 0.2 = " C.
*実行結果
0.1 + 0.2 = 0.30

係)きっちり0.30 誤差ゼロだ。

COBOLの普通の小数(PIC …V…)は「固定小数点」っつって、10進をそのまま正確に持つ。

だから、銀行や金融はCOBOLを使い続けてる。1円どころか1銭の誤差も許されん世界だからな。

な)ほー、ずれない系男子なんすねー。割り勘の端数までもずらさない。割り切れない場合は、次回の飲み会まで持ち越されるレベルっすね。嫌な世の中ですね⋯

係)ただな、COBOLにはもう一つ、浮動小数点(float) っていう裏の顔があって、そっちを使うとズレるぞ。

な)なにその2面性!表の顔と裏の顔が違うやつ!?そもそも二重人格!?昨日はYESで今日はNO!?YES/NO枕!?

係)うるさいな…の話だよ!

ズレるのは“float”の世界:0.1は2進で割り切れない

な)でも、なんで山のフドウだとズレるんすか?

係)浮動小数点だよ!

コンピュータは「2進数」で計算してる。人間が使う10進数だと0.1 = 1/10できっちり表せる。

でも、2進数だと0.1(10進)= 0.00011001100110011…(2進)と無限に続いて、割り切れん。

な)え、つまり「2進で0.1を正確に表せない」ってこと?

係)その通り。

だから浮動小数点(float)は、途中で打ち切って「だいたい0.1っぽい数」で代用する。

な)わお、“だいたい”とか言ってる時点で信用できないやつじゃないすか。

「ちょっ、あと5000円入れたら確変入るから金貸してくれ!すぐ返すから!ダイジョーブだって!」って言って8万溶かすやつのセリフっすよ、それ。

係)お前どんなやつと遊びに行ってんだ…

だがな、ここが肝心だ。

固定小数点(PIC V)はその“だいたい”をやらない。 10進のまま正確に持つ。

だから1で見せたとおりズレない。

ズレるのは、わざわざfloat(COMP-1/COMP-2)を持ち出したときだけだ。ここを混同するな。

な)つか、さっきから言ってるCOMP-2とかってのは何なんす?主役がいまいち不評だから、テコ入れで新キャラ登場すか?

係)floatってのは、USAGE COMP-1COMP-2 で指定する型だ。

数値を「2進の浮動小数点」で持つ。

COMP-1 が単精度、COMP-2 が倍精度——ざっくり、桁の余裕が違うだけだと思っとけ。

PIC 9V9 みたいな固定小数点とは、持ち方からして別物だ。

な)え、じゃあなんでそんな別物、わざわざ使うんすか?

係)業務での金額計算じゃ普通使わないな。

科学計算とか、外から来るfloatデータを受けるときくらいだ。

だからこそ、知らずに手を出すと足をすくわれるぞ。お前は必ずすくわれるぞ。

な)救われるんすね!

係)違うわ!

中身を覗く:見た目0.3、中身は0.2999…

係)floatの中身を覗いてみるか。

0.1 + 0.2 を float(COMP-2)でやって、桁いっぱいまで表示すると…

01 FA   USAGE COMP-2 VALUE 0.1.
01 FB   USAGE COMP-2 VALUE 0.2.
01 FC   USAGE COMP-2.
01 SHOW PIC 9V9(17).

COMPUTE FC = FA + FB.
MOVE FC TO SHOW.
DISPLAY "0.1 + 0.2 = " SHOW.
*実行結果
0.1 + 0.2 = 0.29999999999999993

な)0.3じゃなくて0.2999…になってる!

宝くじで番号123456789を当てて、ウキウキで窓口にどうも億万長者になりに来ましたって換金しに行ったら、窓口で「お客様のお番号、12345688.99ですのでお無効でーす、ペコリ」ってなって、無職誕生・無職転生・無職万歳ってやつじゃないっすか!

係)例えが特殊すぎるんだよ…。だがイメージは合ってる。

見た目は0.3でも、中身はピッタリ0.3じゃない。floatはこれをやる。

な)じゃ float で IF FC = 0.3 ってやったら、中身が0.2999だからハズレ判定で、いつまでも一致しない……逮捕されるのが怖くて軽い銀行強盗をして得た金に手をつけない係長みたいに、永遠にループが止まらないってことすか?

係)だれが逮捕だよ!

…いいか、そこはちょっと注意がいる。

GnuCOBOLは、比較のときに細かい端数を丸めて気を利かせる。

だから = 0.3 でも「一致」と判定して、ちゃんと止まることが多い。実際、中身が 0.99999… でも = 1.0 を「真」と見なして止まる。

な)あ、そなの?だったら別に問題ないんじゃ?深夜にチョコバーキメても大丈夫みたいに?

係)甘い。

な)チョコバーだけに?

係)(ジロリ)

な)ひっ!

係)いいか、それはGnuCOBOLが気を利かせて丸めてくれてるだけで、中身がズレてる事実は1ミリも変わらん。

その生の値を保存したり、他のシステムに渡したり、別のCOBOL処理系で動かしたら、その親切は無い。

比較が普通に狂う。

いいか、「今のところ動いてる」と「正しい」は別物だ。 見た目を信じるな。中身を…いや、型を疑え。

対策(floatを使うなら)

係)だから、付き合い方はこうだ。

大前提:そもそも固定小数点(PIC V / COMP-3)を使う。

これが第一の安全策で、COBOLの王道。floatに手を出さなければ、この問題はそもそも起きない。

対策①:どうしてもfloatで「0.1刻み」を扱うなら、整数で回して最後に割る。

01 I PIC 99.
01 X PIC 9V9.

PERFORM VARYING I FROM 0 BY 1 UNTIL I > 10
    COMPUTE X = I / 10
    DISPLAY "X = " X
END-PERFORM
X = 0.0
X = 0.1
X = 0.2
…
X = 1.0

係)ループ変数I は整数(0〜10)で動く。整数に誤差は無い。

計算結果を出すときだけ/ 10で小数化する。

「10回ループ」「0.1刻み」という意図も正確に保てるだろ?

対策②:比較は「=」ではなく「>=」で終わらせて、安全マージンを取る。

PERFORM UNTIL X >= 1.0

係)= 1.0 だと、中身が 0.9999… のとき、環境によっては“まだ1じゃない”と判定されて止まらん。

>= なら、多少ズレてても確実に止まる。

GnuCOBOLは丸めて助けてくれるが、その親切が無い環境 のために、最初から逃げ道を作っておけ。

おむすび

な)ふむむー…ところで、さっきの 0.2999… って、結局どんだけズレてるんすか?

係)10進で言えば、誤差は小数点以下15〜16桁目の世界だ。0.0000000000000001 とか、その辺だな。

な)細か!でも、神経質な係長にぴったりですね!

係)どこがだよ!

な)係長の信念って、どこまでも、いつまでも、誰よりも、何よりも、チョモランマよりも細かくじゃないすか?

係)どんだけ細かいんだよ!

な)オリンポス山ぐらいですか?

係)どこだよそれ?

な)火星?

係)どういうことだよ!

係長のワンポイント

0.1 は2進浮動小数点(COMP-1/COMP-2)にとって「正確に表せない数」だ。
だが、COBOLの普通の小数(PIC …V… / COMP-3)は固定小数点で、0.10.1 + 0.2 も正確に持つ。まずはこれを使え——それが第一の安全策だ。
どうしても浮動小数点を使うなら、整数にスケールして回し、比較は「=」ではなく「>=」で逃げ道を作れ。
GnuCOBOLは比較を丸めて助けてくれるが、生の値はズレているし、別の環境ではその優しさは無い。
COBOLは値を曖昧に扱う言語ではなく、どの型で持つかを設計する言語だ。

― 目次(読みシリーズ)へ戻る ―

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

CAPTCHA