Mengapa angka-angka ini tidak sama?

273

Kode berikut jelas salah. Apa masalahnya?

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
dplanet
sumber
7
Lihat juga stackoverflow.com/q/6874867 dan stackoverflow.com/q/2769510 . The R Inferno juga membaca besar lain.
Aaron meninggalkan Stack Overflow
1
Q dan A bahasa-agnostik di seluruh situs: Apakah matematika floating point rusak?
Gregor Thomas
dplanet, saya menambahkan solusi untuk semua kasus perbandingan ("<=", "> =", "=") dalam aritmatika presisi ganda di bawah ini. Semoga ini bisa membantu.
Erdogan CEVHER

Jawaban:

355

Alasan umum (agnostik bahasa)

Karena tidak semua angka dapat direpresentasikan secara tepat dalam aritmetika titik apung IEEE (standar yang hampir semua komputer gunakan untuk mewakili angka desimal dan berhitung dengan mereka), Anda tidak akan selalu mendapatkan apa yang Anda harapkan. Ini terutama benar karena beberapa nilai yang sederhana, desimal terbatas (seperti 0,1 dan 0,05) tidak terwakili secara tepat di komputer sehingga hasil aritmatika pada mereka mungkin tidak memberikan hasil yang identik dengan representasi langsung dari " diketahui "jawabannya.

Ini adalah batasan aritmatika komputer yang terkenal dan dibahas di beberapa tempat:

Membandingkan skalar

Solusi standar untuk ini Radalah bukan menggunakan ==, melainkan all.equalfungsi. Atau lebih tepatnya, karena all.equalmemberikan banyak detail tentang perbedaan jika ada isTRUE(all.equal(...)),.

if(isTRUE(all.equal(i,0.15))) cat("i equals 0.15") else cat("i does not equal 0.15")

hasil panen

i equals 0.15

Beberapa contoh lagi menggunakan all.equalbukan ==(contoh terakhir seharusnya menunjukkan bahwa ini akan menunjukkan perbedaan dengan benar).

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

Lebih detail, langsung disalin dari jawaban untuk pertanyaan serupa :

Masalah yang Anda temui adalah floating point tidak dapat mewakili pecahan desimal persis di sebagian besar kasus, yang berarti Anda akan sering menemukan bahwa pencocokan tepat gagal.

sementara R terletak sedikit ketika Anda mengatakan:

1.1-0.2
#[1] 0.9
0.9
#[1] 0.9

Anda dapat mengetahui apa yang sebenarnya dipikirkan dalam desimal:

sprintf("%.54f",1.1-0.2)
#[1] "0.900000000000000133226762955018784850835800170898437500"
sprintf("%.54f",0.9)
#[1] "0.900000000000000022204460492503130808472633361816406250"

Anda dapat melihat angka-angka ini berbeda, tetapi representasi agak sulit digunakan. Jika kita melihatnya dalam biner (well, hex, yang setara) kita mendapatkan gambaran yang lebih jelas:

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"

Anda dapat melihat bahwa mereka berbeda oleh 2^-53, yang penting karena angka ini merupakan perbedaan terkecil yang dapat direpresentasikan antara dua angka yang nilainya mendekati 1, seperti ini.

Kita dapat mengetahui untuk komputer mana pun berapa angka representable terkecil ini dengan melihat di bidang mesin 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"

Anda dapat menggunakan fakta ini untuk membuat fungsi 'hampir sama' yang memeriksa bahwa perbedaannya dekat dengan angka terkecil yang dapat diwakili dalam floating point. Sebenarnya ini sudah ada: 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, ...)
#....

Jadi fungsi all.equal sebenarnya memeriksa bahwa perbedaan antara angka-angka adalah akar kuadrat dari perbedaan terkecil antara dua mantra.

Algoritma ini berjalan agak lucu di dekat angka yang sangat kecil yang disebut denormals, tetapi Anda tidak perlu khawatir tentang itu.

Membandingkan vektor

