浮動小数点数比較の謎

昔のプログラムで浮動小数点数を==で比較するというギョッとするコードがあった。

こんなのまともに動いてたのか? と思ったが、条件によっては問題なさそう。


なぜ浮動小数点数を==で比較するのが御法度とされるのかというと、丸め誤差のためである。

コンピュータの浮動小数点数では0.1は通常は正確に表せないことが知られている。

IEEE 754形式の浮動小数点数、PCだと64bitの倍精度浮動小数点数が使われることが多いが、

マイコンでは32bitの単精度浮動小数点数が使われることが多いので、こちらで例示するが、

0.1は2進数の小数で言えば 0.00110011… という循環小数になる。

2進数で書けば 1.xxxxxxxxxxxxxxxxxxxxxxx × (10)n

という表記にしなければならないので、正確に0.1を表すことが出来ない。

このため、ほぼ同じ数が計算順序などで表記上違う値になることが起きうるのである。

なので、浮動小数点同士がほぼ等しいことを比較するには

fabsf(x-y)<1.0e-5 のように、2つの浮動小数点数の差が非常に小さいと表記すればよいわけである。


なのになぜ問題が起きてなかったのか。

理由は比較する数字がほとんど整数だったからのようだ。

小数は2進数で表記すると循環小数になることも多いが、整数は2進数にしても循環小数になりえない。

単精度の場合、上記の表記で正確に表現できるのは2進数で1が24個並んだ数、すなわち16777215である。

絶対値がこれ以下の整数であれば、浮動小数点表記による丸め誤差が生じることがない。

ゆえにこの範囲の整数演算を浮動小数点数を使って行っても、一応は誤差は生じないことになる。

なので == での比較がうまくいっていたのだろう。


なんでこのプログラムは浮動小数点数を多用していたのかという話だが、

計算の中間値を置くところを浮動小数点型に統一していたためのようだ。

ここに格納される値は必ずしもアナログ的な量に限らず、論理値も入りうる。

その論理値、具体的には –1.0, 0.0, 1.0 の3値論理が多かったのだが、

この比較のために==や!=を使っていたようである。

一応は問題ないと思うが、やはりこういう表記は考え物である。

論理値を表す浮動小数点数は <-0.5は-1、>0.5は1、その間は0と扱うというような処理に改めるべきだろう。


さらにギョッとする使い方もあったんだけど、こちらも上記の範囲の整数ということで説明はできそうだ。

ただ、こちらはあまり必然性がなさそうな気がするので、整数表記に改めた方がいいんじゃないかな。

本当ならば浮動小数点を格納するところと、整数を格納するところを分離した方がいいけど、

そうするといろいろ影響が大きいので、誤動作が生じないやり方でうまくやっていくしかないかな。