浮動小数点数の等値判定

公開日:2018-11-15
最終更新:2018-12-15

tl;dr

二つの浮動小数点が等しいか判定するときは以下のように行う。

def equal?(lhs, rhs, epsilon=1e-10)  
    (lhs - rhs).abs <= epsilon ||  
        (lhs - rhs).abs <= [lhs.abs, rhs.abs].max * epsilon  
end  

本題

二つの浮動小数点数が等しいかどうかを等値演算子で比較してはいけない。浮動小数点数には誤差があるため、直観的には等しいはずの二つの数が異なる場合がある。以下のRubyのスクリプトを実行すると

tmp = 0  
10.times {  
    tmp += 0.1  
}  
values = [[1.0 - 0.9, 0.1],  
          [0.15 + 0.15, 0.1 + 0.2],  
          [tmp, 1.0]]  
values.each {|pair|  
    lhs, rhs = pair  
    printf("equal? = %p, lhs = %.18f, rhs = %.18f, diff = %.18f\n",  
           lhs == rhs, lhs, rhs, (lhs - rhs).abs)  
}  
equal? = false, lhs = 0.099999999999999978, rhs = 0.100000000000000006, diff = 0.000000000000000028  
equal? = false, lhs = 0.299999999999999989, rhs = 0.300000000000000044, diff = 0.000000000000000056  
equal? = false, lhs = 0.999999999999999889, rhs = 1.000000000000000000, diff = 0.000000000000000111  

このように、1.0-0.9と0.1の比較、0.15+0.15と0.1+0.2の比較、0.1の10回加算と1.0の比較、いずれも等しくならない。diffはその絶対誤差を表している。

これを避けるためのよく知られている方法が、絶対誤差を十分小さい数(epsilon)と比べる方法である。

(lhs - rhs).abs <= epsilon  

仮にepsilonを1e-10としてみると

epsilon = 1e-10  
tmp = 0  
10.times {  
    tmp += 0.1  
}  
values = [[1.0 - 0.9, 0.1],  
          [0.15 + 0.15, 0.1 + 0.2],  
          [tmp, 1.0]]  
values.each {|pair|  
    lhs, rhs = pair  
    printf("equal? = %p, lhs = %.18f, rhs = %.18f, diff = %.18f\n",  
           (lhs - rhs).abs <= epsilon, lhs, rhs, (lhs - rhs).abs)  
}  
equal? = true , lhs = 0.099999999999999978, rhs = 0.100000000000000006, diff = 0.000000000000000028  
equal? = true, lhs = 0.299999999999999989, rhs = 0.300000000000000044, diff = 0.000000000000000056  
equal? = true, lhs = 0.999999999999999889, rhs = 1.000000000000000000, diff = 0.000000000000000111  

いずれも絶対誤差が1e-10以下に収まっているので等しいと判定される。

絶対誤差を使う方法の問題点は、とても大きな数を対象にするとそれがepsilonをすぐに超えてしまうことだ。たとえば、上に挙げた0.1の10回加算と1.0の比較にならい、1e6に対する0.1の10回加算と1.0の加算を比較してみると

epsilon = 1e-10  
lhs = rhs = 1e6  
10.times {  
    lhs += 0.1  
}  
rhs += 1.0  
printf("equal? = %p, lhs = %.18f, rhs = %.18f, diff = %.18f\n",  
       (lhs - rhs).abs <= epsilon, lhs, rhs, (lhs - rhs).abs)  
 equal? = false, lhs = 1000000.999999999767169356, rhs = 1000001.000000000000000000, diff = 0.000000000232830644  

両者の絶対誤差が1e-10を超えており、等しいと判定されなくなる。これは、とても大きな数に対する演算誤差が小さな数より大きいからである。

この問題を避けるには、絶対誤差ではなく相対誤差を用いればいい。相対誤差とは、両者の差の絶対値を両者の絶対値の大きい方で割った値である。相対誤差を用いるコードを以下に示す。ただし、ゼロ除算が起きないようにa / b <= ca <= b * cと変形している。

(lhs - rhs).abs <= [lhs.abs, rhs.abs].max * epsilon  

相対誤差を用いて判定すると

epsilon = 1e-10  
lhs = rhs = 1e6  
10.times {  
    lhs += 0.1  
}  
rhs += 1  
printf("equal? = %p, lhs = %.18f, rhs = %.18f, rel = %.18f\n",  
       (lhs - rhs).abs <= [lhs.abs, rhs.abs].max * epsilon,  
       lhs, rhs, (lhs - rhs).abs / [lhs.abs, rhs.abs].max)  
equal? = true, lhs = 1000000.999999999767169356, rhs = 1000001.000000000000000000, rel = 0.000000000000000233  

1e6に0.1を10回加えたものと1e6に1.0を加えたものが等しく判定されるようになる。ここでrelは相対誤差を表している。

しかし相対誤差を用いると、今度は0と見なせる非常に小さな数同士が等しいと判定されなくなる。たとえば、1e-11と1e-12の等値判定に相対誤差を用いると

