84

Yes I know why we always round to the nearest even number if we are in the exact middle (i.e. 2.5 becomes 2) of two numbers. But when I want to evaluate data for some people they don't want this behaviour. What is the simplest method to get this:

x <- seq(0.5,9.5,by=1)
round(x)

to be 1,2,3,...,10 and not 0,2,2,4,4,...,10.

Edit: To clearify: 1.4999 should be 1 after rounding. (I thought this would be obvious)

Jaap
  • 81,064
  • 34
  • 182
  • 193
jakob-r
  • 6,824
  • 3
  • 29
  • 47
  • Am I right in thinking you want values <= 0.4 to round to 0 and values >= 0.5 to round to 1? – Michael Allen Oct 02 '12 at 10:45
  • @CarlWitthoft, are they really? Can you elaborate? That `round` maps `n + .5` to `n` seems arbitrary to me. – flodel Oct 02 '12 at 11:46
  • 4
    It is easy to simulate. Based on the sequence `x` from above try `mean(x); mean(round(x)); mean(floor(0.5 + x))`. Of course this does not proof anything as this could be only a special case. But look at this this way: If we round every x.5 up of course our rounded data than is biased. If we round down every second x.5 we counter this effect. That's why we round to the next even number. – jakob-r Oct 02 '12 at 11:58
  • @flodel Comapre `sum(seq(0.5,1e3,by=0.5))` with the sums of each of the rounded versions of the sequences – James Oct 02 '12 at 11:59
  • 12
    Not to mention that "rounding to the even digit" is the IEC 60559 standard as mentioned in `?round` . – Carl Witthoft Oct 02 '12 at 13:18

8 Answers8

96

This is not my own function, and unfortunately, I can't find where I got it at the moment (originally found as an anonymous comment at the Statistically Significant blog), but it should help with what you need.

round2 = function(x, digits) {
  posneg = sign(x)
  z = abs(x)*10^digits
  z = z + 0.5 + sqrt(.Machine$double.eps)
  z = trunc(z)
  z = z/10^digits
  z*posneg
}

x is the object you want to round, and digits is the number of digits you are rounding to.

An Example

x = c(1.85, 1.54, 1.65, 1.85, 1.84)
round(x, 1)
# [1] 1.8 1.5 1.6 1.8 1.8
round2(x, 1)
# [1] 1.9 1.5 1.7 1.9 1.8

(Thanks @Gregor for the addition of + sqrt(.Machine$double.eps).)

Roel
  • 3,089
  • 2
  • 30
  • 34
