Числовое сравнение сложности в R - PullRequest
37 голосов
/ 05 мая 2010

Я пытаюсь сравнить два числа в R как часть условия оператора if:

(a-b) >= 0.5

В данном конкретном случае a = 0,58 и b = 0,08 ... и все же (a-b) >= 0.5 ложно. Я знаю об опасностях использования == для сравнения точных чисел, и это кажется связанным:

(a - b) == 0.5) ложно, а

all.equal((a - b), 0.5) верно.

Единственное решение, которое я могу придумать, - это иметь два условия: (a-b) > 0.5 | all.equal((a-b), 0.5). Это работает, но действительно ли это единственное решение? Должен ли я навсегда отказаться от семейства операторов сравнения =? 1018 *

Редактировать для ясности: Я знаю, что это проблема с плавающей запятой. По сути, я спрашиваю: что мне с этим делать? Какой разумный способ справиться со сравнениями, превышающими или равными сравнениям в R, поскольку >= нельзя доверять?

Ответы [ 7 ]

36 голосов
/ 05 мая 2010

Я никогда не был фанатом all.equal за такие вещи. Мне кажется, что толерантность иногда работает таинственным образом. Почему бы просто не проверить что-то большее, чем допуск менее 0,05

tol = 1e-5

(a-b) >= (0.05-tol)

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

Если x == y, то x-y == 0. Возможно x-y не совсем 0, поэтому для таких случаев я использую

abs(x-y) <= tol

Вы все равно должны установить допуск для all.equal, и это более компактно и просто, чем all.equal.

11 голосов
/ 05 мая 2010

Вы можете создать это как отдельный оператор или переписать исходную функцию> = (вероятно, не очень хорошая идея), если вы хотите часто использовать этот подход:

# using a tolerance
epsilon <- 1e-10 # set this as a global setting
`%>=%` <- function(x, y) (x + epsilon > y)

# as a new operator with the original approach
`%>=%` <- function(x, y) (all.equal(x, y)==TRUE | (x > y))

# overwriting R's version (not advised)
`>=` <- function(x, y) (isTRUE(all.equal(x, y)) | (x > y))

> (a-b) >= 0.5
[1] TRUE
> c(1,3,5) >= 2:4
[1] FALSE FALSE  TRUE
7 голосов
/ 05 мая 2010

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

round(0.58 - 0.08, 2) == 0.5
3 голосов
/ 05 мая 2010

Выберите уровень допуска:

epsilon <- 1e-10

Тогда используйте

(a-b+epsilon) >= 0.5
2 голосов
/ 06 мая 2010

Но, если вы все равно используете допуски, почему вы заботитесь о том, чтобы a-b == .5 (на самом деле) не оценивалось? Если вы все равно используете допуски, вы говорите, что меня не волнуют конечные точки.

Вот что правда если ((a-b)> = .5) если ((a-b) <.5) </p>

один из них всегда должен оцениваться как true на каждой паре двойников. Любой код, который использует один неявно, определяет, по крайней мере, операцию no для другого. Если вы используете допуски для фактического .5, включенного в первый, но ваша проблема определена в непрерывном домене, вы не добились многого. В большинстве задач, связанных с непрерывными значениями в основной задаче, будет очень мало смысла в этом, поскольку значения, произвольно превышающие .5, всегда будут оцениваться так, как должны. Значения, произвольно близкие к .5, перейдут к «неправильному» управлению потоком, но в постоянных задачах, когда вы используете соответствующую точность, которая не имеет значения.

Допуски имеют смысл только тогда, когда вы имеете дело с проблемами типа если ((a-b) == c) if ((a-b)! = c)

Здесь никакая «подходящая точность» не может вам помочь. Причина в том, что вы должны быть готовы к тому, что второе значение всегда будет иметь значение true, если вы вручную не установите биты a-b на очень низком уровне, хотя на самом деле вы, вероятно, хотите, чтобы первое значение иногда было истинным.

1 голос
/ 03 мая 2019
Сравнения

<= и >= не зависят от языка, если числовая сложность возрастает в числах с плавающей запятой.

IsSmallerOrEqual <- function(a,b) {   # To check a <= b
# Check whether "Mean relative difference..." exist in all.equal's result; 
# 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; To check |-2-(-2.2)| <= 0.2
IsSmallerOrEqual(abs(-2-(-2.2)), 0.3) # TRUE
IsSmallerOrEqual(abs(-2-(-2.2)), 0.1) # FALSE

IsBiggerOrEqual  <- function(a,b) {   # To check a >= b
# Check whether "Mean relative difference..." exist in all.equal's result; 
# 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) # TRUE
IsBiggerOrEqual(4,3) # TRUE
IsBiggerOrEqual(3,4) # FALSE
IsBiggerOrEqual(0.58 - 0.08,0.5)  # TRUE

Если all.equal не обработан, мы можем столкнуться с ошибками.

Следующее необязательно, но полезно:

abs(-2-(-2.2)) # 0.2

sprintf("%.54f",abs(-2-(-2.2)))  # "0.200000000000000177635683940025046467781066894531250000"
sprintf("%.54f",0.2)             # "0.200000000000000011102230246251565404236316680908203125"

all.equal(abs(-2-(-2.2)), 0.2)  # TRUE; check nearly equivalence of floating point numbers
identical(abs(-2-(-2.2)), 0.2)  # FALSE; check exact equivalence of floating point numbers
1 голос
/ 19 апреля 2017

Еще один комментарий. all.equal является универсальным. Для числовых значений используется all.equal.numeric. Проверка этой функции показывает, что она использовала .Machine$double.eps^0.5, где .Machine$double.eps определяется как

double.eps: the smallest positive floating-point number ‘x’ such that
          ‘1 + x != 1’.  It equals ‘double.base ^ ulp.digits’ if either
          ‘double.base’ is 2 or ‘double.rounding’ is 0; otherwise, it
          is ‘(double.base ^ double.ulp.digits) / 2’.  Normally
          ‘2.220446e-16’.

(. Страница руководства к машине).

Другими словами, это был бы приемлемый выбор для вашей терпимости:

myeq <- function(a, b, tol=.Machine$double.eps^0.5)
      abs(a - b) <= tol
...