在R中进行数字比较的困难

52

作为if语句条件的一部分,我尝试在R中比较两个数字:

(a-b) >= 0.5

在这种情况下,a = 0.58,b = 0.08... 但是(a-b) >= 0.5是false。我知道使用==进行精确数字比较的危险性,这似乎有关:

(a - b) == 0.5)是false,而

all.equal((a - b), 0.5)是true。

我唯一想到的解决方案是有两个条件:(a-b) > 0.5 | all.equal((a-b), 0.5)。 这可以工作,但这真的是唯一的解决方案吗?我应该永远放弃=比较运算符家族吗?

编辑以提高清晰度:我知道这是浮点问题。更根本地说,我的问题是:我该怎么办?由于>=不能真正信任,因此在R中处理大于等于比较的最合理方法是什么?


2
基于两个条件 (a-b) > 0.5 | all.equal((a-b), 0.5) 的解决方案在许多情况下是错误的,因此不能/不应该使用:a <- 4.005; b <- 4.002; a-b > 0.5 | all.equal(a-b, 0.5) # Error in a - b > 0.5 | all.equal(a - b, 0.5) : operations are possible only for numeric, logical or complex types。原因是:all.equal 会产生逻辑或字符类型。因此,会出现 "逻辑 | 字符" 类型不兼容的问题。我将展示如何防止这种类型不兼容性。 - Erdogan CEVHER
1
我在下面提供了一个非常简化的解决方案。 - Erdogan CEVHER
8个回答

50

我从来不喜欢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更紧凑、更简单明了。


all.equal 函数有什么问题吗?它使用了一个相当合理的默认值——eps的平方根。是的,有时需要更大的公差,但您也可以指定这些值。 - Josiah Yoder
也许是因为它还检查类型是否匹配?我发现自己使用isTRUE(all.equal(as.numeric(v2), as.numeric(v2))) - Josiah Yoder

15

如果您经常使用此方法,您可以将其创建为单独的运算符或覆盖原始的>=函数(这可能不是一个好主意):

# 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

3
我个人认为这是最佳方案,因为你不需要自己决定 epsilon。甚至可以借鉴 Perl 的做法,给它们起名字比如 gelene - Ken Williams

13

出于完整性的考虑,我要指出,在某些情况下,你可以简单地四舍五入到几位小数(与之前发布的更好的解决方案比起来,这种解决方法有点差劲)。

round(0.58 - 0.08, 2) == 0.5

5
我认为这是最佳解决方案,对于原始问题,我将使用 round(a-b, 10) >= 0.5(10位数字足以支持未来扩展)。 - Marek

8

再补充一点,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’.  Normally2.220446e-16’.

(机器手册页)

换句话说,这将是您容忍的可接受选择:

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

4

选择一些容差级别:

epsilon <- 1e-10

然后使用。
(a-b+epsilon) >= 0.5

3

但是,如果您已经在使用公差值,为什么还要关心a-b == .5(实际上)不会得到评估?如果您已经在使用公差值,那么您是在说我不关心端点。

以下是真相: if( (a-b) >= .5) if( (a-b) < .5)

这两者中的一个应该在每一对双精度数上始终为true。任何使用其中一个的代码都会隐含地定义另一个无操作,至少如此。如果您使用公差值来获得实际的.5并将其包含在第一个数中,但您的问题在连续域上定义,那么您不会有太大的成就感。在涉及基本问题中涉及连续值的大多数问题中,没有这样做的必要,因为超过.5的值总是按照它们应该进行评估。与.5无限接近的值将进入“错误”的流程控制,但在连续问题中,只要您使用适当的精度,这并不重要。

唯一需要使用公差值的时间是处理以下类型的问题时, if( (a-b) == c) if( (a-b) != c)

在这里,“适当的精度”无法帮助您。原因是您必须准备好第二个将始终评估为true,除非您手动设置a-b的位级别非常低的位,而实际上您可能希望第一个有时为true。


2
<最初的回答> 当浮点数的数值难度增加时,<=>=比较不受语言限制。
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

0
如果有人需要的话,我最近开始使用这些辅助工具。
不过我不确定它们是否完全正确,所以如果有任何这些工具可能无法正常工作的例子,请随时提供。
double_equal <- function(x, y, tol = sqrt(.Machine$double.eps)){
  abs(x - y) < tol
}
double_gt <- function(x, y, tol = sqrt(.Machine$double.eps)){
  (x - y) > tol
}
double_gte <- function(x, y, tol = sqrt(.Machine$double.eps)){
  (x - y) > -tol
}
double_lt <- function(x, y, tol = sqrt(.Machine$double.eps)){
  (x - y) < -tol
}
double_lte <- function(x, y, tol = sqrt(.Machine$double.eps)){
  (x - y) < tol
}

x <- sqrt(2)^2
y <- 2
double_gte(x, y)
#> [1] TRUE
double_gte(y, x)
#> [1] TRUE

double_gt(x, y)
#> [1] FALSE
double_gt(y, x)
#> [1] FALSE

double_lte(x, y)
#> [1] TRUE
double_lte(y, x)
#> [1] TRUE

double_lt(x, y)
#> [1] FALSE
double_lt(y, x)
#> [1] FALSE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接