2013年12月7日土曜日

[Java][Scala] 浮動小数点の有効桁数と同値性

浮動小数点で掛け算や割り算を行うと微妙に正確な値からずれていって困っちゃいますよね。例えば、いろいろ計算した結果、机上ではぴったり1になるはずなのに、コンピュータ上では、0.9999999になってたりとか。こんなとき困るのが同値性を検証するときです。こっちは1だと思ってるので、とうぜん、
if( x == 1.)
とかで比較したくなりますよね。でも実はxが0.999999だったりしてfalseになっちゃったりします。でも、これほぼ1やん!って。trueにしよて!ってなりますよね。こんなときに、上位何桁まで一致してたら同じって判断してくるようなものないでしょうか。

どうやらあるようです。

そりゃそうですよね。こんなシチュエーションってよくあると思いますから、解決方法もちゃんと用意されているはずですよね。
参考になったのが、このサイトです。

このサイトで言ってるのは、java.lang.Mathクラスのulpメソッドを使えってことです。簡単に言うと、このメソッドは、浮動小数点の分解能を返してくれます。精度って言ってもいいかもしれません。いくつかの例で試してみましょう。
scala> java.lang.Math.ulp(1.0f)
res2: Float = 1.1920929E-7
1.0fの場合は、10-7くらいの精度をもってるようです。
次は大きい数字で試してみましょう。
scala> java.lang.Math.ulp(87654321.0f)
res3: Float = 8.0
107くらいの大きな数の場合は、100くらいの精度のようです。
次はものすごく小さい数字です。
scala> java.lang.Math.ulp(0.00000001234567f)
res6: Float = 8.881784E-16
10-8くらいの小さな数の場合は、10-16くらいの精度のようです。
こうやってみると、Float型は、総じて、だいたい上位7,8桁くらいまで表現できる能力を持っているようですね。このメッソドを使えば、上位から好きな桁数で簡単に比較できそうですね。

もっとも細かく比較する場合は、
if( Math.abs( x - y ) < Math.ulp( x ) )
とすればよさげですね。

もっと精度を低くしたければ、
if( Math.abs( x - y ) < Math.ulp( x )*10 )
のようにulpを10倍、100倍としていけば、どんどん粗い桁で同値性を検証できます。

0 件のコメント:

コメントを投稿