Bu sayılar neden eşit değil?


273

Aşağıdaki kod açıkça yanlıştır. Sorun ne?

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
Site çapında bir dil agnostik Soru-Cevap: Kayan nokta matematiği kırık mı?
Gregor Thomas

dplanet, aşağıdaki çift kesinlikli aritmetikteki tüm karşılaştırma durumları ("<=", "> =", "=") için bir çözüm ekledim. Umarım yardımcı olur.
Erdoğan CEVHER

Yanıtlar:


355

Genel (dil agnostik) nedeni

Tüm sayılar tam olarak IEEE kayan nokta aritmetiğinde temsil edilemediğinden (neredeyse tüm bilgisayarların ondalık sayıları temsil etmek ve onlarla matematik yapmak için kullandığı standart), her zaman beklediğiniz şeyi elde edemezsiniz. Bu özellikle doğrudur, çünkü basit, sonlu ondalık sayılar (0.1 ve 0.05 gibi) tam olarak bilgisayarda temsil edilmez ve bu nedenle üzerlerindeki aritmetik sonuçlar, " bilinen cevap.

Bu, bilgisayar aritmetiğinin iyi bilinen bir sınırlamasıdır ve birkaç yerde tartışılmaktadır:

Skalerlerin karşılaştırılması

Bunun standart çözümü Rkullanmak ==değil, all.equalişlevdir. Ya da daha doğrusu, all.equaleğer varsa, farklılıklar hakkında çok fazla ayrıntı verdiğinden isTRUE(all.equal(...)),.

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

verim

i equals 0.15

all.equalBunun yerine kullanmaya ilişkin bazı örnekler ==(son örnekte bunun farklılıklar doğru gösterileceği gösterilmektedir).

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

Benzer bir sorunun cevabından doğrudan kopyalanan biraz daha detay :

Karşılaştığınız sorun, kayan noktanın ondalık kesirleri çoğu durumda tam olarak temsil edememesidir, yani tam eşleşmelerin başarısız olduğunu sık sık bulacaksınız.

R dediğinde R hafifçe yatar:

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

Ondalık olarak gerçekten ne düşündüğünü öğrenebilirsiniz:

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

Bu sayıların farklı olduğunu görebilirsiniz, ancak gösterim biraz hantal. Onlara ikili olarak bakarsak (iyi, onaltılık, eşdeğer) daha net bir resim elde ederiz:

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^-53Farklı olduklarını görebilirsiniz , bu önemlidir çünkü bu sayı, değeri 1'e yakın olan iki sayı arasındaki en küçük temsil edilebilir farktır.

Herhangi bir bilgisayar için, R'nin makine alanına bakarak bu en küçük temsil edilebilir numaranın ne olduğunu bulabiliriz :

 ?.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"

Bu gerçeği, farkın kayan noktadaki en küçük temsil edilebilir sayıya yakın olduğunu kontrol eden 'neredeyse eşittir' işlevi oluşturmak için kullanabilirsiniz. Aslında bu zaten var: 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 işlevi aslında sayılar arasındaki farkın iki mantis arasındaki en küçük farkın kare kökü olduğunu kontrol etmektir.

Bu algoritma denormals denilen son derece küçük sayıların yakınında biraz komik oluyor, ancak bunun için endişelenmenize gerek yok.

Vektörleri karşılaştırma

Yukarıdaki tartışma iki tek değerin karşılaştırılmasını varsaymıştır. R'de skaler yoktur, sadece vektörler ve örtük vektörleşme dilin gücüdür. Vektörlerin değerini element açısından karşılaştırmak için önceki ilkeler geçerlidir, ancak uygulama biraz farklıdır. tüm vektörleri tek bir varlık olarak karşılaştırırken ==, vektörize edilir (eleman bazında bir karşılaştırma yapar) all.equal.

Önceki örnekleri kullanma

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)

=="beklenen" sonucu all.equalvermez ve öğe bazında çalışmaz

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

Aksine, iki vektörün üzerinden geçen bir versiyon kullanılmalıdır.

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

Bunun işlevsel bir versiyonu isteniyorsa, yazılabilir

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

ki bu sadece

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

Alternatif olarak, all.equaldaha fazla işlev çağrısını sarmak yerine, yalnızca ilgili all.equal.numericiçleri çoğaltabilir ve örtük vektörleştirmeyi kullanabilirsiniz:

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

Bu, dplyr::nearkendini şu şekilde belgeleyen yaklaşımdır .

Kayan nokta sayılarının iki vektörünün (çift olarak) eşit olup olmadığını karşılaştırmanın güvenli bir yoludur. Kullanmaktan daha güvenlidir ==, çünkü yerleşik bir toleransı vardır

dplyr::near(a, b)
#[1]  TRUE  TRUE  TRUE FALSE

R istatistiksel hesaplama için ücretsiz bir yazılım ortamı ??
kittygirl

41

Brian'ın yorumuna (bunun nedeni) ekleyerek all.equalbunun yerine şunu kullanabilirsiniz:

# 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

Burada Joshua'nın uyarısı güncellenmiş kod (Teşekkürler 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

17
all.equalFALSEfarklılıklar olduğunda geri dönmez , bu nedenle isTRUEbir ififadede kullanırken onu sarmanız gerekir .
Joshua Ulrich

12

Bu acayip, ama hızlı:

if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")

2
Ancak all.equal(... tolerance)parametreyi kullanabilirsiniz . all.equal(0.147, 0.15, tolerance=0.05)doğru.
smci

10

dplyr::near()kayan nokta sayılarının iki vektörünün eşit olup olmadığını test etmek için bir seçenektir. Bu dokümanlardan bir örnek :

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

Fonksiyonun yerleşik bir tolerans parametresi vardır: tol = .Machine$double.eps^0.5ayarlanabilir. Varsayılan parametre, için varsayılanla aynıdır all.equal().


0

Benzer bir sorun yaşadım. Aşağıdaki çözümü kullandım.

@ Eşitsiz kesim aralıklarıyla ilgili bu çözümü çözüm buldum. @ R'de yuvarlak işlevi kullandım. Seçeneği 2 rakama ayarlayarak sorunu çözmedim.

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 ))
)

seçeneklere göre eşit olmayan kesme aralıklarının çıkışı (basamak = 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 ))
)

yuvarlak fonksiyona dayalı eşit kesme aralıklarının çıkışı:

      [,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

0

Çift önlü aritmetikte genelleştirilmiş karşılaştırmalar ("<=", "> =", "="):

<= B karşılaştırması:

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

> = B karşılaştırması:

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

A = b karşılaştırması:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.