Dlaczego te liczby nie są równe?

Poniższy kod jest oczywiście błędny. W czym problem?

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
Author: Jaap, 2012-03-01

3 answers

Ogólny (język agnostyczny) rozum

Ponieważ nie wszystkie liczby mogą być reprezentowane dokładnie w IEEE arytmetyka zmiennoprzecinkowa (standard, który prawie wszystkie komputery używają do reprezentowania liczb dziesiętnych i matematyki z nimi), nie zawsze otrzymasz to, czego oczekiwałeś. Jest to szczególnie prawdziwe, ponieważ niektóre wartości, które są proste, skończone dziesiętne (takie jak 0,1 i 0,05) nie są dokładnie reprezentowane w komputerze, a więc wyniki arytmetyki na nich mogą nie dać wyniku jest to identyczne z bezpośrednim przedstawieniem odpowiedzi "znanej".

Jest to dobrze znane ograniczenie arytmetyki komputerowej i jest omawiane w kilku miejscach:]}

Porównywanie skalarów

Standardowym rozwiązaniem tego w R nie jest użycie ==, ale raczej all.equal funkcja. A raczej, ponieważ all.equal podaje wiele szczegółów na temat różnic, jeśli istnieją, 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

Kilka przykładów użycia all.equal zamiast == (ostatni przykład ma pokazać, że to poprawnie pokaże różnice).

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

Trochę więcej szczegółów, bezpośrednio skopiowane z odpowiedzi na podobne pytanie:

Napotkany problem polega na tym, że zmiennoprzecinkowy nie może dokładnie reprezentować ułamków dziesiętnych w większości przypadków, co oznacza, że często okaże się, że dokładne dopasowania nie.

Podczas gdy R leży lekko, gdy mówisz:

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

Możesz dowiedzieć się, co naprawdę myśli w dziesiętnym:

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

Widać, że te liczby są różne, ale reprezentacja jest trochę nieporęczna. Jeśli spojrzymy na nie w postaci binarnej (dobrze, hex, co jest równoważne), otrzymamy jaśniejszy obraz: {]}

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"

Widać, że różnią się one o 2^-53, co jest ważne, ponieważ liczba ta jest najmniejszą reprezentowalną różnica między dwoma liczbami, których wartość jest bliska 1, Jak to jest.

Możemy dowiedzieć się dla dowolnego komputera, czym jest ta najmniejsza reprezentowalna liczba, patrząc w polu r maszyny :

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

Możesz użyć tego faktu, aby utworzyć funkcję 'prawie równą', która sprawdza, czy różnica jest bliska najmniejszej reprezentowalnej liczby zmiennoprzecinkowej. W rzeczywistości to już istnieje: 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, ...)
#....

Więc wszystko.funkcja equal sprawdza, że różnica między liczbami jest pierwiastkiem kwadratowym najmniejszej różnicy między dwoma mantisami.

Ten algorytm jest trochę zabawny w pobliżu bardzo małych liczb zwanych denormalami, ale nie musisz się o to martwić.

Porównywanie wektorów

Powyższa dyskusja zakładała porównanie dwóch pojedynczych wartości. W R nie ma skalarów, tylko Wektory, a wektoryzacja jest siłą języka. Dla porównania wartości elementów wektorów, poprzednie Zasady trzymają, ale realizacja jest nieco inna. == jest wektoryzowany (dokonuje porównania pierwiastkowego), podczas gdy all.equal porównuje całe wektory jako jeden byt.

Używając poprzednich przykładów

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)

== nie daje "oczekiwanego" wyniku i all.equal nie wykonuje element-wise

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

Zamiast tego należy użyć wersji, która pętli nad dwoma wektorami

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

Jeśli pożądana jest wersja funkcjonalna, może być napisane

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

Które można nazwać po prostu

elementwise.all.equal(a, b)
#[1]  TRUE  TRUE  TRUE FALSE
W przeciwieństwie do innych funkcji, można po prostu replikować odpowiednie wewnętrzne elementy all.equal.numeric i używać niejawnej wektoryzacji: {[34]]}
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
 299
Author: Brian Diggs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-26 23:14:51

Dodając do komentarza Briana (który jest powodem) możesz to zrobić używając zamiast tego all.equal:

# 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 Joshua Ostrzeżenie tutaj jest zaktualizowany kod (dzięki 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
 36
Author: Tyler Rinker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-01 00:59:22

To jest hackish, ale szybkie:

if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")
 9
Author: Hillary Sanders,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-07 01:09:33