studio - superponer graficas en r
¿Por qué estos números no son iguales? (4)
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
Razón general (idioma agnóstico)
Como no todos los números pueden representarse 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 decimales finitos simples (como 0.1 y 0.05) no se representan exactamente en la computadora y, por lo tanto, los resultados de la aritmética en ellos pueden no dar un resultado que sea idéntico a una representación directa de " respuesta conocida.
Esta es una limitación bien conocida de la aritmética computacional y se discute en varios lugares:
- La R Preguntas frecuentes tiene una pregunta dedicada: R Preguntas frecuentes 7.31
- El Inferno R de Patrick Burns dedica el primer "Círculo" a este problema (a partir de la página 9)
- David Goldberg, "Lo que todo científico informático debería saber sobre la aritmética de punto flotante", ACM Computing Surveys 23 , 1 (1991-03), 5-48 doi>10.1145/103162.103163 ( revisión también disponible )
- La guía de punto flotante: lo que todo programador debe saber sobre la aritmética de punto flotante
- 0.30000000000000004.com compara la aritmética de punto flotante con los lenguajes de programación
- Varias preguntas de desbordamiento de pila incluyendo
- ¿Por qué los números de punto flotante son inexactos?
- ¿Por qué los números decimales no se pueden representar exactamente en binario?
- ¿Es rota la matemática de punto flotante?
- El duplicado canónico para "punto flotante es inexacto" (una discusión meta sobre una respuesta canónica para este problema)
Comparando los escalares
La solución estándar para esto en R
es no usar ==
, sino la función all.equal
. O mejor dicho, ya que all.equal
da muchos detalles sobre las diferencias, si existen, 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 del uso de 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, directamente copiados 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 averiguar lo que realmente piensa en decimal:
sprintf("%.54f",1.1-0.2)
#[1] "0.900000000000000133226762955018784850835800170898437500"
sprintf("%.54f",0.9)
#[1] "0.900000000000000022204460492503130808472633361816406250"
Puedes 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, tal como es.
Podemos averiguar para cualquier computadora determinada cuál es el número representable más pequeño buscando en el campo de la machine 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 utilizar este hecho para crear una función "casi igual a" que verifica que la diferencia esté cerca del número más pequeño representable en punto 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, ...)
#....
Así que 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 se vuelve un poco raro cerca de números extremadamente pequeños llamados denormales, pero no debes preocuparte por eso.
Comparando vectores
La discusión anterior asumió una comparación de dos valores únicos. 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 de forma elemental, los principios anteriores se mantienen, pero la implementación es ligeramente diferente. ==
se vectoriza (hace una comparación de elementos) mientras que all.equal
compara los vectores completos como una entidad única.
Utilizando 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 se realiza de forma elemental
a==b
#[1] FALSE FALSE FALSE FALSE
all.equal(a,b)
#[1] "Mean relative difference: 0.01234568"
isTRUE(all.equal(a,b))
#[1] FALSE
Más bien, se debe usar una versión que recorra 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 puede ser llamado como justo
elementwise.all.equal(a, b)
#[1] TRUE TRUE TRUE FALSE
Alternativamente, en lugar de envolver 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 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
Agregando al comentario de Brian (que es la razón) puede superar esto utilizando all.equal
en all.equal
lugar:
# 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")
dplyr::near()
es una opción para probar si dos vectores de números de punto flotante son iguales. Este es el ejemplo de la docs :
sqrt(2) ^ 2 == 2
#> [1] FALSE
library(dplyr)
near(sqrt(2) ^ 2, 2)
#> [1] TRUE
La función tiene un parámetro de tolerancia incorporado: tol = .Machine$double.eps^0.5
que se puede ajustar. El parámetro predeterminado es el mismo que el predeterminado para all.equal()
.