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

Resuelto dplanet asked hace 54 años • 7 respuestas

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
dplanet avatar Jan 01 '70 08:01 dplanet
Aceptado

Motivo general (independiente del idioma)

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

Ésta es una limitación bien conocida de la aritmética informática y se analiza en varios lugares:

  • Las preguntas frecuentes de R tienen preguntas dedicadas: Preguntas frecuentes de R 7.31
  • The R Inferno de Patrick Burns dedica el primer "Círculo" a este problema (que comienza en la página 9)
  • David Goldberg, "Lo que todo 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 coma flotante entre lenguajes de programación
  • Varias preguntas de Stack Overflow que incluyen
    • ¿Por qué los números de coma flotante son inexactos?
    • ¿Por qué los números decimales no se pueden representar exactamente en binario?
    • ¿Están rotas las matemáticas de punto flotante?
    • Duplicado canónico para "el punto flotante es inexacto" (una meta discusión sobre una respuesta canónica para este problema)

Comparando escalares

La solución estándar para esto Rno es usar ==, sino la all.equalfunción. O mejor dicho, ya que all.equalbrinda 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 uso all.equalen 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"

Puedes 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"

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

Podemos averiguar para cualquier computadora cuál es este número representable más pequeño mirando 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 utilizar este hecho para crear una función "casi igual" 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, ...)
#....

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

Este algoritmo se vuelve un poco extraño cerca de números extremadamente pequeños llamados denormales, pero no necesitas 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 por elementos, se mantienen los principios anteriores, pero la implementación es ligeramente diferente. ==se vectoriza (hace una comparación de elementos) mientras all.equalcompara 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.equalno funciona por 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

Más bien, se debe utilizar 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.equalaún más llamadas a funciones, puede simplemente replicar los aspectos internos relevantes all.equal.numericy 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 usar ==, porque tiene una tolerancia incorporada.

dplyr::near(a, b)
#[1]  TRUE  TRUE  TRUE FALSE

Prueba de aparición de un valor dentro de un vector

La función R estándar %in%también puede sufrir el mismo problema si se aplica a valores de punto flotante. Por ejemplo:

x = seq(0.85, 0.95, 0.01)
# [1] 0.85 0.86 0.87 0.88 0.89 0.90 0.91 0.92 0.93 0.94 0.95
0.92 %in% x
# [1] FALSE

Podemos definir un nuevo operador infijo para permitir una tolerancia en la comparación de la siguiente manera:

`%.in%` = function(a, b, eps = sqrt(.Machine$double.eps)) {
  any(abs(b-a) <= eps)
}

0.92 %.in% x
# [1] TRUE

dplyr::nearenvuelto en anytambién se puede utilizar para el cheque vectorizado

any(dplyr::near(0.92, x))
# [1] TRUE
Brian Diggs avatar Feb 29 '2012 23:02 Brian Diggs

Agregando al comentario de Brian (que es la razón), puedes superar esto usando all.equalen su 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

Según la advertencia de Joshua, aquí está 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
Tyler Rinker avatar Feb 29 '2012 23:02 Tyler Rinker

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.5que se puede ajustar. El parámetro predeterminado es el mismo que el predeterminado para all.equal().

sbha avatar Nov 09 '2018 03:11 sbha

Esto es un truco, pero rápido:

if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")
Hillary Sanders avatar Sep 07 '2013 01:09 Hillary Sanders

Comparaciones generalizadas ("<=", ">=", "=") en aritmética de doble precisió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
Erdogan CEVHER avatar May 25 '2019 21:05 Erdogan CEVHER