2012-02-29 3 views
197

Следующий код, очевидно, неверен. В чем проблема?Почему эти цифры не равны?

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

Смотрите также HTTP: //stackoverflow.com/q/6874867 и http://stackoverflow.com/q/2769510. [R Inferno] (http://www.burns-stat.com/pages/Tutor/R_inferno.pdf) также является отличным чтением. – Aaron

ответ

261

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

Поскольку не все числа могут быть представлены именно в IEEE floating point arithmetic (стандарт, что практически все компьютеры используют для представления десятичных чисел и делать математику с ними), вы не всегда будете получить то, что вы ожидали. Это особенно верно, потому что некоторые значения, которые являются простыми, конечными десятичными знаками (такими как 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 

Некоторые более подробно, непосредственно скопированные из answer to a similar question:

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

в то время как 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, как это.

Мы можем найти для любого данного компьютера, что это наименьшее представляемое число, посмотрев в 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." 
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 
32

Добавление комментария Брайана (который является причиной), вы можете прийти через это нами ING 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 

предупреждение Per Джошуа здесь обновленный код (Спасибо 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

Я пропустил ссылку Брайана, которая объясняет мой ответ лаконично. –

+14

'all.equal' не возвращает' FALSE', когда есть различия, поэтому вам нужно обернуть его 'isTRUE' при использовании его в' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' ''. –

7

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

if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15") 
Смежные вопросы