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
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
Jawaban:
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:
Solusi standar untuk ini R
adalah bukan menggunakan ==
, melainkan all.equal
fungsi. Atau lebih tepatnya, karena all.equal
memberikan 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.equal
bukan ==
(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.
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.equal
membandingkan 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.equal
tidak 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.equal
lebih banyak panggilan fungsi, Anda bisa meniru internal yang relevan all.equal.numeric
dan 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
Menambahkan ke komentar Brian (yang merupakan alasan) Anda dapat mengatasi ini dengan menggunakan all.equal
sebagai 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
all.equal
tidak kembali FALSE
ketika ada perbedaan, jadi Anda harus membungkusnya dengan isTRUE
saat menggunakannya dalam if
pernyataan.
Ini retas, tetapi cepat:
if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")
all.equal(... tolerance)
parameter. all.equal(0.147, 0.15, tolerance=0.05)
adalah benar.
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.5
yang dapat disesuaikan. Parameter default sama dengan default untuk all.equal()
.
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
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