Числовая сложность сравнения в R

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

(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). Это работает, но это действительно единственное решение? Должен ли я просто кляться от семейства операторов сравнения = навсегда?

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

Ответ 1

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

tol = 1e-5

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

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

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

abs(x-y) <= tol

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

Ответ 2

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

# 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 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

Ответ 3

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

round(0.58 - 0.08, 2) == 0.5

Ответ 4

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

epsilon <- 1e-10

Затем используйте

(a-b+epsilon) >= 0.5

Ответ 5

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

Вот что верно если ((a-b) > =.5) если ((a-b) <.5)

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

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

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

Ответ 6

Еще один комментарий. 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

Ответ 7

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

IsSmallerOrEqual <- function(a,b) {   # To check a <= b
# Check whether "Mean relative difference..." exist in all.equal 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 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