6

In case of unsigned short I shifted 383 by 11 positions towards left and again in the same instruction shifted it by 15 positions right, I expected the value to be 1 but it was 27. But when I used both the shift operations in different instructions one after another(first left shift and then right), output was 1. here is a sample code :-

unsigned short seed = 383;
printf("size of short: %d\n",sizeof(short));
unsigned short seedout,seed1,seed2,seed3,seedout1;
seed1 = (seed<<11);
seed2 = (seed1>>15);
seed3 = ((seed<<11)>>15);
printf("seed1 :%d\t  seed2: %d\t seed3: %d\n",seed1,seed2,seed3);

and its output was :

size of short: 2
seed1 :63488      seed2: 1       seed3: 23    
seedout1: 8     seedout :382
Process returned 0 (0x0)   execution time : 0.154 s
dragosht
  • 3,237
  • 2
  • 23
  • 32
Mrityu
  • 71
  • 1
  • 4
  • If you shift left then 0's come into the LSB as you shift and bits shifted off the MSB are lost. If you shift right then the same happens but starting as the MSB and bits shift off the LSB. In your example an unsigned short is 16 bits, your seed value starts at 383 decimal, 0001 0111 1111 binary or 17F hex. Shifting it left 11 bits will result in 1111 1000 0000 0000. – SPlatten Jan 10 '20 at 08:00

3 Answers3

7

For clarity, you compare

unsigned short seed1 = (seed<<11);
unsigned short seed2 = (seed1>>15);

on one hand and

unsigned short seed3 = ((seed<<11)>>15);

on the other hand.

The first one takes the result of the shift operation, stores it in an unsigned short variable (which apparently is 16 bit on your platform) and shifts this result right again.

The second one shifts the result immediately.

The reason why this is different resp. the bits which are shifted out to the left are retained is the following:

Although seed is unsigned short, seed<<11 is signed int. Thus, these bits are not cut off as it is the case when storing the result, but they are kept in the intermediate signed int. Only the assignment to seed1 makes the value unsigned short, which leads to a clipping of the bits.

In other words: your second example is merely equivalent to

int seed1 = (seed<<11); // instead of short
unsigned short seed2 = (seed1>>15);
Alex Celeste
  • 12,824
  • 10
  • 46
  • 89
glglgl
  • 89,107
  • 13
  • 149
  • 217
6

Regarding left shifting, type signedness & implicit promotion:

Whenever something is left shifted into the sign bit of a signed integer type, we invoke undefined behavior. Similarly, we also invoke undefined behavior when left-shifting a negative value.

Therefore we must always ensure that the left operand of << is unsigned. And here's the problem, unsigned short is a small integer type, so it is subject to implicit type promotion whenever used in an expression. Shift operators always integer promote the left operand:

C17 6.5.7:

The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand.

(This makes shifts in particular a special case, since they don't care about the type of the right operand but only look at the left one.)

So in case of a 16 bit system, you'll run into the case where unsigned short gets promoted to unsigned int, because a 16 bit int cannot hold all values of a 16 bit unsigned short. And that's fine, it's not a dangerous conversion.

On a 32 bit system however, the unsigned short gets promoted to int which is signed. Should you left shift a value like 0x8000 (MSB) set 15 bits or more, you end up shifting data into the sign bit of the promoted int, which is a subtle and possibly severe bug. For example, this prints "oops" on my Windows computer:

#include <stdio.h>

int main (void)
{
  unsigned short x=0x8000;
  if((x<<16) < 0) ) // undefined behavior
    puts("oops");
}

But the compiler could as well have assumed that a left shift of x can never result in a value < x and removed the whole machine code upon optimization.

We need to be sure that we never end up with a signed type by accident! Meaning we must know how implicit type promotion works in C.


As for left-shifting unsigned int or larger unsigned types, that's perfectly well-defined as long as we don't shift further than the width of the (promoted) type itself (more than 31 bits on a 32 bit system). Any bits shifted out well be discarded, and if you right shift, it will always be a logical shift where zeroes are shifted in from the right.


To answer the actual question:

Your unsigned short is integer promoted to an int on a 32 bit system. This allows to shift beyond the 16 bits of an unsigned short, but if you discard those extra bits by saving the result in an unsigned short, you end up with this:

383 = 0x17F  
0x17f << 11 = 0xBF800  
0xBF800 truncated to 16 bits = 0xF800 = 63488
0xF800 >> 15 = 0x1

However, if skipping the middle step truncation to 15 bits, you have this instead:

0xBF800 >> 15 = 0x17 = 23

But again, this is only by luck since this time we didn't end up shifting data into the sign bit.

Another example, when executing this code, you might expect to get either the value 0 or the value 32768:

unsigned short x=32768;
printf("%d", x<<16>>16);

But it prints -32768 on my 2's complement PC. The x<<16 invokes undefined behavior, and the >>16 then apparently sign extended the result.

These kind of subtle shift bugs are common, particularly in embedded systems. A frightening amount of all C programs out there are written by people who didn't know about implicit promotions.

Lundin
  • 195,001
  • 40
  • 254
  • 396
-2

I shifted 383 by 11 positions towards left and again in the same instruction shifted it by 15 positions right, I expected the value to be 1 but it was 27

Simple math, you've shifted it 4 bits to the right, which is equivalent to dividing by 16.

Divide 383 by 16, and you get 27 (integer-division, of course).

Note that the "simply shifted it 4 bits" part holds because:

  1. You've used an unsigned operand, which means that you did not "drag 1s" when shifting right
  2. The shift-left operation likely returns an unsigned integer (32 bits) on your platform, so no data was loss during that part.

BTW, with regards to the 2nd bullet above - when you do this in parts and store the intermediate result into an unsigned short, you do indeed lose data and get a different result.

In other words, when doing seed<<11, the compiler uses 32-bit operations, and when storing it into seed1, only the LSB part of the previous result is preserved.

EDIT:

27 above should be 23. I copied that from your description without checking, though I see that you did mention 23 further down your question, so I'm assuming that 27 was a simple typo...

goodvibration
  • 5,980
  • 4
  • 28
  • 61
  • 2
    Simple math, 383/16 is 23.9375; 23 with integer division. Not 27. – Yunnosch Jan 10 '20 at 08:09
  • @Yunnosch: Hahaha, good one. I assumed, but you know what they say about assumptions... In any case, I just copy-pasted from his description, which is probably a typo, since in the actual printout he indeed wrote 23. – goodvibration Jan 10 '20 at 08:11
  • Please spend the time to [edit], for the purpose of being a good example of at least fixing the errors caused by making unverified assumptions. – Yunnosch Jan 10 '20 at 08:14
  • 1
    Re “The shift-left operation likely returns an unsigned integer (32 bits) on your platform”: No. The type of the result of `<<` depends on the operand. In this case, it is `int`, because the standard integer promotions convert `unsigned short` to `int`. It is not an unsignef 32-bit integer. – Eric Postpischil Jan 10 '20 at 08:31
  • The type used isn't very relevant, the integer promoted type is what matters. It is `int` on a 32 bit system. So both 1. and 2. are misleading. I've posted an answer explaining this more in detail. – Lundin Jan 10 '20 at 10:18