Dlaczego te liczby nie są równe?

273

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
dplanet
źródło
7
Zobacz także stackoverflow.com/q/6874867 i stackoverflow.com/q/2769510 . R Inferno jest również inny wielki odczytu.
Aaron opuścił Stack Overflow
1
Niezależne od języka pytania i odpowiedzi w całej witrynie: Czy matematyka zmiennoprzecinkowa jest zepsuta?
Gregor Thomas
dplanet, dodałem rozwiązanie dla wszystkich przypadków porównawczych („<=”, „> =”, „=”) w arytmetyce podwójnej precyzji poniżej. Mam nadzieję, że to pomoże.
Erdogan CEVHER,

Odpowiedzi:

355

Powód ogólny (agnostyczny z języka)

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:

Porównywanie skalarów

Standardowym rozwiązaniem tego Rnie jest użycie ==, ale all.equalfunkcja. Albo raczej, ponieważ all.equaldaje 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.equalzamiast ==(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ć.

Porównywanie wektorów

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.equalporó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.equalnie 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.equaljeszcze więcej wywołań funkcji, możesz po prostu powielić odpowiednie elementy wewnętrzne all.equal.numerici 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
Brian Diggs
źródło
R to wolne środowisko programowe do obliczeń statystycznych?
Kittygirl
41

Dodając do komentarza Briana (z tego powodu) możesz to zrobić, używając all.equalzamiast 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
Tyler Rinker
źródło
17
all.equalnie zwraca, FALSEgdy występują różnice, więc musisz go owinąć, isTRUEgdy używasz go w ifinstrukcji.
Joshua Ulrich
12

To jest hackerskie, ale szybkie:

if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")
Hillary Sanders
źródło
2
Ale możesz użyć all.equal(... tolerance)parametru. all.equal(0.147, 0.15, tolerance=0.05)jest prawdziwy.
smci
10

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.5można go regulować. Domyślny parametr jest taki sam jak domyślny dla all.equal().

sbha
źródło
0

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
Elias EstatisticsEU
źródło
0

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
Erdogan CEVHER
źródło