检查数字是否为整数。

144

我很惊讶地发现R没有一个方便的函数来检查数字是否为整数。

is.integer(66) # FALSE

帮助文件警告

is.integer(x) 并不测试 x 是否包含整数! 要测试是否为整数,请使用 round 函数,例如示例中的 is.wholenumber(x)

该示例提供了这个自定义函数作为“解决方法”。

is.wholenumber <- function(x, tol = .Machine$double.eps^0.5)  abs(x - round(x)) < tol
is.wholenumber(1) # is TRUE
如果我需要编写一个用于检查整数的函数,假设我没有阅读上面的评论,我会编写一个类似以下代码的函数:
check.integer <- function(x) {
    x == round(x)
}

我的方法在哪些情况下会失败?如果你处于我假设的境地中,你会采用什么样的方法绕过这些问题?


我希望如果 round(x) 被正确实现,将其应用于整数的结果始终是该整数... - Stephen
1
请查看关于R的常见问题解答:http://cran.r-project.org/doc/FAQ/R-FAQ.html#Why-doesn_0027t-R-think-these-numbers-are-equal_003f - Richie Cotton
5
检查整数(9.0) [1] TRUE 不是。 - Peng Peng
@PengPeng,VitoshKa在被接受的答案中修复了这个问题。 - Roman Luštrik
4
我认为关于整数的数学和计算概念存在混淆。函数 is.integer 检查计算概念,而 check.integer 用户函数则检查数学角度。 - João Daniel
14个回答

170

另一种选择是检查小数部分:

x%%1==0

或者,如果你想在一定容差范围内进行检查:

min(abs(c(x%%1, x%%1-1))) < tol

1
容差检查建议真的有效吗?x <- 5-1e-8; x%%1得到0.9999999(这意味着如果例如tol==1e-5),那么x不是整数。 - Ben Bolker
@BenBolker 很好的发现,我认为它适用于正向扰动。我已经更改为另一种解决方案应该可以工作。 - James
2
@James,我认为应该是min(abs(c(x%%1, x%%1-1))) < tol而不是abs(min(x%%1, x%%1-1)) < tol,否则你会得到任何整数的FALSE... - Cath
5
as.integer(x) == x 有什么问题吗?它不会像 is.integer(x) 一样拒绝 3 或 3.0,但它会捕捉到 3.1。 - Gabi

39

以下是使用更简单的函数和没有任何 hack 的解决方案:

all.equal(a, as.integer(a))

此外,如果您愿意,您可以一次测试整个向量。以下是一个函数:

testInteger <- function(x){
  test <- all.equal(x, as.integer(x), check.attributes = FALSE)
  if(test == TRUE){ return(TRUE) }
  else { return(FALSE) }
}

对于向量、矩阵等,您可以将其更改为使用*apply


12
最后的if else可以简单地用isTRUE(test)代替。实际上,这就是你需要替换if else语句和return语句的全部内容。因为在R中,自动返回最后一个表达式的结果。 - Gavin Simpson
8
testInteger(1.0000001) [1] FALSEtestInteger(1.00000001) [1] TRUE - PatrickT
3
all(a == as.integer(a))可以解决这个问题! - Alex
这个不正常!请看下面的反例:frac_test <- 1/(1-0.98),all.equal(frac_test, as.integer(frac_test)),isTRUE(all.equal(frac_test, as.integer(frac_test))) - tstudio

11

这里提供一种看起来可靠的方法:

check.integer <- function(N){
    !grepl("[^[:digit:]]", format(N,  digits = 20, scientific = FALSE))
}

check.integer(3243)
#TRUE
check.integer(3243.34)
#FALSE
check.integer("sdfds")
#FALSE

这种解决方案还允许科学计数法中的整数:

> check.integer(222e3)
[1] TRUE

1
这对我来说看起来不太可靠:check.integer(1e4)是TRUE,而check.integer(1e5)是FALSE。 - wch
6
这比 is.wholenumber 或其他答案中提供的解决方案更糟糕。以下两者结果不应该不同:check.integer(1e22); check.integer(1e23)。你可以显然地改变正则表达式来解决这个问题,但这种方法是可怕的。(此评论来自installr包的归属声明。) - Joshua Ulrich
1
@PatrickT,我明白了。这是默认的数字参数。请使用format(40, scientific = FALSE, digits = 20)代替。我已经更新了答案。感谢您的指出。 - VitoshKa
1
@PatrickT 你处于机器相关的舍入误差领域。在这方面,我的解决方案与被接受的方案相同 1.0000000000000001 == 1L [1] TRUE。但是,如果你已经得到一个字符串形式的数字,我的解决方案更好 check.integer("1000000000000000000000000000000000001") [1] TRUE - VitoshKa
5
@VitoshKa,我喜欢你的回答!不过有一个地方你忽略了,没有小数点的负数也是整数 ;) 我相应地修改了你的代码。 - Mehrad Mahmoudian
显示剩余9条评论

