Por que esses números não são iguais?

273

O código a seguir está obviamente errado. Qual é o problema?

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
fonte
7
Consulte também stackoverflow.com/q/6874867 e stackoverflow.com/q/2769510 . O R Inferno também é outra ótima leitura.
Aaron deixou o Stack Overflow
1
Perguntas e respostas independentes de idioma em todo o site: A matemática de ponto flutuante está quebrada?
Gregor Thomas
dplanet, adicionei uma solução para todos os casos de comparação ("<=", "> =", "=") na aritmética de dupla precisão abaixo. Espero que ajude.
Erdogan CEVHER

Respostas:

355

Razão geral (independente de idioma)

Como nem todos os números podem ser representados exatamente na aritmética de ponto flutuante IEEE (o padrão que quase todos os computadores usam para representar números decimais e fazer contas com eles), nem sempre você obtém o que esperava. Isso é especialmente verdadeiro porque alguns valores que são decimais simples e finitos (como 0,1 e 0,05) não são representados exatamente no computador e, portanto, os resultados da aritmética neles podem não fornecer um resultado idêntico a uma representação direta do " conhecida "resposta.

Essa é uma limitação bem conhecida da aritmética computacional e é discutida em vários locais:

Comparando Escalares

A solução padrão para isso Ré não usar ==, mas sim a all.equalfunção. Ou melhor, já que all.equalfornece muitos detalhes sobre as diferenças, se houver alguma isTRUE(all.equal(...)).

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

rendimentos

i equals 0.15

Mais alguns exemplos de uso em all.equalvez de ==(o último exemplo deve mostrar que isso mostrará corretamente as diferenças).

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

Mais detalhes, copiados diretamente de uma resposta para uma pergunta semelhante :

O problema que você encontrou é que o ponto flutuante não pode representar frações decimais exatamente na maioria dos casos, o que significa que você encontrará frequentemente que as correspondências exatas falham.

enquanto R fica um pouco quando você diz:

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

Você pode descobrir o que realmente pensa em decimal:

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

Você pode ver que esses números são diferentes, mas a representação é um pouco difícil. Se olharmos para eles em binário (bem, hexadecimal, que é equivalente), obtemos uma imagem mais clara:

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"

Você pode ver que eles diferem por 2^-53, o que é importante porque esse número é a menor diferença representável entre dois números cujo valor é próximo a 1, como é este.

Podemos descobrir em qualquer computador qual é esse menor número representável olhando no campo da máquina de 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"

Você pode usar esse fato para criar uma função 'quase igual' que verifique se a diferença está próxima do menor número representável no ponto flutuante. Na verdade isso já existe: 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, ...)
#....

Portanto, a função all.equal está realmente verificando se a diferença entre os números é a raiz quadrada da menor diferença entre duas mantissas.

Esse algoritmo fica um pouco engraçado perto de números extremamente pequenos chamados denormals, mas você não precisa se preocupar com isso.

Comparando vetores

A discussão acima assumiu uma comparação de dois valores únicos. Em R, não existem escalares, apenas vetores e a vetorização implícita é uma força da linguagem. Para comparar o valor de vetores em termos de elementos, os princípios anteriores são válidos, mas a implementação é um pouco diferente. ==é vetorizado (faz uma comparação elemento a elemento) enquanto all.equalcompara os vetores inteiros como uma única entidade.

Usando os exemplos anteriores

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)

==não fornece o resultado "esperado" e all.equalnão executa em elementos

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

Em vez disso, uma versão que passa pelos dois vetores deve ser usada

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

Se uma versão funcional disso for desejada, ela poderá ser escrita

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

que pode ser chamado apenas

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

Como alternativa, em vez de agrupar all.equalainda mais chamadas de função, você pode apenas replicar os internos relevantes all.equal.numerice usar a vetorização implícita:

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

Essa é a abordagem adotada dplyr::near, que se documenta como

Esta é uma maneira segura de comparar se dois vetores de números de ponto flutuante são (em pares) iguais. Isso é mais seguro do que usar ==, porque possui uma tolerância integrada

dplyr::near(a, b)
#[1]  TRUE  TRUE  TRUE FALSE
Brian Diggs
fonte
R é um ambiente de software livre para computação estatística?
kittygirl 17/06
41

Adicionando ao comentário de Brian (que é o motivo), você pode passar por isso usando 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

De acordo com o aviso de Joshua, aqui está o código atualizado (Obrigado 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
fonte
17
all.equalnão retorna FALSEquando há diferenças; portanto, você precisa envolvê-lo isTRUEao usá-lo em uma ifinstrução
Joshua Ulrich
12

Isso é hackish, mas rápido:

if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")
Hillary Sanders
fonte
2
Mas você pode usar o all.equal(... tolerance)parâmetro all.equal(0.147, 0.15, tolerance=0.05)é verdade.
smci 28/05
10

dplyr::near()é uma opção para testar se dois vetores de números de ponto flutuante são iguais. Este é o exemplo dos documentos :

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

A função possui um parâmetro de tolerância embutido: tol = .Machine$double.eps^0.5que pode ser ajustado. O parâmetro padrão é o mesmo que o padrão para all.equal().

sbha
fonte
0

Eu tive um problema parecido. Eu usei a seguinte solução.

@ Encontrei esta solução alternativa para intervalos de corte desiguais. @ Usei a função round em R. Ao definir a opção para 2 dígitos, não resolvi o problema.

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

saída de intervalos de corte desiguais com base nas opções (dígitos = 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 ))
)

saída de intervalos de corte iguais com base na função redonda:

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

Comparações generalizadas ("<=", "> =", "=") na aritmética de duplo precião:

Comparando a <= 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

Comparando 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

Comparando 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
fonte