Почему эти цифры не равны?

243

Следующий код явно ошибочен. В чем проблема?

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
Теги:
floating-point
floating-accuracy
r-faq

5 ответов

325
Лучший ответ

Общая (языковая агностика) причина

Поскольку не все числа могут быть представлены точно в IEEE с плавающей точкой арифметики (стандарт, который почти все компьютеры используют для представления десятичных чисел и делает математику с ними), вы не всегда получите то, что ожидаете. Это особенно верно, потому что некоторые значения, которые являются простыми, конечными десятичными знаками (такими как 0,1 и 0,05), не представлены точно в компьютере, и поэтому результаты арифметики на них могут не дать результата, который идентичен прямому представлению "известный" ответ.

Это хорошо известное ограничение компьютерной арифметики и обсуждается в нескольких местах:

Сравнение скаляров

Стандартное решение для этого в R заключается не в использовании ==, а all.equal. Вернее, поскольку all.equal дает много подробностей о различиях, если они есть, isTRUE(all.equal(...)).

if(isTRUE(all.equal(i,0.15))) cat("i equals 0.15") else cat("i does not equal 0.15")

дает

i equals 0.15

Еще несколько примеров использования all.equal вместо == (последний пример должен показать, что это будет правильно показывать различия).

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

Более подробная информация, непосредственно скопированная из ответа на аналогичный вопрос:

Проблема, с которой вы столкнулись, заключается в том, что плавающая точка не может представлять десятичные дроби точно в большинстве случаев, что означает, что вы часто обнаружите, что точные совпадения терпят неудачу.

в то время как R слегка лежит, когда вы говорите:

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

Вы можете узнать, что он на самом деле думает в десятичной форме:

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

Вы можете видеть, что эти цифры разные, но представление немного громоздко. Если мы посмотрим на них в двоичном (ну, шестнадцатеричный, что эквивалентно), мы получим более четкое изображение:

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"

Вы можете видеть, что они отличаются 2^-53, что важно, потому что это число является наименьшей представимой разницей между двумя числами, значение которых близко к 1, так как это.

Мы можем узнать для любого данного компьютера, что это наименьшее представимое число, посмотрев в R machine поле:

 ?.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"

Этот факт можно использовать для создания функции "почти равных", которая проверяет, что разница близка к наименьшему представимому числу в плавающей запятой. На самом деле это уже существует: 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, ...)
#....

Таким образом, функция all.equal фактически проверяет, что разница между числами является квадратным корнем из наименьшей разницы между двумя мантиссами.

Этот алгоритм выглядит немного смешно вблизи чрезвычайно маленьких чисел, называемых денормалами, но вам не нужно об этом беспокоиться.

Сравнение векторов

Вышеприведенное обсуждение предполагало сравнение двух одиночных значений. В R нет скаляров, просто векторы и неявная векторизация являются силой языка. Для сравнения значений векторов по-прежнему соблюдаются предыдущие принципы, но реализация несколько отличается. == векторизован (сравнивается по элементам), а all.equal сравнивает все векторы как единую сущность.

Использование предыдущих примеров

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)

== не дает ожидаемого результата, а all.equal не выполняет элементарные

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

Скорее, следует использовать версию, которая пересекает два вектора

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

Если требуется функциональная версия, ее можно записать

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

который можно назвать просто

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

В качестве альтернативы вместо того, чтобы обернуть all.equal еще больше вызовов функций, вы можете просто реплицировать соответствующие внутренние элементы all.equal.numeric и использовать неявную векторию:

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
36

Добавляя к комментарию Брайана (что является причиной), вы можете это сделать, используя all.equal вместо:

# 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

В ответ на 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
    Я пропустил ссылку Брайана, которая кратко объясняет мой ответ.
  • 15
    all.equal не возвращает FALSE при наличии различий, поэтому вам нужно isTRUE его в isTRUE при использовании его в операторе if .
9

Это хаки, но быстро:

if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")
  • 2
    Но вы можете использовать параметр all.equal(... tolerance) . all.equal(0.147, 0.15, tolerance=0.05) - TRUE.
1

dplyr::near() - это опция для тестирования, если два вектора чисел с плавающей точкой равны. Это пример из документов:

sqrt(2) ^ 2 == 2
#> [1] FALSE
library(dplyr)
near(sqrt(2) ^ 2, 2)
#> [1] TRUE

Функция имеет встроенный параметр допуска: tol =.Machine$double.eps^0.5 который можно настроить. Параметр по умолчанию такой же, как по умолчанию для all.equal().

0

используйте метод all.equal() для сравнения двух значений в R.

all.equal(x, y) - это утилита для сравнения R объектов x и y, тестирующих "почти равенство". Если они различаются, сравнение все равно выполняется до некоторой степени, и возвращается отчет о различиях. Не используйте all.equal напрямую в выражениях if --- используйте isTRUE (all.equal(....)) или идентичные, если это необходимо.

Например:

all.equal(pi, 355/113)
# not precise enough (default tol) > relative error

d45 <- pi*(1/4 + 1:10)
stopifnot(
all.equal(tan(d45), rep(1, 10)))          # TRUE, but
all      (tan(d45) == rep(1, 10))         # FALSE, since not exactly
all.equal(tan(d45), rep(1, 10), tolerance = 0)  # to see difference

## advanced: equality of environments
ae <- all.equal(as.environment("package:stats"),
                asNamespace("stats"))
stopifnot(is.character(ae), length(ae) > 10,
          ## were incorrectly "considered equal" in R <= 3.1.1
          all.equal(asNamespace("stats"), asNamespace("stats")))

Ещё вопросы

Сообщество Overcoder
Наверх
Меню