142

I was surprised to learn that R doesn't come with a handy function to check if the number is integer.

is.integer(66) # FALSE

The help files warns:

is.integer(x) does not test if x contains integer numbers! For that, use round, as in the function is.wholenumber(x) in the examples.

The example has this custom function as a "workaround"

is.wholenumber <- function(x, tol = .Machine$double.eps^0.5)  abs(x - round(x)) < tol
is.wholenumber(1) # is TRUE

If I would have to write a function to check for integers, assuming I hadn't read the above comments, I would write a function that would go something along the lines of

check.integer <- function(x) {
    x == round(x)
}

Where would my approach fail? What would be your work around if you were in my hypothetical shoes?

zx8754
  • 52,746
  • 12
  • 114
  • 209
Roman Luštrik
  • 69,533
  • 24
  • 154
  • 197
  • I would hope that if `round(x)` is implemented properly, the result of applying it to an integer would always be that integer... – Stephen Aug 13 '10 at 12:39
  • 1
    Take a look at the FAQ on R http://cran.r-project.org/doc/FAQ/R-FAQ.html#Why-doesn_0027t-R-think-these-numbers-are-equal_003f – Richie Cotton Aug 13 '10 at 16:27
  • 5
    > check.integer(9.0) [1] TRUE it's not. – Peng Peng Jul 26 '12 at 01:58
  • @PengPeng, VitoshKa fixed this in the accepted answer. – Roman Luštrik Jul 26 '12 at 06:53
  • 4
    I think there is a confusion about mathematical and computational concepts of integer. The function `is.integer` checks the computational concept, the `check.integer` user function checks the mathematical point of view. – João Daniel Nov 20 '14 at 14:08

14 Answers14

167

Another alternative is to check the fractional part:

x%%1==0

or, if you want to check within a certain tolerance:

min(abs(c(x%%1, x%%1-1))) < tol
Khaynes
  • 1,976
  • 2
  • 15
  • 27
James
  • 65,548
  • 14
  • 155
  • 193
  • 1
    does the tolerance-checking suggestion really work?? `x <- 5-1e-8; x%%1` gives 0.9999999 (which would imply if `tol==1e-5` for example) that `x` is **not** an integer. – Ben Bolker Jan 24 '14 at 15:34
  • @BenBolker Good catch, it works for positive perturbations I think. I've changed it to an alternative solution should work. – James Jan 24 '14 at 16:23
  • 2
    @James, I think it should be `min(abs(c(x%%1, x%%1-1))) < tol` instead of `abs(min(x%%1, x%%1-1)) < tol` otherwise, you'll get `FALSE` for any integer... – Cath May 27 '15 at 09:37
  • 5
    What's wrong with `as.integer(x) == x`? It will not reject 3 or 3.0 like `is.integer(x)` would, and it will catch 3.1. – Gabi Oct 01 '15 at 19:04
38

Here's a solution using simpler functions and no hacks:

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

What's more, you can test a whole vector at once, if you wish. Here's a function:

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

You can change it to use *apply in the case of vectors, matrices, etc.

Iterator
  • 20,250
  • 12
  • 75
  • 111
  • 12
    The last `if` `else` could be done with simply `isTRUE(test)`. Indeed that is all you need to replace the `if` `else` clause and the `return` statements as R automatically returns the result of the last evaluation. – Gavin Simpson Mar 05 '13 at 16:00
  • 8
    ``testInteger(1.0000001)`` [1] FALSE ``testInteger(1.00000001)`` [1] TRUE – PatrickT May 25 '15 at 18:38
  • 3
    `all(a == as.integer(a))` gets around this problem!' – Alex Mar 04 '17 at 05:04
  • This is not working properly! Check out the following counter-example: frac_test <- 1/(1-0.98), all.equal(frac_test, as.integer(frac_test)), isTRUE(all.equal(frac_test, as.integer(frac_test))) – tstudio May 06 '18 at 08:03
11

Reading the R language documentation, as.integer has more to do with how the number is stored than if it is practically equivalent to an integer. is.integer tests if the number is declared as an integer. You can declare an integer by putting a L after it.

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

Also functions like round will return a declared integer, which is what you are doing with x==round(x). The problem with this approach is what you consider to be practically an integer. The example uses less precision for testing equivalence.

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

So depending on your application you could get into trouble that way.

Andrew Redd
  • 4,632
  • 8
  • 40
  • 64
  • 1
    The second line says "as.integer tests if the number is declared as an integer." but I am pretty sure you meant "is.integer". It is only a one character edit so I couldn't easily change it. – PeterVermont Mar 18 '17 at 01:23
11

Here is one, apparently reliable way:

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

This solution also allows for integers in scientific notation:

