1

I'm trying to write a C function that will always find the arithmetic right shift of an unsigned integer argument, using bitwise operators. I managed to write a function that would always find the logical right shift of a signed integer using bitwise operators, but the same idea doesn't seem to be working for finding the arithmetic shift.

Here's my function right now:

unsigned arith(unsigned x, int k) {
    unsigned x_arith = (int) x >> k;

    x_arith = x_arith | ((-1<<(sizeof(x_arith)*8)-k)) & ~(-1<<(sizeof(x_arith)*8));
    return x_arith;
}

Explanation:

(-1 << (sizeof(x_arith)*8-k))

Create an all-1's binary then shift those ones so that the first 1 aligns with the leftmost 1 in x_arth.

& ~(-1 << (sizeof(x_arith)*8))

Create an all-1's binary and shift it so the first one aligns after the leftmost bit in x_arth, then flip it, then keep only those bits in common with the binary above.

This whole process supposedly creates a mask that has 1's where x_arith needs to have ones. Finally, I | (or) the original x_arith with this mask to get the 1's that I need to make the shift arithmetic and return the result.

However, the result is still a logical shift. I tried changing many things in the above code and got really frustrated with the weird numbers I got. What am I doing wrong?

UPDATE: The function posted above does actually find the arithmetic shift. The problem was that I was testing it with 4 and k=1, expecting the result to be 6, which turns out to be not how arithmetic shifting works. Here's a nicer-looking version of the function for future reference:

unsigned arith_shift(unsigned x, int k) {

    unsigned x_arith = (int) x >> k;

    int bits = sizeof(int) * 8;

    unsigned mask = k? ((~0 << (bits-k)) & ~(~0 << bits)): 0;

    return x_arith | mask;

}

Note that you can use both -1 and ~0 for an all-1's binary number. However, this function may behave differently on different machines since the behavior of left-shifting -1 is undefined (see e0k's answer).

