¿Por qué estos números no son iguales?

El siguiente código es obviamente incorrecto. ¿Cuál es el 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 

Motivo general (idioma agnóstico)

Como no todos los números se pueden representar exactamente en la aritmética de punto flotante IEEE (el estándar que casi todas las computadoras usan para representar números decimales y hacer cálculos matemáticos con ellos), no siempre obtendrá lo que esperaba. Esto es especialmente cierto porque algunos valores que son simples, decimales finitos (como 0.1 y 0.05) no están representados exactamente en la computadora y, por lo tanto, los resultados de la aritmética sobre ellos pueden no dar un resultado que sea idéntico a una representación directa de la ” conocida “respuesta”.

Esta es una limitación bien conocida de la aritmética computacional y se discute en varios lugares:

  • La pregunta frecuente R tiene una pregunta dedicada a ella: R Preguntas frecuentes 7.31
  • El R Inferno de Patrick Burns dedica el primer “círculo” a este problema (a partir de la página 9)
  • David Goldberg, “Lo que todo informático debe saber sobre la aritmética de coma flotante”, Encuestas de computación de ACM 23 , 1 (1991-03), 5-48 doi> 10.1145 / 103162.103163 ( revisión también disponible )
  • La guía de puntos flotantes: lo que todo progtwigdor debería saber sobre la aritmética de coma flotante
  • 0.30000000000000004.com compara la aritmética de coma flotante en los lenguajes de progtwigción
  • Varias preguntas de desbordamiento de stack que incluyen
    • ¿Por qué los números de coma flotante son inexactos?
    • ¿Por qué los números decimales no pueden representarse exactamente en binario?
    • ¿Las matemáticas de punto flotante están rotas?
    • El duplicado canónico para “coma flotante es inexacto” (una meta discusión sobre una respuesta canónica para este problema)

Comparando escalares

La solución estándar para esto en R no es usar == , sino la función all.equal . O más bien, dado que all.equal proporciona muchos detalles sobre las diferencias si las hay, isTRUE(all.equal(...)) .

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

rendimientos

 i equals 0.15 

Algunos ejemplos más de usar all.equal lugar de == (se supone que el último ejemplo muestra que esto mostrará correctamente las diferencias).

 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 

Algunos detalles más, copiados directamente de una respuesta a una pregunta similar :

El problema que ha encontrado es que el punto flotante no puede representar fracciones decimales exactamente en la mayoría de los casos, lo que significa que con frecuencia encontrará que las coincidencias exactas fallan.

mientras que R miente ligeramente cuando dices:

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

Puedes descubrir lo que realmente piensa en decimal:

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

Puede ver que estos números son diferentes, pero la representación es un poco difícil de manejar. Si los vemos en binario (bueno, hexadecimal, que es equivalente) obtenemos una imagen más 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" 

Puede ver que difieren en 2^-53 , lo cual es importante porque este número es la diferencia representable más pequeña entre dos números cuyo valor es cercano a 1, como es esto.

Podemos averiguar para cualquier computadora cuál es el número representable más pequeño al buscar en el campo de la 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" 

Puede usar este hecho para crear una función ‘casi igual’ que verifique que la diferencia esté cerca del número representable más pequeño en coma flotante. De hecho, esto ya 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, ...) #.... 

Entonces, la función all.equal en realidad está comprobando que la diferencia entre los números es la raíz cuadrada de la diferencia más pequeña entre dos mantisas.

Este algoritmo resulta un poco divertido cerca de números extremadamente pequeños llamados denormales, pero no debes preocuparte por eso.

Comparando vectores

La discusión anterior supone una comparación de dos valores individuales. En R, no hay escalares, solo vectores y la vectorización implícita es una fortaleza del lenguaje. Para comparar el valor de los vectores a nivel de elemento, los principios anteriores se mantienen, pero la implementación es ligeramente diferente. == está vectorizado (hace una comparación de elementos) mientras que all.equal compara los vectores completos como una entidad única.

Usando los ejemplos 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) 

== no da el resultado "esperado" y all.equal no realiza elemento-sabio

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

Por el contrario, se debe utilizar una versión que pase por los dos vectores

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

Si se desea una versión funcional de esto, se puede escribir

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

que se puede llamar como solo

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

Alternativamente, en lugar de all.equal en aún más llamadas a funciones, puede simplemente replicar las all.equal.numeric internas relevantes de all.equal.numeric y usar la vectorización 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 

Añadiendo al comentario de Brian (que es la razón) puedes superar esto 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 

La advertencia de Per Joshua aquí es el código actualizado (Gracias 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 

Esto es hackish, pero rápido:

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