次のコードは明らかに間違っています。何が問題なのでしょうか?
i <- 0.1
i <- i + 0.05
i
## [1] 0.15
if(i==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")
## i does not equal 0.15
ベストアンサー1
一般的な(言語に依存しない)理由
すべての数字が正確に表現できるわけではないのでIEEE 浮動小数点演算(ほとんどすべてのコンピューターが小数点数を表し、それを使って計算を行うために使用する標準)、期待どおりの結果が常に得られるとは限りません。これは特に、単純な有限小数点数 (0.1 や 0.05 など) の一部の値がコンピューターで正確に表現されず、それらの値に対する計算結果が「既知の」答えを直接表現したものと同じにならない可能性があるためです。
これはコンピュータ演算のよく知られた制限であり、いくつかの場所で議論されています。
- R FAQ にはこれに関する質問があります:R FAQ 7.31
- パトリック・バーンズ著『R インフェルノ』最初の「Circle」をこの問題に当てます(9ページ目から)
- デビッド・ゴールドバーグ、「すべてのコンピュータ科学者が浮動小数点演算について知っておくべきこと」、ACM コンピューティング調査 23、1 (1991-03)、5-4810.1145/103162.103163 翻訳(改訂版も入手可能)
- 浮動小数点ガイド - すべてのプログラマが浮動小数点演算について知っておくべきこと
- 0.300000000000000004.comプログラミング言語間で浮動小数点演算を比較する
- いくつかのStack Overflowの質問には、
- 浮動小数点数はなぜ不正確なのでしょうか?
- なぜ 10 進数は 2 進数で正確に表現できないのでしょうか?
- 浮動小数点演算は壊れていますか?
- 「浮動小数点が不正確」の正規の重複(この問題に対する標準的な回答についてのメタディスカッション)
スカラーの比較
これに対する標準的な解決策R
は、==
ではなく、all.equal
機能。というか、all.equal
違いがあれば、その詳細が多数表示されるので、isTRUE(all.equal(...))
。
if(isTRUE(all.equal(i,0.15))) cat("i equals 0.15") else cat("i does not equal 0.15")
収穫
i equals 0.15
all.equal
instead ofを使用する例をいくつか示します==
(最後の例は、違いが正しく表示されることを示しているはずです)。
0.1+0.05==0.15
#[1] FALSE
isTRUE(all.equal(0.1+0.05, 0.15))
#[1] TRUE
1-0.1-0.1-0.1==0.7
#[1] FALSE
isTRUE(all.equal(1-0.1-0.1-0.1, 0.7))
#[1] TRUE
0.3/0.1 == 3
#[1] FALSE
isTRUE(all.equal(0.3/0.1, 3))
#[1] TRUE
0.1+0.1==0.15
#[1] FALSE
isTRUE(all.equal(0.1+0.1, 0.15))
#[1] FALSE
さらに詳しい内容は、同様の質問への回答:
発生した問題は、ほとんどの場合、浮動小数点数は小数点数を正確に表現できないため、正確な一致が失敗することが多いということです。
一方、R は次のように言うと少し嘘をつきます。
1.1-0.2
#[1] 0.9
0.9
#[1] 0.9
実際に何を考えているのかを 10 進数で調べることができます。
sprintf("%.54f",1.1-0.2)
#[1] "0.900000000000000133226762955018784850835800170898437500"
sprintf("%.54f",0.9)
#[1] "0.900000000000000022204460492503130808472633361816406250"
これらの数字は異なっていることがわかりますが、表現が少し扱いにくいです。2 進数 (正確には 16 進数) で見ると、より明確な図が得られます。
sprintf("%a",0.9)
#[1] "0x1.ccccccccccccdp-1"
sprintf("%a",1.1-0.2)
#[1] "0x1.ccccccccccccep-1"
sprintf("%a",1.1-0.2-0.9)
#[1] "0x1p-53"
それらは だけ異なることがわかります2^-53
。これは重要なことです。なぜなら、この数値は、値が 1 に近い 2 つの数値間の表現可能な最小の差であり、次のようになるからです。
任意のコンピュータで表現可能な最小の数が何であるかは、Rの機械分野:
?.Machine
#....
#double.eps the smallest positive floating-point number x
#such that 1 + x != 1. It equals base^ulp.digits if either
#base is 2 or rounding is 0; otherwise, it is
#(base^ulp.digits) / 2. Normally 2.220446e-16.
#....
.Machine$double.eps
#[1] 2.220446e-16
sprintf("%a",.Machine$double.eps)
#[1] "0x1p-52"
この事実を利用して、差が浮動小数点で表現可能な最小の数値に近いかどうかをチェックする「ほぼ等しい」関数を作成できます。実際、これはすでに存在しています: all.equal
。
?all.equal
#....
#all.equal(x,y) is a utility to compare R objects x and y testing ‘near equality’.
#....
#all.equal(target, current,
# tolerance = .Machine$double.eps ^ 0.5,
# scale = NULL, check.attributes = TRUE, ...)
#....
したがって、all.equal 関数は、実際には数値の差が 2 つの仮数間の最小の差の平方根であるかどうかをチェックしています。
このアルゴリズムは、非正規数と呼ばれる非常に小さな数の近くでは少しおかしくなりますが、それについては心配する必要はありません。
ベクトルの比較
上記の説明では、2 つの単一値の比較を想定しています。R にはスカラーはなく、ベクトルのみであり、暗黙的なベクトル化は言語の強みです。ベクトルの値を要素ごとに比較する場合、前の原則は当てはまりますが、実装が若干異なります。==
はベクトル化されます (要素ごとに比較します)。一方、all.equal
はベクトル全体を単一のエンティティとして比較します。
前の例を使って
a <- c(0.1+0.05, 1-0.1-0.1-0.1, 0.3/0.1, 0.1+0.1)
b <- c(0.15, 0.7, 3, 0.15)
==
「期待される」結果が得られず、all.equal
要素ごとに実行されない
a==b
#[1] FALSE FALSE FALSE FALSE
all.equal(a,b)
#[1] "Mean relative difference: 0.01234568"
isTRUE(all.equal(a,b))
#[1] FALSE
むしろ、2つのベクトルをループするバージョンを使用する必要があります
mapply(function(x, y) {isTRUE(all.equal(x, y))}, a, b)
#[1] TRUE TRUE TRUE FALSE
これを機能的にしたい場合は、次のように記述できます。
elementwise.all.equal <- Vectorize(function(x, y) {isTRUE(all.equal(x, y))})
それはまさに
elementwise.all.equal(a, b)
#[1] TRUE TRUE TRUE FALSE
あるいは、さらに多くの関数呼び出しをラップする代わりにall.equal
、関連する内部を複製してall.equal.numeric
暗黙的なベクトル化を使用することもできます。
tolerance = .Machine$double.eps^0.5
# this is the default tolerance used in all.equal,
# but you can pick a different tolerance to match your needs
abs(a - b) < tolerance
#[1] TRUE TRUE TRUE FALSE
dplyr::near
これは、次のように文書化されている のアプローチです。
これは、2つの浮動小数点数のベクトルが(ペアで)等しいかどうかを比較する安全な方法です。これは、
==
許容誤差が組み込まれているため、を使用するよりも安全です。
dplyr::near(a, b)
#[1] TRUE TRUE TRUE FALSE
ベクトル内の値の出現をテストする
標準の R 関数%in%
も、浮動小数点値に適用すると、同じ問題が発生する可能性があります。例:
x = seq(0.85, 0.95, 0.01)
# [1] 0.85 0.86 0.87 0.88 0.89 0.90 0.91 0.92 0.93 0.94 0.95
0.92 %in% x
# [1] FALSE
次のように、比較の許容範囲を可能にする新しい中置演算子を定義できます。
`%.in%` = function(a, b, eps = sqrt(.Machine$double.eps)) {
any(abs(b-a) <= eps)
}
0.92 %.in% x
# [1] TRUE
dplyr::near
ラップされたany
ベクトルチェックにも使用できる
any(dplyr::near(0.92, x))
# [1] TRUE