Novice
  • 613
  • 1
  • 5
  • 17
  • Did you try to do the shift on a regular int instead of unsigned? (or alternatively enclose the casted x in parenthesis in the first shift?) – ilomambo Jan 30 '16 at 08:46
  • I am uncertain what you want to achieve and why you think it's not working. -- But at least `sizeof(x_arith)*8` is the number of bits of an (unsigned) `int`. So shifting `-1` by this number results in (a) 0 or (b) undefined behavior. In can (a) `~` will give you `-1` again. So this looks not useful. – harper Jan 30 '16 at 08:46
  • 1
    Look at this expression: `(-1 << (sizeof(x_arith)*8))` You're taking an all-ones value, then attempting to shift *all* of it off the end. It makes no sense. I don't even think a shift amount that's the same as the number of bits is defined, but the best you could hope for is all zeros. Perhaps you need to rethink this. – Tom Karzes Jan 30 '16 at 08:49
  • @ilomambo No. @harper & @Tom A very similar procedure works with converting an arithmetic shift into a logical shift, using -1. It's simply `xl = xl & ~(-1<< s)`, where s = `sizeof(xl)*8 - k`. So I think -1 is useful. I tried to use `~0` instead and it seems to give the same behavior. @bolov This is part of a homework exercise I want to do. The point is to get to know C's bitwise operators. – Novice Jan 30 '16 at 09:09
  • `unsigned x_arith = (int) x >> k;` already performs arithmetic right shift. (Assuming the usual behaviour of `(int)x`, which is not well-defined). What is the rest of the code for? – M.M Feb 02 '16 at 09:08
  • Possible duplicate of [Bitwise Leftshift (<<) strange behavior](https://stackoverflow.com/questions/16879251/bitwise-leftshift-strange-behavior) – phuclv Sep 08 '18 at 05:55

2 Answers2

2

From C99 6.5.7 Bitwise shift operators paragraph 4: (emphasis mine)

  1. The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1 x 2E2 , reduced modulo one more than the maximum value representable in the result type. If E1 has a signed type and nonnegative value, and E1 x 2E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.

You are trying to use -1<<(sizeof(x_arith)*8) for which your E1 is -1:

Therefore, your statement falls into the dreaded "undefined behavior" category, which means all bets are off. But all is not lost.

First of all, you can cheat. The CPU's assembly language usually has an arithmetic shift right instruction, so you can just use that. For example, with the x86 instruction sar (shift arithmetic right):

    int arith(int x, unsigned k) {
      asm volatile ("sar %1,%0" :"+r"(x) :"c"((unsigned char)k));
      return x;
    }

Notice that I have changed your function signature a bit. Semantically, the number of bits k being shifted right should never be negative. (If you want to shift left, use that.) In fact, the paragraph (#3) before the one quoted above states (referring to both << and >>):

If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.

So k should never be negative under any circumstances, and I have made it unsigned. I changed x to a signed int so that it can be tested and examined with negative numbers (more on this below). The return type was changed to match.

Of course this assembly solution only works on an x86 machine. Something universal might be better. This answer to Shift operator in C has a nice summary of the mathematics of bit shifting. For a nonnegative x, arithmetic shift right by k bits has the same result as integer division by 2k. The difference is where x is negative, integer division is rounded towards zero and arithmetic shift right is rounded to negative infinity. This leads to

    int arith(int x, unsigned k) {
      if ( x >= 0 ) {
        // Same as integer division for nonnegative x
        return x/(1<<k);
      } else {
        // If negative, divide but round to -infinity
        return x/(1<<k) - ( x % (1<<k) == 0 ? 0 : 1 );
      }
    }

The ternary operator is to decide if there is any remainder in the division. If there is, a 1 is subtracted to "round down." The 1<<k of course is a simple way to calculate 2k. Notice that this version requires x to be a signed type, otherwise x >= 0 would always evaluate to true.

Community
  • 1
  • 1
e0k
  • 6,961
  • 2
  • 23
  • 30
  • Thanks for the reply, e0k. Do you have a suggestion for a different approach that I can try? – Novice Jan 30 '16 at 09:39
  • Both of your functions worked, but I couldn't use either (the first because I don't understand it and the second because because the exercise says not to use division). It turns out the code I posted actually works (see above); I just didn't know how to test it properly. Since you explained why my code might not work, I accepted your answer. Thanks for the help. – Novice Jan 31 '16 at 01:15
0

You just need to create a mask with k leftmost 1's (if the number was negative), which is the sign expansion done by an arithmetic shift:

unsigned mask = x > 0x8000 ? (0xFFFF << (16-k)): 0;

Then apply the mask to the logical shift result

unsigned x_arith = mask | (x >> k);

Note: I am assuming 16 bit unsigned, otherwise you need to adjust using sizeof(), since this is homework I leave that bit to you

ilomambo
  • 8,290
  • 12
  • 57
  • 106
  • This doesn't seem to work. I tried the function with 4 and a shift of 1 and the result was still 4 (should be 6, right?). Similarly with -4. Without the ternary operator, I get -2147483644, like I was getting once before :( – Novice Jan 30 '16 at 09:37
  • Not 6. Let see binary what should happen: `4 = 00000000 00000100` shift right arith 1 should be: `0 -> 00000000 00000100 = 00000000 00000100` still 4. The arithmetic shift extends the sign bit, which is the leftmost binary bit, in this case 0 gets extended to 0. If you have `10000000 00000100` (32722 unsigned, -32764 signed) then shift by 1 would be: `1 -> 10000000 00000100 = 11000000 00000010` which is 49154 unsigned (-16382 signed) – ilomambo Jan 30 '16 at 10:07
  • @ilomambo Why is shifting 4 (binary 0100) right once not 2 (binary 0010)? – e0k Jan 30 '16 at 10:35
  • Sorry, you are correct, shifting positive 4 always is 2. The result bits after shift 1 in my previous comment should be `00000000 00000010` – ilomambo Jan 30 '16 at 15:28