10

I wrote a (potentially not particularly good!) function to test if a number is a whole number or not:

is.wholeNumber <- function(x) x == floor(x)

In general this function works fine for my purpose as I am really only considering cases where I'm testing numbers with a handful of deciminal places, so my naive understanding was that machine precision shouldn't be a factor.

When I apply this function in the case 45 x 1.4 = 63, however I get

> is.wholeNumber( 45 * 1.4)
[1] FALSE

This appears to occur because the floor function of R is not evaluating as I would expect:

> floor(45 * 1.4)
[1] 62

which should in fact be 63.


On doing some reading I came across this popular post about how to code this in R. The top voted answer there suggests the function

is.wholeNumber <- function(x) ( x %% 1 ) == 0

which again does not appear to work in my context, since I get

> (45 * 1.4 ) %% 1
[1] 1

The second most upvoted post suggests using

is.wholeNumber <- function(x) all.equal(x, as.integer(x))

and whilst once again this does not work, it does give the illuminatingly unexpected output of

> is.wholeNumber( 45 * 1.4)
[1] "Mean relative difference: 0.01587302"

I have now tried this in both a clean R studio workspace, and from R terminal (R 3.4.2 Short Summer) and duplicated this issue. I'd be interested to know:

  1. Can people reproduce this issue on their machines?
  2. Why I am getting this counter intuitive result?
  3. What is a correct way to work around this?
owen88
  • 454
  • 3
  • 12
  • 3
    A possible clue from ?%%: `For double arguments, %% can be subject to catastrophic loss of accuracy if x is much larger than y, and a warning is given if this is detected.` – divibisan Apr 03 '18 at 18:11
  • 3
    It is a problem with double precision numbers. You can reproduce it virtually in any language. Open the browser developer tools (F12) go to console, and type `Math.floor(45 * 1.4)` you get `62`. If you type `45 * 1.4` you get `62.99999999999999`. I assume R's floor takes a double and works the same. What is the correct workaround? I am not familiar with R. I cannot help with that. – Theraot Apr 03 '18 at 18:11
  • @Theraot,that's interesting to know; as I understand base R commands are effectively calls to C functions (... I'm sure others will jump in to correct!); so would you be able to know a valid work around for C? – owen88 Apr 03 '18 at 18:13
  • Numbers for computers are hard. See [Why are these numbers not equal?](https://stackoverflow.com/questions/9508518/why-are-these-numbers-not-equal). Why is it important for you to test for whole numbers? Maybe there are better solutions depending on exactly what you are doing. – MrFlick Apr 03 '18 at 18:21
  • Sometimes the best you can do is check for equality within a minimum error, which in R would typically be `.Machine$double.eps`, although I think in this specific instance the difference is greater than that. – joran Apr 03 '18 at 18:24
  • 4
    FWIW, I might use a function more like this: `is.wholeNumber <- function(x) abs(x - round(x)) < .Machine$double.eps^0.5`. See also [here](https://stackoverflow.com/q/46739569/324364). – joran Apr 03 '18 at 18:28
  • 4
    There is no general solution. There is plenty of literature on how double precision floating point numbers works, their limitation, and approaches to compare them. I had a look at the binary representation of `45 * 1.4` and it is off from `62` by one bit. Thus, it correct that it is not integer. You can either allow a margin of error, look another way to get your results (`(14 * 45) / 10` yields `63`), avoid double (you can, define a custom fraction type, or use some a fixed point available in R), or something else for your particular use case. Again, there is no general solution. – Theraot Apr 03 '18 at 18:36
  • Thanks all for the comments and suggested resources; I've also been quite startled by the number of posts where all of the answers are susceptible to this issue, but there are no comments to this effect. – owen88 Apr 04 '18 at 05:59
  • As you’ve no doubt worked out, the decimal precision of numbers has no bearing on their safety in a binary representation. (Except for integers.) – Hugh Apr 04 '18 at 08:32

1 Answers1

1

Following function will give you correct results:

is.wholeNumber <- function(x, tol = .Machine$double.eps^0.5)  abs(x - round(x)) < tol
sfr
  • 43
  • 5