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

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 

Razão geral (linguagem agnóstica)

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 matemática com eles), nem sempre você obterá 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 dar um resultado que seja idêntico a uma representação direta de ” conhecida “resposta.

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

  • O R FAQ tem uma pergunta dedicada a ele: R FAQ 7.31
  • O R Inferno de Patrick Burns dedica o primeiro “Círculo” a este problema (a partir da página 9)
  • David Goldberg, “O que todo cientista da computação deve saber sobre aritmética de ponto flutuante”, ACM Computing Surveys 23 , 1 (1991-03), 5-48 doi> 10.1145 / 103162.103163 ( revisão também disponível )
  • O Guia de Ponto-Flutuante – O que todo programador deve saber sobre a aritmética de ponto flutuante
  • 0.30000000000000004.com compara aritmética de ponto flutuante entre linguagens de programação
  • Várias perguntas sobre estouro de pilha, incluindo
    • Por que números de ponto flutuante são imprecisos?
    • Por que os números decimais não podem ser representados exatamente em binário?
    • A matemática do ponto flutuante está quebrada?
    • Duplicado canônico para “ponto flutuante é impreciso” (uma meta discussão sobre uma resposta canônica para esse problema)

Comparando escalares

A solução padrão para isso em R não é usar == , mas sim a function all.equal . Ou melhor, como all.equal fornece muitos detalhes sobre as diferenças, se houver, 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 usar all.equal vez 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 alguns detalhes, copiados diretamente de uma resposta para uma pergunta semelhante :

O problema encontrado é que o ponto flutuante não pode representar frações decimais exatamente na maioria dos casos, o que significa que você frequentemente descobrirá 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 complicada. Se olharmos para eles em binário (bem, hexadecimal, o que é equivalente), temos 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 em 2^-53 , o que é importante porque esse número é a menor diferença representável entre dois números cujo valor é próximo de 1, como é o caso.

Podemos descobrir para qualquer computador o que esse menor número representável está procurando 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 function ‘quase igual’, que verifica 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, ...) #.... 

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

Este algoritmo é 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 há escalares, apenas vetores e vetorização implícita é uma força da linguagem. Para comparar o valor dos vetores elementarmente, os princípios anteriores são válidos, mas a implementação é um pouco diferente. == é vetorizado (faz uma comparação all.equal elementos) enquanto all.equal compara os vetores inteiros como uma entidade única.

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.equal não executa 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 faz um loop sobre os 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 é desejada, pode 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 

Alternativamente, em vez de envolver all.equal em ainda mais chamadas de function, você pode apenas replicar as all.equal.numeric internas relevantes de all.equal.numeric e 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 

Adicionando ao comentário de Brian (que é o motivo) você pode vir mais isso usando all.equal vez disso:

 # 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 

O aviso de Per Joshua aqui é 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 

Isso é hackish, mas rápido:

 if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")