11

阅读R语言文档,as.integer 更多的与数字的存储方式有关,而不是是否实际等同于整数。 is.integer 用于测试数字是否被声明为整数。你可以通过在数字后面加上 L 来声明一个整数。

> is.integer(66L)
[1] TRUE
> is.integer(66)
[1] FALSE

同样像round这样的函数会返回一个声明的整数,这就是你使用x==round(x)的原因。但这种方法的问题在于你认为什么是实际上的整数。该示例在测试等价性时使用了较少的精度。

> is.wholenumber(1+2^-50)
[1] TRUE
> check.integer(1+2^-50)
[1] FALSE

所以,根据您的应用程序,您可能会遇到这样的麻烦。


1
第二行应该是 "is.integer 测试数字是否被声明为整数",而不是 "as.integer"。只需要更正一个字符,所以我无法轻易地更改它。 - PeterVermont

9

看起来您似乎认为不需要加入一些错误容忍度。如果所有整数都以整数形式输入,那么不需要这样做,但是有时它们作为算术运算的结果而失去了一些精度。例如:

> 2/49*49
[1] 2
> check.integer(2/49*49)
[1] FALSE 
> is.wholenumber(2/49*49)
[1] TRUE

请注意,这并不是R的弱点,所有计算机软件都有一定的精度限制。

3
以防有些人不理解这里发生了什么... 如果你输入as.integer(2/49*49),你会得到1!![顺便说一句,令人沮丧的是R没有将初始计算结果呈现为2.0来表示该值具有一些小数部分) 参见...https://dev59.com/JHI_5IYBdhLWcg3wEe5u#3398868] - John

6
您可以使用简单的if条件,例如:
if(round(var) != var)­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­

6

来自 Hmisc::spss.get

all(floor(x) == x, na.rm = TRUE)

我认为这是一种更安全的选择,因为它“绕过”了机器精度问题。如果尝试is.integer(floor(1)),会得到FALSE。顺便说一下,如果整数大于默认值2147483647,则不会保存为整数,因此要么改变integer.max值,要么使用替代检查...


1
如果 x <- sqrt(2)^2,那么 all(floor(x) == x, na.rm = TRUE) 返回 FALSE - Corrado

1

1

[更新] ==============================================================

尊重下面的[旧]答案,我发现它之所以有效是因为我将所有数字放在一个单独的原子向量中;其中一个是字符,因此每个数字都变成了字符。

如果我们使用列表(因此不进行强制转换),则所有测试都正确通过,但有一个测试未通过:1/(1 - 0.98),它仍然是一个numeric。这是因为tol参数默认为100 * .Machine$double.eps,而该数字远远小于50的两倍。因此,对于这种类型的数字,我们必须决定我们的公差!

因此,如果您想让所有测试变为TRUE,可以使用assertive::is_whole_number(x, tol = 200 * .Machine$double.eps)

无论如何,我确认IMO assertive仍然是最佳解决方案。

以下是此[更新]的reprex。

expect_trues_c <- c(
  cl = sqrt(2)^2,
  pp = 9.0,
  t = 1 / (1 - 0.98),
  ar0 = 66L,
  ar1 = 66,
  ar2 = 1 + 2^-50,
  v = 222e3,
  w1 = 1e4,
  w2 = 1e5,
  v2 = "1000000000000000000000000000000000001",
  an = 2 / 49 * 49,
  ju1 = 1e22,
  ju2 = 1e24,
  al = floor(1),
  v5 = 1.0000000000000001 # this is under machine precision!
)

str(expect_trues_c)
#>  Named chr [1:15] "2" "9" "50" "66" "66" "1" "222000" "10000" "1e+05" ...
#>  - attr(*, "names")= chr [1:15] "cl" "pp" "t" "ar0" ...
assertive::is_whole_number(expect_trues_c)
#> Warning: Coercing expect_trues_c to class 'numeric'.
#>                      2                      9                     50 
#>                   TRUE                   TRUE                   TRUE 
#>                     66                     66                      1 
#>                   TRUE                   TRUE                   TRUE 
#>                 222000                  10000                 100000 
#>                   TRUE                   TRUE                   TRUE 
#>                  1e+36                      2                  1e+22 
#>                   TRUE                   TRUE                   TRUE 
#> 9.9999999999999998e+23                      1                      1 
#>                   TRUE                   TRUE                   TRUE



expect_trues_l <- list(
  cl = sqrt(2)^2,
  pp = 9.0,
  t = 1 / (1 - 0.98),
  ar0 = 66L,
  ar1 = 66,
  ar2 = 1 + 2^-50,
  v = 222e3,
  w1 = 1e4,
  w2 = 1e5,
  v2 = "1000000000000000000000000000000000001",
  an = 2 / 49 * 49,
  ju1 = 1e22,
  ju2 = 1e24,
  al = floor(1),
  v5 = 1.0000000000000001 # this is under machine precision!
)

