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

273

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
fuente
77
Consulte también stackoverflow.com/q/6874867 y stackoverflow.com/q/2769510 . El R Inferno es también otra gran lectura.
Aaron dejó Stack Overflow el
1
Preguntas y respuestas independientes del lenguaje en todo el sitio: ¿Están dañadas las matemáticas de coma flotante?
Gregor Thomas
dplanet, agregué una solución para todos los casos de comparación ("<=", "> =", "=") en la aritmética de doble precisión a continuación. Espero eso ayude.
Erdogan CEVHER

Respuestas:

355

Razón general (agnóstico del lenguaje)

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:

Comparación de escalares

La solución estándar para esto Res no usar ==, sino la all.equalfunción. O mejor dicho, ya que all.equalda 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.equallugar 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.

Comparación de vectores

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.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 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.equalaún más llamadas a funciones, puede replicar las partes internas 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 usarlo ==, ya que tiene una tolerancia incorporada

dplyr::near(a, b)
#[1]  TRUE  TRUE  TRUE FALSE
Brian Diggs
fuente
R es un entorno de software libre para computación estadística?
kittygirl
41

Agregando al comentario de Brian (que es la razón) puedes superar esto usando en su all.equallugar:

# 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
Tyler Rinker
fuente
17
all.equalno regresa FALSEcuando hay diferencias, por lo que debe envolverlo isTRUEcuando lo usa en una ifdeclaración.
Joshua Ulrich
12

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")
Hillary Sanders
fuente
2
Pero puedes usar el all.equal(... tolerance)parámetro. all.equal(0.147, 0.15, tolerance=0.05)es verdad.
smci
10

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
fuente
0

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
Elias EstatisticsEU
fuente
0

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
Erdogan CEVHER
fuente