なぜこれらの数字は等しくないのでしょうか? 質問する

なぜこれらの数字は等しくないのでしょうか? 質問する

次のコードは明らかに間違っています。何が問題なのでしょうか?

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は、==ではなく、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.equalinstead 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

おすすめ記事