Diskusi di atas mengasumsikan perbandingan dua nilai tunggal. Dalam R, tidak ada skalar, hanya vektor dan vektorisasi tersirat adalah kekuatan bahasa. Untuk membandingkan nilai elemen vektor, prinsip-prinsip sebelumnya berlaku, tetapi implementasinya sedikit berbeda. ==adalah vektor (melakukan perbandingan elemen-bijaksana) sambil all.equalmembandingkan seluruh vektor sebagai entitas tunggal.

Menggunakan contoh-contoh sebelumnya

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)

==tidak memberikan hasil "yang diharapkan" dan all.equaltidak melakukan elemen-bijaksana

a==b
#[1] FALSE FALSE FALSE FALSE
all.equal(a,b)
#[1] "Mean relative difference: 0.01234568"
isTRUE(all.equal(a,b))
#[1] FALSE

Sebaliknya, versi yang mengulang dua vektor harus digunakan

mapply(function(x, y) {isTRUE(all.equal(x, y))}, a, b)
#[1]  TRUE  TRUE  TRUE FALSE

Jika versi fungsional ini diinginkan, dapat ditulis

elementwise.all.equal <- Vectorize(function(x, y) {isTRUE(all.equal(x, y))})

yang bisa disebut adil

elementwise.all.equal(a, b)
#[1]  TRUE  TRUE  TRUE FALSE

Atau, alih-alih membungkus all.equallebih banyak panggilan fungsi, Anda bisa meniru internal yang relevan all.equal.numericdan menggunakan vektorisasi implisit:

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

Ini adalah pendekatan yang diambil oleh dplyr::near, yang mendokumentasikan dirinya sebagai

Ini adalah cara yang aman untuk membandingkan jika dua vektor angka floating point sama (berpasangan). Ini lebih aman daripada menggunakan ==, karena memiliki toleransi bawaan

dplyr::near(a, b)
#[1]  TRUE  TRUE  TRUE FALSE
Brian Diggs
sumber
R adalah lingkungan perangkat lunak bebas untuk komputasi statistik ??
kittygirl
41

Menambahkan ke komentar Brian (yang merupakan alasan) Anda dapat mengatasi ini dengan menggunakan all.equalsebagai gantinya:

# i <- 0.1
# i <- i + 0.05
# i
#if(all.equal(i, .15)) cat("i equals 0.15\n") else cat("i does not equal 0.15\n")
#i equals 0.15

Per peringatan Joshua di sini adalah kode yang diperbarui (Terima kasih Joshua):

 i <- 0.1
 i <- i + 0.05
 i
if(isTRUE(all.equal(i, .15))) { #code was getting sloppy &went to multiple lines
    cat("i equals 0.15\n") 
} else {
    cat("i does not equal 0.15\n")
}
#i equals 0.15
Tyler Rinker
sumber
17
all.equaltidak kembali FALSEketika ada perbedaan, jadi Anda harus membungkusnya dengan isTRUEsaat menggunakannya dalam ifpernyataan.
Joshua Ulrich
12

Ini retas, tetapi cepat:

if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")
Hillary Sanders
sumber
2
Tapi Anda bisa menggunakan all.equal(... tolerance)parameter. all.equal(0.147, 0.15, tolerance=0.05)adalah benar.
smci
10

dplyr::near()adalah opsi untuk pengujian jika dua vektor angka floating point sama. Ini adalah contoh dari dokumen :

sqrt(2) ^ 2 == 2
#> [1] FALSE
library(dplyr)
near(sqrt(2) ^ 2, 2)
#> [1] TRUE

Fungsi ini memiliki parameter toleransi bawaan: tol = .Machine$double.eps^0.5yang dapat disesuaikan. Parameter default sama dengan default untuk all.equal().

sbha
sumber
0

Saya punya masalah serupa. Saya menggunakan solusi berikut.

@ Saya menemukan solusi ini tentang interval pemotongan yang tidak sama. @ Saya menggunakan fungsi putaran dalam R. Dengan mengatur opsi ke 2 digit, tidak memecahkan masalah.