> check.integer(222e3)
[1] TRUE
VitoshKa
  • 8,387
  • 3
  • 35
  • 59
  • 1
    This doesn't look very reliable to me: `check.integer(1e4)` is TRUE, while `check.integer(1e5)` is FALSE. – wch Feb 14 '12 at 18:02
  • 6
    -1 This is worse than `is.wholenumber`, or any of the other solutions provided in other answers. These shouldn't be different: `check.integer(1e22); check.integer(1e23)`. You can obviously change the regex to fix this, but this approach is dreadful. (Comment comes from attribution in the installr package.) – Joshua Ulrich Mar 05 '13 at 15:30
  • @Joshua, Your comment is completely misleading for three reasons. First, 1e22 in your example cannot be represented accurately, and all non-regexp based solutions will fail. For example the now accepted solution (1e20+1.1)%%1 will give you 0 with a warning! Second, does this string represent an integer "1313213121313232321123213"? If you think it does, then my solution is the only one which works at all! – VitoshKa Mar 27 '13 at 10:05
  • And finally, how do you think R parser understands that you entered an integer if not by regexp-type matching? From what you say, R parser is "dreadful". If I would implement a complete specification of an integer in my reg-exp it will never fail! – VitoshKa Mar 27 '13 at 10:05
  • check.Integer(1.000001) [1] FALSE check.Integer(1.0000001) [1] TRUE – PatrickT May 25 '15 at 18:42
  • It does here. I just checked again. I can't explain it. Here a screenshot: http://postimg.org/image/xrbx1dlen/ – PatrickT May 27 '15 at 17:27
  • 1
    @PatrickT, I see. It's the default digit's argument. use `format(40, scientific = FALSE, digits = 20)` instead. I have updated the answer. Thanks for spotting it. – VitoshKa May 28 '15 at 16:38
  • @VitoshKa, much better, but there's a break at 15 digits, not 20 - is that expected? ``check.integer(1.000000000000001)`` [1] FALSE ``check.integer(1.0000000000000001)`` [1] TRUE – PatrickT May 29 '15 at 02:01
  • 1
    @PatrickT You are in the realm of machine dependent rounding errors. In that respect my solution is the same as the accepted one `1.0000000000000001 == 1L [1] TRUE`. But my solution is better if you already get a number in string form `check.integer("1000000000000000000000000000000000001") [1] TRUE` – VitoshKa Jun 01 '15 at 19:42
  • @VitoshKa, couldn't that problem be avoided if you convert the number to a character and look for the presence of non-zero digits after the decimal dot marker? I assumed that was the point of your choice of ``grepl``? – PatrickT Jun 02 '15 at 07:06
  • @PatrickT. That's precisely what `format` does. It converts to character. – VitoshKa Jun 03 '15 at 10:56
  • @VitoshKa, thanks! Ah got it: ``?print.default`` __Note that for large values of digits, currently for digits >= 16, the calculation of the number of significant digits will depend on the platform's internal (C library) implementation of sprintf() functionality. __ – PatrickT Jun 04 '15 at 14:49
  • 5
    @VitoshKa loved your answer! Although there is one point that you missed, negative numbers without decimal points are also integer ;) I modified your code accordingly. – Mehrad Mahmoudian Dec 04 '15 at 23:33
  • @MehradMahmoudian How did you cope with negative numbers? – green diod May 02 '22 at 09:52
8

It appears that you do not see the need to incorporate some error tolerance. It would not be needed if all integers came entered as integers, however sometimes they come as a result of arithmetic operations that loose some precision. For example:

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

Note that this is not R's weakness, all computer software have some limits of precision.

Aniko
  • 18,516
  • 4
  • 48
  • 45
  • 3
    just in case some people don't quite get what happened here... if you enter as.integer(2/49*49) you get 1 !! [BTW, it is ever so frustrating that R doesn't present the result of the initial calculation as 2.0 to represent that the value has some decimal component) see... http://stackoverflow.com/questions/1535021/whats-the-biggest-r-gotcha-youve-run-across/3398868#3398868 – John Aug 13 '10 at 13:52
6

From Hmisc::spss.get:

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

much safer option, IMHO, since it "bypasses" the machine precision issue. If you try is.integer(floor(1)), you'll get FALSE. BTW, your integer will not be saved as integer if it's bigger than .Machine$integer.max value, which is, by default 2147483647, so either change the integer.max value, or do the alternative checks...

aL3xa
  • 35,415
  • 18
  • 79
  • 112
6

you can use simple if condition like:

if(round(var) != var)­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
ɢʀᴜɴᴛ
  • 32,025
  • 15
  • 116
  • 110
Meru Patil
  • 61
  • 1
  • 1
1

In R, whether a number is numeric or integer can be determined by class function. Generally all numbers are stored as numeric and to explicitly define a number as integer we need to specify 'L' after the number.

