8

I'm working on a way to divide a signed integer by a power of 2 using only binary operators (<< >> + ^ ~ & | !), and the result has to be round toward 0. I came across this question also on Stackoverflow on the problem, however, I cannot understand why it works. Here's the solution:

int divideByPowerOf2(int x, int n)
{
    return (x + ((x >> 31) & ((1 << n) + ~0))) >> n;
}

I understand the x >> 31 part (only add the next part if x is negative, because if it's positive x will be automatically round toward 0). But what's bothering me is the (1 << n) + ~0 part. How can it work?

3
  • two's complement. But you're right, the answer doesn't explain anything. Now you get downvotes when you do that ... Commented Sep 25, 2016 at 21:06
  • 1
    x + ~0 is a funny way to write x - 1, it's just to truncate that rounding mask to n bits
    – user555045
    Commented Sep 25, 2016 at 21:16
  • 1) Concerned about dividing negative numbers? 2) Concerned about dividing negative numbers portably? 3) Concerned about handling x == INT_MIN? 4) what about powers of 2 that meet/exceed the bit width of int? Otherwise the goal is so narrow that it is not that useful beyond a narrow idea of the possibility or the range and details of int. Commented Sep 25, 2016 at 23:50

3 Answers 3

9

Assuming 2-complement, just bit-shifting the dividend is equivalent to a certain kind of division: not the conventional division where we round the dividend to next multiple of divisor toward zero. But another kind where we round the dividend toward negative infinity. I rediscovered that in Smalltalk, see http://smallissimo.blogspot.fr/2015/03/is-bitshift-equivalent-to-division-in.html.

For example, let's divide -126 by 8. traditionally, we would write

-126 = -15 * 8 - 6

But if we round toward infinity, we get a positive remainder and write it:

-126 = -16 * 8 + 2

The bit-shifting is performing the second operation, in term of bit patterns (assuming 8 bits long int for the sake of being short):

1000|0010 >> 3 = 1111|0000
1000|0010      = 1111|0000 * 0000|1000 + 0000|0010

So what if we want the traditional division with quotient rounded toward zero and remainder of same sign as dividend? Simple, we just have to add 1 to the quotient - if and only if the dividend is negative and the division is inexact.

You saw that x>>31 corresponds to first condition, dividend is negative, assuming int has 32 bits.

The second term corresponds to the second condition, if division is inexact.

See how are encoded -1, -2, -4, ... in two complement: 1111|1111 , 1111|1110 , 1111|1100. So the negation of nth power of two has n trailing zeros.

When the dividend has n trailing zeros and we divide by 2^n, then no need to add 1 to final quotient. In any other case, we need to add 1.

What ((1 << n) + ~0) is doing is creating a mask with n trailing ones.

The n last bits don't really matter, because we are going to shift to the right and just throw them away. So, if the division is exact, the n trailing bits of dividend are zero, and we just add n 1s that will be skipped. On the contrary, if the division is inexact, then one or more of the n trailing bits of the dividend is 1, and we are sure to cause a carry to the n+1 bit position: that's how we add 1 to the quotient (we add 2^n to the dividend). Does that explain it a bit more?

0
2

This is "write-only code": instead of trying to understand the code, try to create it by yourself.

For example, let's divide a number by 8 (shift right by 3). If the number is negative, the normal right-shift rounds in the wrong direction. Let's "fix" it by adding a number:

int divideBy8(int x)
{
    if (x >= 0)
        return x >> 3;
    else
        return (x + whatever) >> 3;
}

Here you can come up with a mathematical formula for whatever, or do some trial and error. Anyway, here whatever = 7:

int divideBy8(int x)
{
    if (x >= 0)
        return x >> 3;
    else
        return (x + 7) >> 3;
}

How to unify the two cases? You need to make an expression that looks like this:

(x + stuff) >> 3

where stuff is 7 for negative x, and 0 for positive x. The trick here is using x >> 31, which is a 32-bit number whose bits are equal to the sign-bit of x: all 0 or all 1. So stuff is

(x >> 31) & 7

Combining all these, and replacing 8 and 7 by the more general power of 2, you get the code you asked about.


Note: in the description above, I assume that int represents a 32-bit hardware register, and hardware uses two's complement representation to do right shift.

2
  • 3
    Since x is of type int, (x >> 31) may or may not result in all-1s when x is negative. The ISO C standard states (e.g. C99 section 6.5.7, paragraph 5) that right-shifting a signed integer of negative value invokes implementation-defined behavior.
    – njuffa
    Commented Sep 25, 2016 at 22:59
  • implementation defined behaviour can be fixed by casting to unsigned, also this solution breaks if it is shifting by 0.
    – r4bb1t
    Commented Jan 25, 2020 at 18:02
0

OP's reference is of a C# code and so many subtle differences that cause it to be bad code with C, as this post is tagged.

int is not necessarily 32-bits so using a magic number of 32 does not make for a robust solution.

In particular (1 << n) + ~0 results in implementation defined behavior when n causes a bit to be shifted into the sign place. Not good coding.

Restricting code to only using "binary" operators << >> + ^ ~ & | ! encourages a coder to assume things about int which is not portable nor compliant with the C spec. So OP's posted code does not "work" in general, although may work in many common implementations.

OP code fails when int is not 2's complement, not uses the range [-2147483648 .. 2147483647] or when 1 << n uses implementation behavior that is not as expected.

// weak code
int divideByPowerOf2(int x, int n) {
  return (x + ((x >> 31) & ((1 << n) + ~0))) >> n;
}

A simple alternative, assuming long long exceeds the range of int follows. I doubt this meets some corner of OP's goals, but OP's given goals encourages non-robust coding.

int divideByPowerOf2(int x, int n) {
  long long ill = x;
  if (x < 0) ill = -ill;
  while (n--) ill >>= 1;
  if (x < 0) ill = -ill;
  return (int) ill;
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.