str(expect_trues_l)
#> List of 15
#>  $ cl : num 2
#>  $ pp : num 9
#>  $ t  : num 50
#>  $ ar0: int 66
#>  $ ar1: num 66
#>  $ ar2: num 1
#>  $ v  : num 222000
#>  $ w1 : num 10000
#>  $ w2 : num 1e+05
#>  $ v2 : chr "1000000000000000000000000000000000001"
#>  $ an : num 2
#>  $ ju1: num 1e+22
#>  $ ju2: num 1e+24
#>  $ al : num 1
#>  $ v5 : num 1
assertive::is_whole_number(expect_trues_l)
#> Warning: Coercing expect_trues_l to class 'numeric'.
#> There was 1 failure:
#>   Position              Value      Cause
#> 1        3 49.999999999999957 fractional
assertive::is_whole_number(expect_trues_l, tol = 200 * .Machine$double.eps)
#> Warning: Coercing expect_trues_l to class 'numeric'.
#>     2.0000000000000004                      9     49.999999999999957 
#>                   TRUE                   TRUE                   TRUE 
#>                     66                     66     1.0000000000000009 
#>                   TRUE                   TRUE                   TRUE 
#>                 222000                  10000                 100000 
#>                   TRUE                   TRUE                   TRUE 
#>                  1e+36     1.9999999999999998                  1e+22 
#>                   TRUE                   TRUE                   TRUE 
#> 9.9999999999999998e+23                      1                      1 
#>                   TRUE                   TRUE                   TRUE



expect_falses <- list(
  bb = 5 - 1e-8,
  pt1 = 1.0000001,
  pt2 = 1.00000001,
  v3 = 3243.34,
  v4 = "sdfds"
)

str(expect_falses)
#> List of 5
#>  $ bb : num 5
#>  $ pt1: num 1
#>  $ pt2: num 1
#>  $ v3 : num 3243
#>  $ v4 : chr "sdfds"
assertive::is_whole_number(expect_falses)
#> Warning: Coercing expect_falses to class 'numeric'.
#> Warning in as.this_class(x): NAs introduced by coercion
#> There were 5 failures:
#>   Position              Value      Cause
#> 1        1 4.9999999900000001 fractional
#> 2        2 1.0000001000000001 fractional
#> 3        3 1.0000000099999999 fractional
#> 4        4 3243.3400000000001 fractional
#> 5        5               <NA>    missing
assertive::is_whole_number(expect_falses, tol = 200 * .Machine$double.eps)
#> Warning: Coercing expect_falses to class 'numeric'.

#> Warning: NAs introduced by coercion
#> There were 5 failures:
#>   Position              Value      Cause
#> 1        1 4.9999999900000001 fractional
#> 2        2 1.0000001000000001 fractional
#> 3        3 1.0000000099999999 fractional
#> 4        4 3243.3400000000001 fractional
#> 5        5               <NA>    missing

此文档由reprex package (v0.3.0)于2019年7月23日创建

[旧版] =================================================================

在我看来,最佳解决方案来自assertive包(目前为止,该包已解决了本主题中所有的正面和负面示例):

are_all_whole_numbers <- function(x) {
  all(assertive::is_whole_number(x), na.rm = TRUE)
}

are_all_whole_numbers(c(
  cl = sqrt(2)^2,
  pp = 9.0,
  t = 1 / (1 - 0.98),
  ar0 = 66L,
  ar1 = 66,
  ar2 = 1 + 2^-50,
  v = 222e3,
  w1 = 1e4,
  w2 = 1e5,
  v2 = "1000000000000000000000000000000000001",
  an = 2 / 49 * 49,
  ju1 = 1e22,
  ju2 = 1e24,
  al = floor(1),
  v5 = 1.0000000000000001 # difference is under machine precision!
))
#> Warning: Coercing x to class 'numeric'.
#> [1] TRUE

are_all_not_whole_numbers <- function(x) {
  all(!assertive::is_whole_number(x), na.rm = TRUE)
}

are_all_not_whole_numbers(c(
  bb = 5 - 1e-8,
  pt1 = 1.0000001,
  pt2 = 1.00000001,
  v3 = 3243.34,
  v4 = "sdfds"
))
#> Warning: Coercing x to class 'numeric'.
#> Warning in as.this_class(x): NAs introduced by coercion
#> [1] TRUE

这段内容是由reprex package (v0.3.0)于2019年7月23日创建的


1
在R中,可以使用class函数来确定数字是numeric还是integer类型。通常,所有数字都被存储为numeric类型,如果要将一个数字显式定义为integer类型,需要在数字后面指定“L”。
例子:
x <- 1 class(x)
[1] "numeric"
x <- 1L class(x)
[1] "integer"
希望这正是您所需要的。谢谢 :)

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