2012-02-29 10 views
197

El siguiente código es obviamente incorrecto. ¿Cuál es el problema?¿Por qué estos números no son iguales?

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 
+5

Ver también http: //stackoverflow.com/q/6874867 y http://stackoverflow.com/q/2769510. El [R Inferno] (http://www.burns-stat.com/pages/Tutor/R_inferno.pdf) es también otra gran lectura. – Aaron

Respuesta

261

general (agnóstico idioma) razón

Dado que no todos los números pueden representarse exactamente en IEEE floating point arithmetic (el estándar que casi todos los equipos utilizan para representar los números decimales y hacer operaciones matemáticas con ellos), a mí no siempre consigue lo que esperabas 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".

Ésta es una limitación conocida de la aritmética ordenador y se discute en varios lugares:

escalares Comparando

la solución estándar a esto en R es no utilizar ==, sino más bien 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") 

produce

i equals 0.15 

Algunos ejemplos más de la utilización de all.equal en lugar de == (el último ejemplo se supone que debe demostrar 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 

algo más de detalle, directamente copiado de un answer to a similar question:

El problema encontrado es que coma flotante no puede representar fracciones decimales exactamente en la mayoría de los casos, lo que significa que con frecuencia se encuentra que coincidencias exactas fallan.

mientras que R se encuentra ligeramente cuando dicen:

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

puede averiguar lo que realmente piensa en decimal:

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

Se puede ver estos números son diferentes, pero la representación es una un poco difícil de manejar. Si nos fijamos en ellos en binario (bueno, hexagonal, 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" 

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

Podemos encontrar fuera para cualquier equipo dado que este número representable más pequeño es mirando en machine campo 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 un 'casi igual' función que comprueba que la diferencia está cerca del número representable más pequeño 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, ...) 
#.... 

Por lo tanto la función all.equal es en realidad la comprobación de que la diferencia entre los números es la raíz cuadrada de la diferencia más pequeña entre dos mantisas.

Este algoritmo es un poco gracioso cerca de números extremadamente pequeños llamados denormales, pero no tiene que preocuparse por eso.

vectores Comparando

La discusión anterior asume 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 (realiza una comparación de elementos) mientras que 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 la "espera" número y all.equal no realiza elemento a elemento

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

Más bien, una versión que los bucles a través de los dos vectores debe ser utilizado

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

Si un funcional se desea versión de este, se puede escribir

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

que puede ser llamado simplemente como

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

Como alternativa, en lugar de envolver all.equal en aún más las llamadas de función, sólo puede replicar las partes internas pertinentes de all.equal.numeric y utilizar 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 
32

Adición al comentario de Brian (que es la razón) que puede venir sobre esto por nosotros ing 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 

advertencia de por 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 
+0

Eché de menos el enlace de Brian, lo que explica mi respuesta de manera sucinta. –

+14

'all.equal' no devuelve' FALSE' cuando hay diferencias, por lo que debe envolverlo con 'isTRUE' cuando lo use en una instrucción' if'. –

7

Ésta es hacker, pero rápido:

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