Poniższy kod jest oczywiście nieprawidłowy. Jaki jest 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
Poniższy kod jest oczywiście nieprawidłowy. Jaki jest 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
Odpowiedzi:
Ponieważ nie wszystkie liczby można przedstawić dokładnie w arytmetyce zmiennoprzecinkowej IEEE (standard, którego prawie wszystkie komputery używają do reprezentowania liczb dziesiętnych i wykonywania z nimi matematyki), nie zawsze otrzymasz to, czego się spodziewałeś. Jest to szczególnie prawdziwe, ponieważ niektóre wartości, które są prostymi, skończonymi liczbami dziesiętnymi (np. 0,1 i 0,05), nie są dokładnie reprezentowane w komputerze, więc wyniki arytmetyki na nich mogą nie dać wyniku identycznego z bezpośrednią reprezentacją „ znana „odpowiedź.
Jest to dobrze znane ograniczenie arytmetyki komputerowej i jest omawiane w kilku miejscach:
Standardowym rozwiązaniem tego R
nie jest użycie ==
, ale all.equal
funkcja. Albo raczej, ponieważ all.equal
daje wiele szczegółów na temat różnic, jeżeli istnieją, isTRUE(all.equal(...))
.
if(isTRUE(all.equal(i,0.15))) cat("i equals 0.15") else cat("i does not equal 0.15")
daje
i equals 0.15
Więcej przykładów użycia all.equal
zamiast ==
(ostatni przykład ma wykazać, że poprawnie pokażą 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
Więcej szczegółów, bezpośrednio skopiowanych z odpowiedzi na podobne pytanie :
Problem, z którym się spotkałeś, polega na tym, że zmiennoprzecinkowe nie mogą reprezentować ułamków dziesiętnych dokładnie w większości przypadków, co oznacza, że często zdarza się, że dokładne dopasowanie nie powiedzie się.
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 tak naprawdę myśli dziesiętnie:
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 nieco nieporęczna. Jeśli spojrzymy na nie w systemie binarnym (cóż, heks, co jest równoważne), otrzymamy wyraź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 2^-53
, co jest ważne, ponieważ liczba ta jest najmniejszą możliwą do reprezentacji różnicą między dwiema liczbami, których wartość jest bliska 1, jak to jest.
Możemy dowiedzieć się dla każdego komputera, jaki jest ten najmniejszy reprezentowalny numer, patrząc na pole maszyny 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"
Możesz użyć tego faktu, aby utworzyć funkcję „prawie równą”, która sprawdza, czy różnica jest bliska najmniejszej reprezentowalnej liczbie w liczbach zmiennoprzecinkowych. 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, ...)
#....
Zatem funkcja all.equal faktycznie sprawdza, czy różnica między liczbami jest pierwiastkiem kwadratowym najmniejszej różnicy między dwiema mantysami.
Ten algorytm jest nieco zabawny w pobliżu bardzo małych liczb zwanych denormalami, ale nie musisz się tym martwić.
Powyższa dyskusja zakładała porównanie dwóch pojedynczych wartości. W języku R nie ma skalarów, tylko wektory, a ukryta wektoryzacja to siła języka. Przy porównywaniu wartości wektorów elementarnych obowiązują poprzednie zasady, ale implementacja jest nieco inna. ==
jest wektoryzowany (dokonuje porównania elementarnego), podczas gdy all.equal
porównuje całe wektory jako jedną całość.
Korzystając z 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 elementu
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 zapętla dwa wektory
mapply(function(x, y) {isTRUE(all.equal(x, y))}, a, b)
#[1] TRUE TRUE TRUE FALSE
Jeśli pożądana jest jej funkcjonalna wersja, można ją zapisać
elementwise.all.equal <- Vectorize(function(x, y) {isTRUE(all.equal(x, y))})
które można nazwać sprawiedliwym
elementwise.all.equal(a, b)
#[1] TRUE TRUE TRUE FALSE
Alternatywnie, zamiast owijać all.equal
jeszcze więcej wywołań funkcji, możesz po prostu powielić odpowiednie elementy wewnętrzne all.equal.numeric
i użyć niejawnej wektoryzacji:
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
Takie jest podejście dplyr::near
, które dokumentuje się jako
Jest to bezpieczny sposób porównywania, jeśli dwa wektory liczb zmiennoprzecinkowych są (parami) równe. Jest to bezpieczniejsze niż używanie
==
, ponieważ ma wbudowaną tolerancję
dplyr::near(a, b)
#[1] TRUE TRUE TRUE FALSE
Dodając do komentarza Briana (z tego powodu) możesz to zrobić, używając all.equal
zamiast tego:
# 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
Ostrzeżenie Jozuego tutaj to 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
all.equal
nie zwraca, FALSE
gdy występują różnice, więc musisz go owinąć, isTRUE
gdy używasz go w if
instrukcji.
To jest hackerskie, ale szybkie:
if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")
all.equal(... tolerance)
parametru. all.equal(0.147, 0.15, tolerance=0.05)
jest prawdziwy.
dplyr::near()
jest opcją do testowania, czy dwa wektory liczb zmiennoprzecinkowych są równe. Oto przykład z dokumentacji :
sqrt(2) ^ 2 == 2
#> [1] FALSE
library(dplyr)
near(sqrt(2) ^ 2, 2)
#> [1] TRUE
Funkcja ma wbudowany parametr tolerancji: tol = .Machine$double.eps^0.5
można go regulować. Domyślny parametr jest taki sam jak domyślny dla all.equal()
.
Miałem podobny problem. Użyłem następującego rozwiązania.
@ Znalazłem to obejście rozwiązania dotyczące nierównych odstępów czasu cięcia. @ Użyłem funkcji okrągłej w R. Ustawienie opcji na 2 cyfry nie rozwiązało problemu.
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 ))
)
generowanie nierównych przedziałów cięcia na podstawie opcji (cyfry = 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 ))
)
wyjście równych przedziałów cięcia na podstawie funkcji okrągłej:
[,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
Uogólnione porównania („<=”, „> =”, „=”) w podwójnej arytmetyki:
Porównywanie <= 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
Porównywanie 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
Porównywanie 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