4

Seguramente ya te has encontrado con algún problema como el siguiente:

> (2.3 - 1.8) == 0.5
[1] FALSE
> sqrt(2)^2 == 2
[1] FALSE

La explicación al problema general de manejar números de coma flotante la puedes encontrar aquí: ¿Por qué mis programas no pueden hacer cálculos aritméticos correctamente?.

Esto no es un problema particular de R sino de cualquier lenguaje que maneje números de coma flotante.

Ahora bien, ¿cómo podemos resolver o manejar estas "inconsistencias" en el lenguaje a la hora de realizar comparaciones?

Patricio Moracho
  • 54,367
  • 12
  • 35
  • 68

1 Answers1

4

Primero que nada, la demostración simple del problema:

> sprintf("%.60f",2.3 - 1.8)
[1] "0.499999999999999777955395074968691915273666381835937500000000"
> sprintf("%.60f",sqrt(2)^2)
[1] "2.000000000000000444089209850062616169452667236328125000000000"

Vemos que en el primer caso 2.3 - 1.8 no es exactamente 0.5 y también que sqrt(2)^2 tampoco es exactamente 2. Lo que ocurre es que, para mayor claridad, en R no se muestra una gran cantidad de decimales por defecto:

> 2.3 - 1.8
[1] 0.5

Este problema no es de R, sino que es inherente a la propia arquitectura computacional (Ver: ¿Por qué mis programas no pueden hacer cálculos aritméticos correctamente?), la imposibilidad de representar ciertos números mediante coma flotante. Uno podría pensar que el problema es representar números como 1/3 o Pi, pero no son solo estos, por el algebra binaria, incluso números decimales "más simples" no pueden ser representados de forma exacta, sino que lo que se maneja siempre es una aproximación:

> sprintf("%.60f", 0.5) 
[1] "0.500000000000000000000000000000000000000000000000000000000000"

> sprintf("%.60f", .1) # 0.1 se maneja como una aproximación
[1] "0.100000000000000005551115123125782702118158340454101562500000"

Obviamente solo se ven los decimales más significativos, podemos ver hasta cuantos dígitos significativos se tomarán con getOption("digits") ahora si modificamos esto:

> options(digits=22) 
> 2.3 - 1.8
[1] 0.49999999999999978

Notamos claramente dónde tenemos el problema. ¿Como resolvemos esta inconsistencia?

Comparaciones (igualdad)

Uno de los principales problemas, como ya vimos, es el de comparar dos números dónde al menos uno sea producto de una operación con valores de coma flotante. La forma natural de resolverlo es usando la función all.equal() y combinada con isTRUE()

> isTRUE(all.equal(2.3 - 1.8,0.5))
[1] TRUE

Esto sirve para tratar escalares, o siendo consistentes con el lenguaje, vectores de un solo elemento, sin embargo para vectores con varios elementos, hay que "vectorizar" la anterior solución, por ejemplo:

> 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)
# Ya sabemos que la igualdad habitual no funcionará
> a==b                          
[1] FALSE FALSE FALSE FALSE

# Pero tampoco nos sirve all.equal() 
> isTRUE(all.equal(a,b))
[1] FALSE

# La alternativa es vectorizar all.equal() usando mapply()
> mapply(function(x, y) {isTRUE(all.equal(x, y))}, a, b)
[1]  TRUE  TRUE  TRUE FALSE

> # O mucho mejor creando una función ad-hoc
> elementwise.all.equal <- Vectorize(function(x, y) {isTRUE(all.equal(x, y))})
 
> elementwise.all.equal(a, b)
[1]  TRUE  TRUE  TRUE FALSE

Nota: En caso de usar dplyr contamos con near()

> library(tidyverse)
> near(a,b)
[1]  TRUE  TRUE  TRUE FALSE

Otras comparaciones

Para el resto de las comparaciones, debemos atacar el problema de otra forma. Este sería el problema:

> v <- c(2.3 - 1.8, 0.1 + 0.4, sqrt(0.5)^2)
> v 
[1] 0.5 0.5 0.5

> unique(v)
[1] 0.5 0.5 0.5
> v > 0.5
[1] FALSE FALSE  TRUE

Aquí lo que podemos hacer es usar zapsmall() que no es más que una "wrapper" alrededor de un round() para quitar dígitos innecesarios:

> unique(zapsmall(v, digits=2))
[1] 0.5
> zapsmall(v, digits=2) > 0.5
[1] FALSE FALSE FALSE

Fuentes:

Patricio Moracho
  • 54,367
  • 12
  • 35
  • 68
  • Esto aplica pa todos los lenguajes, debería ser una respuesta de referencia, deberían usarla para marcar todas las demás que haya como duplicadas, amé esta respuesta, etc etc :) – Alfabravo Sep 24 '20 at 16:52