Example:

x <- 1

class(x)

[1] "numeric"

x <- 1L

class(x)

[1] "integer"

I hope this is what was needed. Thanks :)

Community
  • 1
  • 1
1

If you prefer not to write your own function, try check.integer from package installr. Currently it uses VitoshKa's answer.

Also try check.numeric(v, only.integer=TRUE) from package varhandle, which has the benefit of being vectorized.

qwr
  • 9,525
  • 5
  • 58
  • 102
1

[UPDATE] ==============================================================

Respect to the [OLD] answer here below, I have discovered that it worked because I have put all the numbers in a single atomic vector; one of them was a character, so every one become characters.

If we use a list (hence, coercion does not happen) all the test pass correctly but one: 1/(1 - 0.98), which remains a numeric. This because the tol parameter is by default 100 * .Machine$double.eps and that number is far from 50 little less than the double of that. So, basically, for this kind of numbers, we have to decide our tolerance!

So if you want all test became TRUE, you can assertive::is_whole_number(x, tol = 200 * .Machine$double.eps)

Anyway, I confirm that IMO assertive remains the best solution.

Here below a reprex for this [UPDATE].

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

Created on 2019-07-23 by the reprex package (v0.3.0)

[OLD] =================================================================

IMO the best solution comes from the assertive package (which, for the moment, solve all positive and negative examples in this thread):

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

Created on 2019-07-23 by the reprex package (v0.3.0)

Corrado
  • 625
  • 6
  • 12
1

Here's my attempt at a solution using Rcpp for the case when you want to check that all numbers are whole numbers.

I've written a function that basically loops through x and returns FALSE if any number is not a whole number using the condition abs(round(x) - x) < tol so it's quite useful if you might have decimals at the start of your vector.

Rcpp function

library(Rcpp)

Rcpp::cppFunction(
'bool is_whole_num(NumericVector x) {
  double tol = sqrt(std::numeric_limits<double>::epsilon());
  bool has_decimal;
  double diff;
  bool out = true;
  for (int i = 0; i < x.length(); ++i) {
    diff = abs(round(x[i]) - x[i]);
    has_decimal = !(diff < tol);
    if (has_decimal && !NumericVector::is_na(x[i])){
      out = false;
      break;
    }
  }
  return out;
}'
)

Benchmark

x1 <- c(1:10^7, 0.01)
x2 <- c(0.01, 1:10^7)

bench::mark(james = all(x1%%1==0),
            iterator = isTRUE(all.equal(x1, as.integer(x1))),
            me = is_whole_num(x1))
# A tibble: 3 x 13
  expression      min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time result    memory    
  <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm> <list>    <list>    
1 james       236.5ms  239.1ms      4.18  114.44MB     2.09     2     1      478ms <lgl [1]> <Rprofmem>
2 iterator    226.2ms    234ms      4.27  305.18MB     0        3     0      702ms <lgl [1]> <Rprofmem>
3 me           53.2ms   55.8ms     17.7     2.49KB     0        9     0      507ms <lgl [1]> <Rprofmem>
# i 2 more variables: time <list>, gc <list>
bench::mark(james = all(x2%%1==0),
            iterator = isTRUE(all.equal(x2, as.integer(x2))),
            me = is_whole_num(x2))
# A tibble: 3 x 13
  expression      min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time result    memory    
  <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm> <list>    <list>    
1 james       227.1ms  236.8ms      4.24  114.44MB     0        3     0    707.2ms <lgl [1]> <Rprofmem>
2 iterator    203.4ms  203.4ms      4.92  305.18MB     4.92     1     1    203.4ms <lgl [1]> <Rprofmem>
3 me            1.4us    1.8us 510441.      2.49KB     0    10000     0     19.6ms <lgl [1]> <Rprofmem>
# i 2 more variables: time <list>, gc <list>
NicChr
  • 858
  • 1
  • 9
0

Once can also use dplyr::near:

library(dplyr)

near(a, as.integer(a))

It applies to any vector a, and has an optional tolerance parameter.

James Hirschorn
  • 7,032
  • 5
  • 45
  • 53
0

For a vector m, m[round(m) != m] will return the indices of values in the vector that are not integers.

KarthikS
  • 883
  • 1
  • 11
  • 17
-4

I am not sure what you are trying to accomplish. But here are some thoughts:
1. Convert to integer:
num = as.integer(123.2342)
2. Check if a variable is an integer:
is.integer(num)
typeof(num)=="integer"

bernardw
  • 1,370
  • 3
  • 15
  • 27
  • I'm just making sure the users enters an appropriate number - we're talking about the number of "subjects", which can be only an integer. – Roman Luštrik Aug 14 '10 at 17:46