options(digits = 2)
cbind(
  seq(      from = 1, to = 9, by = 1 ), 
  cut( seq( from = 1, to = 9, by = 1),          c( 0, 3, 6, 9 ) ),
  seq(      from = 0.1, to = 0.9, by = 0.1 ), 
  cut( seq( from = 0.1, to = 0.9, by = 0.1),    c( 0, 0.3, 0.6, 0.9 )),
  seq(      from = 0.01, to = 0.09, by = 0.01 ), 
  cut( seq( from = 0.01, to = 0.09, by = 0.01),    c( 0, 0.03, 0.06, 0.09 ))
)

output dari interval pemotongan yang tidak sama berdasarkan opsi (digit = 2):

  [,1] [,2] [,3] [,4] [,5] [,6]
 [1,]    1    1  0.1    1 0.01    1
 [2,]    2    1  0.2    1 0.02    1
 [3,]    3    1  0.3    2 0.03    1
 [4,]    4    2  0.4    2 0.04    2
 [5,]    5    2  0.5    2 0.05    2
 [6,]    6    2  0.6    2 0.06    3
 [7,]    7    3  0.7    3 0.07    3
 [8,]    8    3  0.8    3 0.08    3
 [9,]    9    3  0.9    3 0.09    3


options(digits = 200)
cbind(
  seq(      from = 1, to = 9, by = 1 ), 
  cut( round(seq( from = 1, to = 9, by = 1), 2),          c( 0, 3, 6, 9 ) ),
  seq(      from = 0.1, to = 0.9, by = 0.1 ), 
  cut( round(seq( from = 0.1, to = 0.9, by = 0.1), 2),    c( 0, 0.3, 0.6, 0.9 )),
  seq(      from = 0.01, to = 0.09, by = 0.01 ), 
  cut( round(seq( from = 0.01, to = 0.09, by = 0.01), 2),    c( 0, 0.03, 0.06, 0.09 ))
)

output dari interval pemotongan yang sama berdasarkan fungsi putaran:

      [,1] [,2] [,3] [,4] [,5] [,6]
 [1,]    1    1  0.1    1 0.01    1
 [2,]    2    1  0.2    1 0.02    1
 [3,]    3    1  0.3    1 0.03    1
 [4,]    4    2  0.4    2 0.04    2
 [5,]    5    2  0.5    2 0.05    2
 [6,]    6    2  0.6    2 0.06    2
 [7,]    7    3  0.7    3 0.07    3
 [8,]    8    3  0.8    3 0.08    3
 [9,]    9    3  0.9    3 0.09    3
Elias EstatisticsEU
sumber
0

Perbandingan umum ("<=", "> =", "=") dalam aritmatika precion ganda:

Membandingkan a <= b:

IsSmallerOrEqual <- function(a,b) {   
# Control the existence of "Mean relative difference..." in all.equal; 
# if exists, it results in character, not logical:
if (   class(all.equal(a, b)) == "logical" && (a<b | all.equal(a, b))) { return(TRUE)
 } else if (a < b) { return(TRUE)
     } else { return(FALSE) }
}

IsSmallerOrEqual(abs(-2-(-2.2)), 0.2) # TRUE
IsSmallerOrEqual(abs(-2-(-2.2)), 0.3) # TRUE
IsSmallerOrEqual(abs(-2-(-2.2)), 0.1) # FALSE
IsSmallerOrEqual(3,3); IsSmallerOrEqual(3,4); IsSmallerOrEqual(4,3) 
# TRUE; TRUE; FALSE

Membandingkan a> = b:

IsBiggerOrEqual <- function(a,b) {
# Control the existence of "Mean relative difference..." in all.equal; 
# if exists, it results in character, not logical:
if (   class(all.equal(a, b)) == "logical" && (a>b | all.equal(a, b))) { return(TRUE)
 } else if (a > b) { return(TRUE)
     } else { return(FALSE) }
}
IsBiggerOrEqual(3,3); IsBiggerOrEqual(4,3); IsBiggerOrEqual(3,4) 
# TRUE; TRUE; FALSE

Membandingkan a = b:

IsEqual <- function(a,b) {
# Control the existence of "Mean relative difference..." in all.equal; 
# if exists, it results in character, not logical:
if (   class(all.equal(a, b)) == "logical" ) { return(TRUE)
 } else { return(FALSE) }
}

IsEqual(0.1+0.05,0.15) # TRUE
Erdogan CEVHER
sumber