A5C1D2H2I1M1N2O1R2T1
  • 190,393
  • 28
  • 405
  • 485
  • 3
    +1 - Very nice, I only realize now that your answer is a generalization of mine. Maybe you should have used `n=0` in your example, even set it as default in your function. Also note that it handles negative numbers differently: `round2(-0.5, 0)` gives `-1` while my method will return `0`. – flodel Oct 02 '12 at 11:15
  • 2
    @flodel, Unfortunately, I can't take credit for the function. I honestly didn't know there were [so many different ways of rounding](http://www.diycalculator.com/popup-m-round.shtml) until I had encountered R's round-to-even behavior and did some reading up on the topic. – A5C1D2H2I1M1N2O1R2T1 Oct 02 '12 at 12:42
  • Just to provide the single-line version for rounding `x` away from zero with `n` digits: `round2 = function(x, n=0) {scale<-10^n; sign(x)*trunc(abs(x)*scale+0.5)/scale}`. Surely there's a native/built-in function for this in R? – krevelen Apr 26 '17 at 09:02
  • 1
    And now I see the elegant solution below, which would yield: `round2 = function(x, n=0) {scale<-10^n; trunc(x*scale+sign(x)*0.5)/scale}` – krevelen Apr 26 '17 at 09:11
  • Or as a oneliner: `round2 <- function(x, n) (trunc((abs(x) * 10 ^ n) + 0.5) / 10 ^ n) * sign(x)` – MS Berends Nov 24 '18 at 19:07
  • 6
    There are numerical precision issues, e.g., `round2(2436.845, 2)` returns `2436.84`. Changing `z + 0.5` to `z + 0.5 + sqrt(.Machine$double.eps)` seems to work for me. – Gregor Thomas Jun 24 '20 at 02:16
  • 7
    The janitor package also adds this functionality, so it might be an easy way to get the behaviour you want without writing it yourself. https://cran.r-project.org/web/packages/janitor/vignettes/janitor.html#directionally-consistent-rounding-behavior-with-round_half_up – bryn Oct 08 '20 at 22:59
  • Very useful. And what about if there're more that one decimal and you wanna round all of them? For example, `round2(1.48, 0)`gives me `1` and not `2`. I solved trough `round2(round(1.48), 0)` but maybe another option in the function would be super nice. – jgarces Sep 13 '21 at 11:12
  • @GregorThomas Is it correct to always add machine error? Can't the floating point representation cause both over and under estimation? – Cole May 24 '23 at 07:31
  • @Cole I'd be careful with the *"always"*, but **in this case** the behavior we want to change is "round up, not down". So for this particular problem adding is correct. (Also noting that the `sign` is considered.) If 2.5 is represented as 2.49999999...23 and we want it to round up, we need to add. If 2.5 is represented as 2.50000...23, then adding does no harm... – Gregor Thomas May 24 '23 at 14:49
  • 1
    The risk, I suppose, is *"what if we have a value that is actually within `sqrt(.Machine$double.eps)` less than 2.5? It should be rounded down"*, something like `2.5 - (0.5 * sqrt(.Machine$double.eps))`. I don't really buy that it should be rounded down, as it's **so close** to 2.5 as to be virtually indistinguishable. But if you trust you estimates to be accurate to 8 decimal places, then this could introduce issues, and you may need to set an even smaller adjustment than `sqrt(.Machine$double.eps)` (or handle the problem differently). – Gregor Thomas May 24 '23 at 14:51
  • @GregorThomas Ah, ok, what you are saying makes sense. Essentially we are biasing slightly upwards in the case machine precision causes an underestimate of the true value and accepting the tradeoff that some values very close to `.5` (not due to precision error) will also get bumped over the threshold. – Cole May 24 '23 at 22:37
  • @GregorThomas My question arose because I was trying to adapt this to round down/toward zero instead of up/away from zero. For rounding down then it looks like it should be `ceiling` instead of `trunc` and `-` instead of `+` machine error. – Cole May 24 '23 at 22:39
49

If you want something that behaves exactly like round except for those xxx.5 values, try this:

x <- seq(0, 1, 0.1)
x
# [1] 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
floor(0.5 + x)
# [1] 0 0 0 0 0 1 1 1 1 1 1
MichaelChirico
  • 33,841
  • 14
  • 113
  • 198
flodel
  • 87,577
  • 21
  • 185
  • 223
  • 2
    So simple that I am a little suspicous. – jakob-r Oct 02 '12 at 10:59
  • 2
    @jakobr It's relies on you wanting to only round to whole numbers, rather than any other power of 10 – James Oct 02 '12 at 11:02
  • You could also use sign(x)*floor(abs(x)+ 0.5) if you want to do rounding "away" from 0 and it's possible you have negative values. – Dason Jul 16 '14 at 16:46
  • Why does `floor(0.2850*100+0.5)` return 28, whereas `floor(0.3850*100+0.5)` returns 39, and `floor(0.4850*100+0.5)` returns 49 – Dogan Askan Jan 09 '18 at 20:46
  • 3
    @DoganAskan, it is due to floating point errors. See Circle 1 of https://www.burns-stat.com/pages/Tutor/R_inferno.pdf. I highly recommend reading the whole pdf if you have time. – flodel Jan 09 '18 at 23:11
20

As @CarlWitthoft said in the comments, this is the IEC 60559 standard as mentioned in ?round:

Note that for rounding off a 5, the IEC 60559 standard is expected to be used, ‘go to the even digit’. Therefore round(0.5) is 0 and round(-1.5) is -2. However, this is dependent on OS services and on representation error (since e.g. 0.15 is not represented exactly, the rounding rule applies to the represented number and not to the printed number, and so round(0.15, 1) could be either 0.1 or 0.2).

An additional explanation by Greg Snow:

The logic behind the round to even rule is that we are trying to represent an underlying continuous value and if x comes from a truly continuous distribution, then the probability that x==2.5 is 0 and the 2.5 was probably already rounded once from any values between 2.45 and 2.54999999999999..., if we use the round up on 0.5 rule that we learned in grade school, then the double rounding means that values between 2.45 and 2.50 will all round to 3 (having been rounded first to 2.5). This will tend to bias estimates upwards. To remove the bias we need to either go back to before the rounding to 2.5 (which is often impossible to impractical), or just round up half the time and round down half the time (or better would be to round proportional to how likely we are to see values below or above 2.5 rounded to 2.5, but that will be close to 50/50 for most underlying distributions). The stochastic approach would be to have the round function randomly choose which way to round, but deterministic types are not comforatable with that, so "round to even" was chosen (round to odd should work about the same) as a consistent rule that rounds up and down about 50/50.

If you are dealing with data where 2.5 is likely to represent an exact value (money for example), then you may do better by multiplying all values by 10 or 100 and working in integers, then converting back only for the final printing. Note that 2.50000001 rounds to 3, so if you keep more digits of accuracy until the final printing, then rounding will go in the expected direction, or you can add 0.000000001 (or other small number) to your values just before rounding, but that can bias your estimates upwards.

Jaap
  • 81,064
  • 34
  • 182
  • 193
9

This appears to work:

rnd <- function(x) trunc(x+sign(x)*0.5)

Ananda Mahto's response seems to do this and more - I am not sure what the extra code in his response is accounting for; or, in other words, I can't figure out how to break the rnd() function defined above.

Example:

seq(-2, 2, by=0.5)
#  [1] -2.0 -1.5 -1.0 -0.5  0.0  0.5  1.0  1.5  2.0
round(x)
#  [1] -2 -2 -1  0  0  0  1  2  2
rnd(x)
#  [1] -2 -2 -1 -1  0  1  1  2  2
Megatron
  • 15,909
  • 12
  • 89
  • 97
user1854990
  • 211
  • 3
  • 5
  • 2
    Did you post your entire function? The `rdn` function you posted only takes one argument (the input vector) but you show it being used with two arguments. The function I've posted tries to address rounding to other place values as well. For instance, compare the difference between `round(x, 2)` and `round2(x, 2)` when `x = c(1.855, 1.545, 1.655, 1.855, 1.845)`. – A5C1D2H2I1M1N2O1R2T1 Sep 24 '13 at 02:18
  • This solution works for me: `x <- seq(-5, 5, by=0.5)` and then `trunc(x+sign(x)*0.5)`. Elegant! – Megatron Mar 04 '16 at 13:54
6

Depending on how comfortable you are with jiggling your data, this works:

round(x+10*.Machine$double.eps)
# [1]  1  2  3  4  5  6  7  8  9 10
MichaelChirico
  • 33,841
  • 14
  • 113
  • 198
  • 1
    if you add a factor of $sign(x)$ to the small number it replicates Excel's behaviour of rounding away from zero for negative numbers – Miff Dec 18 '19 at 14:38
3

This method:

round2 = function(x, n) {
  posneg = sign(x)
  z = abs(x)*10^n
  z = z + 0.5
  z = trunc(z)
  z = z/10^n
  z*posneg
}

does not seem to work well when we have numbers with many digits. E.g. doing round2(2436.845, 2) will give us 2436.84. The issue seems to occur with the trunc(z) function.

Overall, I think it has something to do with the way R stores numbers and thus the trunc and float function doesn't always work. I was able to get around it in not the most elegant way:

round2 = function(x, n) {
  posneg = sign(x)
  z = abs(x)*10^n
  z = z + 0.5
  z = trunc(as.numeric(as.character(z)))
  z = z/10^n
  (z)*posneg
}
Bryan Chia
  • 41
  • 4
2

This mimics the rounding away from zero at .5:

round_2 <- function(x, digits = 0) {
  x = x + abs(x) * sign(x) * .Machine$double.eps
  round(x, digits = digits)
}

round_2(.5 + -2:4)
-2 -1  1  2  3  4  5
Gregor Thomas
  • 136,190
  • 20
  • 167
  • 294
mpschramm
  • 520
  • 6
  • 12
  • What's the point of the `abs(x)` in the calculation? Doesn't seem like the amount added should depend on the magnitude of `x`. I could see adding a factor of 10 or 100 times the `double.eps` if you want a small cushion---it just doesn't make sense to me that the cushion should depend on the magnitude of `x` – Gregor Thomas Jan 27 '20 at 14:30
  • I don't have a good answer for that, possibly a platform and representation issue (I did this on Windows): `x = 2.5 round(x + sign(x) * .Machine$double.eps, 0)` results in `2`. Whereas, `round(x + abs(x) * sign(x) * .Machine$double.eps, 0)` results in `3`. Adding a small fixed amount instead of something that scales with x fails as x increases. – mpschramm Jan 27 '20 at 16:26
  • You've demonstrated that the fixed amount needs to be bigger than just `.Machine$double.eps`, but you haven't shown that it needs to *scale with `x`*. I think you just need to make your fixed amount *slightly* bigger. Multiply by a factor of 10 as I suggest in my comment, and as is done in MichaelChirico's answer. Making it scale with `x` means that, as `x` gets larger, values more and more below 0.5 will round up. – Gregor Thomas Jan 27 '20 at 16:39
  • I see your logic and recognize this approach will cause issues if x is quite large. I might be failing to see how to functionalize this approach. For example: `round(10000.5 + 10 *.Machine$double.eps, 0)` returns 10000 instead of 10001. – mpschramm Jan 28 '20 at 14:22
  • Hmmm, interesting. So maybe there is something to do with the magnitude of `x`... at 10000 I had to go up to `10000 * .Machine$double.eps`---i.e., `abs(x)`. So, you've convinced me! – Gregor Thomas Jan 28 '20 at 15:35
0

You could use the following:

ceiling(x-0.49)

or

ceiling(round(x,2)-0.49)
M--
  • 25,431
  • 8
  • 61
  • 93
  • or rather, ceiling(round(x,2)-0.49) – Ben-Iddo Apr 25 '23 at 16:00
  • 2
    Welcome to Stack Overflow! While this code may solve the question, [including an explanation](//meta.stackexchange.com/q/114762) of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. Please [edit] your answer to add explanations and give an indication of what limitations and assumptions apply. – Yunnosch Apr 25 '23 at 16:00