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
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
Respuestas:
Como no todos los números se pueden representar exactamente en aritmética de coma 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 están representados 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 " conocida "respuesta.
Esta es una limitación bien conocida de la aritmética informática y se discute en varios lugares:
La solución estándar para esto R
es no usar ==
, sino la all.equal
función. O mejor dicho, ya que all.equal
da un montón de detalles acerca de 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 uso en 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 levemente 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"
Puede ver que estos números son diferentes, pero la representación es un poco difícil de manejar. Si los miramos 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 lo es.
Podemos averiguar para cualquier computadora cuál es este número representable más pequeño mirando el campo de 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á verificando 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 divertido cerca de números extremadamente pequeños llamados denormales, pero no necesita preocuparse por eso.
La discusión anterior supuso 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 por elementos, se mantienen los principios anteriores, pero la implementación es ligeramente diferente. ==
está vectorizado (hace una comparación por elementos) mientras all.equal
compara los vectores completos como una sola entidad.
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 elementos sabios
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 se puede llamar simplemente
elementwise.all.equal(a, b)
#[1] TRUE TRUE TRUE FALSE
Alternativamente, en lugar de incluir all.equal
aún más llamadas a funciones, puede replicar las partes internas relevantes all.equal.numeric
y utilizar 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
Este es el enfoque adoptado por dplyr::near
, que se documenta como
Esta es una forma segura de comparar si dos vectores de números de coma flotante son (por pares) iguales. Esto es más seguro que usarlo
==
, ya que tiene una tolerancia incorporada
dplyr::near(a, b)
#[1] TRUE TRUE TRUE FALSE
Agregando al comentario de Brian (que es la razón) puedes superar esto usando en su 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 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
all.equal
no regresa FALSE
cuando hay diferencias, por lo que debe envolverlo isTRUE
cuando lo usa en una if
declaración.
Esto es hack, pero rápido:
if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")
all.equal(... tolerance)
parámetro. all.equal(0.147, 0.15, tolerance=0.05)
es verdad.
dplyr::near()
es una opción para probar si dos vectores de números de coma flotante son iguales. Este es el ejemplo de los documentos :
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()
.
Tuve un problema similar. Usé la siguiente solución.
@ Encontré esta solución alternativa sobre intervalos de corte desiguales. @ Utilicé la función redonda en R. Al establecer la opción en 2 dígitos, no resolvió el 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 ))
)
salida de intervalos de corte desiguales basados en opciones (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 ))
)
Salida de intervalos de corte iguales basados en la función de redondeo:
[,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
Comparaciones generalizadas ("<=", "> =", "=") en aritmética de doble preción:
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