Round with integer division

Question:

Is there is a simple, pythonic way of rounding to the nearest whole number without using floating point? I’d like to do the following but with integer arithmetic:

skip = int(round(1.0 * total / surplus))

==============

@John: Floating point is not reproducible across platforms. If you want your code to pass tests across different platforms then you need to avoid floating point (or add some hacky espilon stuff to your tests and hope it works). The above may be simple enough that it would be the same on most/all platforms, but I’d rather not make that determination as it is easier to avoid floating point altogether. How is that “not in the spirit of Python”?

Asked By: new name

||

Answers:

skip = (((total << 1) // surplus) + 1) >> 1

Shifting things left by one bit effectively multiplies by two, shifting things right by one bit divides by two rounding down. Adding one in the middle makes it so that “rounding down” is actually rounding up if the result would have been above a .5 decimal part.

It’s basically the same as if you wrote…

skip = int((1.0*total/surplus) + 0.5)

except with everything multplied by 2, and then later divided by 2, which is something you can do with integer arithmetic (since bit shifts don’t require floating point).

Answered By: Amber

This should work too:

def rint(n):
    return (int(n+.5) if n > 0 else int(n-.5))
Answered By: rubik

Simply take care of the rounding rule before you ever divide. For the simplest round-half-up:

if total % surplus < surplus / 2:
    return total / surplus
else:
    return (total / surplus) + 1

Tweak a little bit if you need to do a proper round-to-even.

Answered By: hobbs

You can do this quite simply:

(n + d // 2) // d, where n is the dividend and d is the divisor.

Alternatives like (((n << 1) // d) + 1) >> 1 or the equivalent (((n * 2) // d) + 1) // 2 may be SLOWER in recent CPythons, where an int is implemented like the old long.

The simple method does 3 variable accesses, 1 constant load, and 3 integer operations. The complicated methods do 2 variable accesses, 3 constant loads, and 4 integer operations. Integer operations are likely to take time which depends on the sizes of the numbers involved. Variable accesses of function locals don’t involve “lookups”.

If you are really desparate for speed, do benchmarks. Otherwise, KISS.

Answered By: John Machin

Yet another funny way:

q, r = divmod(total, surplus)
skip = q + int(bool(r))
Answered By: rmnff

TL;DR:

q, r = divmod(total, surplus)
skip = q if q % 2 == 0 and 2*r == surplus else q + (2*r // surplus) # rounds to nearest integer; half to nearest even

This solution:

  • rounds to the nearest integer as demanded by OP
  • works for both positive and negative integers
  • rounds 0.5 fractional parts to nearest even integer (rnd(0.5)=0, rnd(1.5)=2) which matches the behavior of python’s round function used by OP
  • sticks to integer arithmetic as demanded by OP (see documentation of divmod)

Full story

Inspired by zhmyh’s answer answer, which is

q, r = divmod(total, surplus)
skip = q + int(bool(r)) # rounds to next greater integer (always ceiling)

, I came up with the following solution (UPDATE: that only works for non-negative total and surplus, as pointed out in the comments):

q, r = divmod(total, surplus)
skip = q + int(2 * r >= surplus) # rounds to nearest integer (floor or ceiling) if total and surplus are non-negative

Since the OP asked for rounding to the nearest whole number, zhmhs’s solution is in fact slightly incorrect, because it always rounds to the next greater whole number, while my solution works as demanded works for non-negative total and surplus only. A correct solution that also works for negative numbers is:

q, r = divmod(total, surplus)
skip = q + (2 * r // surplus) # rounds to nearest integer (positive: half away from zero, negative: half toward zero)

Please note though that if 2 * r == surplus, it will basically perform ceiling for both positive and negative results, e.g. ceil(-1.5) == -1 whereas ceil(1.5) == 2. This is still correct behavior in terms of rounding to the nearest integer (because of equal distance to next lower and next greater integer) but it is asymmetrical in reference to zero. To also fix this, that is, round half away from zero, we can add a boolean condition:

q, r = divmod(total, surplus)
skip = q if q < 0 and 2*r == surplus else q + (2*r // surplus) # rounds to nearest integer; half away from zero

And even better, to round half to nearest even like python’s round function used by the OP:

q, r = divmod(total, surplus)
skip = q if q % 2 == 0 and 2*r == surplus else q + (2*r // surplus) # rounds to nearest integer; half to nearest even

In case you wonder how divmod is defined: According to its documentation

For integers, the result is the same as (a // b, a % b).

We therefore stick with integer arithmetic, as demanded by the OP.

Answered By: Daniel K

The question is the rounding strategy you are after.

Here are several that I came up with – note that integer floor and ceiling are cases, but in "round to nearest" there are many strategies. The IEEE 754 and the most professional and industrial and even elegant way of doing it is Round To Nearest Even. I’ve never seen a single example anywhere of doing this for integer arithmetic. You cannot go through float as the 53-bit mantissa may cause precision issues and it already applies round-to-nearest-even mode if desiring to do a different rounding strategy. The correct techniques should always stay in the integer domain

def roundNegInf(n, d): #floor
    return n // d
def roundPosInf(n, d): #ceil
    return (n + d + -1*(d >= 0)) // d
def roundTowardsZero(n, d):
    return (n + ((d + -1*(d >=0)) if (n < 0) ^ (d < 0) else 0)) // d
def roundAwayFromZero(n, d):
    return (n + (0 if (n < 0) ^ (d < 0) else (d + -1*(d >= 0)))) // d
def roundNearestPosInf(n, d):
    #return (((n << 1) // d) + 1) >> 1
    #return (((n * 2) // d) + 1) // 2
    q, r = divmod(n, d)
    return q + (2 * r <= d if d < 0 else 2 * r >= d)
def roundNearestNegInf(n, d):
    q, r = divmod(n, d)
    return q + (2 * r < d if d < 0 else 2 * r > d)
def roundNearestEven(n, d): #round only when for positive numbers guard, round, sticky are 11X or 1X1
    q, r = divmod(n, d)
    return q + (q & 1) * (2 * r == d) + ((2 * r < d) if d < 0 else (2 * r > d))
def roundNearestToZero(n, d):
    q, r = divmod(n, d)
    return q + (q < 0) * (2 * r == d) + ((2 * r < d) if d < 0 else (2 * r > d))
def roundNearestAwayZero(n, d):
    q, r = divmod(n, d)
    return q + (q >= 0) * (2 * r == d) + ((2 * r < d) if d < 0 else (2 * r > d))
def testRounding():
    pairs = ((1, 2), (-1, 2), (1, -2), (-1, -2), (3, 2), (-3, 2), (3, -2), (-3, -2),
             (1, 3), (-1, 3), (1, -3), (-1, -3), (2, 3), (-2, 3), (2, -3), (-2, -3))
    funcs = (roundNegInf, roundPosInf, roundTowardsZero, roundAwayFromZero,
             roundNearestPosInf, roundNearestNegInf,
             roundNearestToZero, roundNearestAwayZero, roundNearestEven)
    res = [[f(*p) for p in pairs] for f in funcs]
    expected = [[0, -1, -1, 0, 1, -2, -2, 1, 0, -1, -1, 0, 0, -1, -1, 0],
                   [1, 0, 0, 1, 2, -1, -1, 2, 1, 0, 0, 1, 1, 0, 0, 1],
                   [0, 0, 0, 0, 1, -1, -1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
                   [1, -1, -1, 1, 2, -2, -2, 2, 1, -1, -1, 1, 1, -1, -1, 1],
                   [1, 0, 0, 1, 2, -1, -1, 2, 0, 0, 0, 0, 1, -1, -1, 1],
                   [0, -1, -1, 0, 1, -2, -2, 1, 0, 0, 0, 0, 1, -1, -1, 1],
                   [0, 0, 0, 0, 1, -1, -1, 1, 0, 0, 0, 0, 1, -1, -1, 1],
                   [1, -1, -1, 1, 2, -2, -2, 2, 0, 0, 0, 0, 1, -1, -1, 1],
                   [0, 0, 0, 0, 2, -2, -2, 2, 0, 0, 0, 0, 1, -1, -1, 1]
                   ]
    assert(all([x == y for x, y in zip(res, expected)]))
Answered By: Gregory Morse