1. ホーム
  2. r

[解決済み] [解答】なぜこれらの数字は等しくないのですか?

2022-04-19 17:10:10

質問

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

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

解決方法は?

一般的な(言語にとらわれない)理由

すべての数字を正確に表現できるわけではありませんので IEEE浮動小数点演算 (ほとんどすべてのコンピュータが十進数を表現し、それを使って計算するために使用している規格)であるため、必ずしも期待通りの結果が得られるとは限りません。特に、単純な有限の小数である値(0.1や0.05など)の中には、コンピュータで正確に表現できないものがあるため、それに対する演算の結果、"known"の答えを直接表現したのと同じ結果が出ない場合があるからです。

これはコンピュータの演算の限界としてよく知られていることで、いくつかの場所で議論されている。

スカラーを比較する

での標準的な解答は 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 の代わりに == (最後の例は、これで差分が正しく表示されることを示すためのものです)。

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 はベクトル全体を1つの実体として比較します。

先ほどの例でいうと

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