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
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:]}- R FAQ ma pytanie: R FAQ 7.31
- W 2004 roku, w ramach projektu "the R Inferno", powstał pierwszy "krąg" poświęcony temu problemowi (począwszy od strony 9).]} David Goldberg, "co każdy informatyk powinien wiedzieć o arytmetyce zmiennoprzecinkowej", ACM Computing Ankiety 23, 1 (1991-03), 5-48 doi>10.1145/103162.103163 (wersja dostępna również )
- Przewodnik Zmiennoprzecinkowy-Co Każdy Programista Powinien Wiedzieć O Arytmetyce Zmiennoprzecinkowej
- 0.30000000000000004.com porównuje arytmetykę zmiennoprzecinkową między językami programowania
- kilka pytań dotyczących przepełnienia stosu, w tym
- Dlaczego Liczby Zmiennoprzecinkowe Niedokładne?
- dlaczego liczby dziesiętne nie mogą być dokładnie reprezentowane w postaci binarnej?
- czy matematyka zmiennoprzecinkowa jest zepsuta?
- duplikat kanoniczny dla "zmiennoprzecinkowego jest niedokładny" (meta-dyskusja na temat kanonicznej odpowiedzi na to pytanie)
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
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
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")
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