epsilon = 1e-10  
lhs = 1e-11  
rhs = 1e-12  
printf("equal? = %p, lhs = %.18f, rhs = %.18f, rel = %.18f\n",  
       (lhs - rhs).abs <= [lhs.abs, rhs.abs].max * epsilon,  
       lhs, rhs, (lhs - rhs).abs / [lhs.abs, rhs.abs].max)  
lhs = 0.000000000010000000, rhs = 0.000000000001000000, rel = 0.900000000000000022, equal? = false  

1e-11と1e-12の絶対誤差は1e-10より小さいが、相対誤差は0.9なので等しいとは判定されない。

いずれの場合の判定も正しく行うには、絶対誤差による判定と相対誤差による判定のいずれかが成立すれば等しいと見なせばよい。

def equal?(lhs, rhs, epsilon=1e-10)  
    (lhs - rhs).abs <= epsilon ||  
        (lhs - rhs).abs <= [lhs.abs, rhs.abs].max * epsilon  
end  

lhs = rhs = 1e6  
10.times {  
    lhs += 0.1  
}  
rhs += 1.0  
printf("equal? = %p, lhs = %.18f, rhs = %.18f, abs = %.18f\n",  
       (lhs - rhs).abs, equal?(lhs, rhs), lhs, rhs)  

lhs = 1e-11  
rhs = 1e-12  
printf("equal? = %p, lhs = %.18f, rhs = %.18f, rel = %.18f\n",  
       equal?(lhs, rhs), lhs, rhs, (lhs - rhs).abs / [lhs.abs, rhs.abs].max)  
equal? = true, lhs = 1000000.999999999767169356, rhs = 1000001.000000000000000000, abs = 0.000000000232830644  
equal? = true, lhs = 0.000000000010000000, rhs = 0.000000000001000000, rel = 0.900000000000000022  

このように大きな数字同士と小さな数字同士のいずれの場合も、期待通り等しいと判定されている。

machine epsilon (計算機イプシロン)の罠

ここまでepsilonは1e-10に決め打ちだったが、より理論的な数字で浮動小数点数の等値比較に使うとよいと誤解されている数字にmachine epsilon (計算機イプシロン)がある。machine epsilonの理論的な定義は、浮動小数点演算の丸めによって生じる相対誤差の最大値である。

さまざまなプログラミング言語でmachine epsilonとして定義されている定数は、それとは異なり1 + epsilon > 1となるepsilonの最小数である。これは理論的な定義の二倍の値となる。以降machine epsilonと言うときはこちらを指す。たとえば、Rubyでは2.220446049250313e-16がFloat::EPSILONとして定義されている。

プログラミング言語のmachine epsilonを、そのまま浮動小数点数の等値比較に使うことを勧めている例として(1)や(2)がある。しかし、その用途にはmachine epsilonは小さすぎる。

たとえば、先に上げた1e6に0.1を10回加えたものと、1e6に1.0を加えたものの等値判定で、epsilonをFloat::EPSILONにすると以下の結果になる。

epsilon = Float::EPSILON  
lhs = rhs = 1e6  
10.times {  
    lhs += 0.1  
}  
rhs += 1  
printf("lhs = %.18f, rhs = %.18f, rel = %.18f, equal? = %p \n",  
       (lhs - rhs).abs <= [lhs.abs, rhs.abs].max * epsilon,  
       lhs, rhs, (lhs - rhs).abs / [lhs.abs, rhs.abs].max)  
equal? = false, lhs = 1000000.999999999767169356, rhs = 1000001.000000000000000000, rel = 0.000000000000000233  

期待に反して等しいとは判定されなくなっている。

machine epsilonが保証するのは、浮動小数点演算一回のまるめ誤差がその半分以下に収まることだけである。繰り返し演算すれば相対誤差はmachine epsilonを超える。

結局のところepsilonとしてどのような数字を用いるべきかは、アプリケーションで浮動小数点数の誤差をどの程度許容するかによる。machine epsilonを何倍かして利用するのも一つの手だが、machine epsilonよりずっと大きな値を使うのが無難である。この話題についてもっと詳しく知りたい人は「Comparing Floating Point Numbers, 2012 Edition」を読むとよい。

記事が少しでもいいなと思ったらクラップを送ってみよう!
90
+1
@fujiedaの技術ブログ

よく一緒に読まれている記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう
目次をみる

技術ブログをはじめよう

Qrunch(クランチ)は、ITエンジニアリングに携わる全ての人のための技術ブログプラットフォームです。

技術ブログを開設する

Qrunchでアウトプットをはじめよう

Qrunch(クランチ)は、ITエンジニアリングに携わる全ての人のための技術ブログプラットフォームです。

Markdownで書ける

ログ機能でアウトプットを加速

デザインのカスタマイズが可能

技術ブログ開設

ここから先はアカウント(ブログ)開設が必要です

英数字4文字以上
.qrunch.io
英数字6文字以上
ログインする