Round up from .5

39,737

Solution 1

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, n) {
  posneg = sign(x)
  z = abs(x)*10^n
  z = z + 0.5 + sqrt(.Machine$double.eps)
  z = trunc(z)
  z = z/10^n
  z*posneg
}

x is the object you want to round, and n 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).)

Solution 2

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

Solution 3

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.

Solution 4

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

Solution 5

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
Share:
39,737

Related videos on Youtube

jakob-r
Author by

jakob-r

Updated on July 05, 2022

Comments

  • jakob-r
    jakob-r almost 2 years

    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)

    • Michael Allen
      Michael Allen over 11 years
      Am I right in thinking you want values <= 0.4 to round to 0 and values >= 0.5 to round to 1?
    • flodel
      flodel over 11 years
      @CarlWitthoft, are they really? Can you elaborate? That round maps n + .5 to n seems arbitrary to me.
    • jakob-r
      jakob-r over 11 years
      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.
    • James
      James over 11 years
      @flodel Comapre sum(seq(0.5,1e3,by=0.5)) with the sums of each of the rounded versions of the sequences
    • Carl Witthoft
      Carl Witthoft over 11 years
      Not to mention that "rounding to the even digit" is the IEC 60559 standard as mentioned in ?round .
  • jakob-r
    jakob-r over 11 years
    So simple that I am a little suspicous.
  • James
    James over 11 years
    @jakobr It's relies on you wanting to only round to whole numbers, rather than any other power of 10
  • flodel
    flodel over 11 years
    +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.
  • A5C1D2H2I1M1N2O1R2T1
    A5C1D2H2I1M1N2O1R2T1 over 11 years
    @flodel, Unfortunately, I can't take credit for the function. I honestly didn't know there were so many different ways of rounding until I had encountered R's round-to-even behavior and did some reading up on the topic.
  • A5C1D2H2I1M1N2O1R2T1
    A5C1D2H2I1M1N2O1R2T1 over 10 years
    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).
  • Dason
    Dason almost 10 years
    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.
  • Megatron
    Megatron about 8 years
    This solution works for me: x <- seq(-5, 5, by=0.5) and then trunc(x+sign(x)*0.5). Elegant!
  • krevelen
    krevelen about 7 years
    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
    krevelen about 7 years
    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}
  • Dogan Askan
    Dogan Askan over 6 years
    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
  • flodel
    flodel over 6 years
    @DoganAskan, it is due to floating point errors. See Circle 1 of burns-stat.com/pages/Tutor/R_inferno.pdf. I highly recommend reading the whole pdf if you have time.
  • MS Berends
    MS Berends over 5 years
    Or as a oneliner: round2 <- function(x, n) (trunc((abs(x) * 10 ^ n) + 0.5) / 10 ^ n) * sign(x)
  • Miff
    Miff over 4 years
    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
  • Gregor Thomas
    Gregor Thomas over 4 years
    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
  • mpschramm
    mpschramm over 4 years
    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.
  • Gregor Thomas
    Gregor Thomas over 4 years
    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.
  • mpschramm
    mpschramm over 4 years
    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.
  • Gregor Thomas
    Gregor Thomas over 4 years
    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
    Gregor Thomas almost 4 years
    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
    Gregor Thomas almost 4 years
    Changing z + 0.5 to z + 0.5 + sqrt(.Machine$double.eps) might work as well, and be a little more elegant.
  • bryn
    bryn over 3 years
    The janitor package also adds this functionality, so it might be an easy way to get the behaviour you want without writing it yourself. cran.r-project.org/web/packages/janitor/vignettes/…
  • jgarces
    jgarces over 2 years
    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.