1

I'm right-shifting -109 by 5 bits, and I expect -3, because -109 = -1101101 (binary) shift right by 5 bits -1101101 >>5 = -11 (binary) = -3

But, I am getting -4 instead. Could someone explain what's wrong?

Code I used:

int16_t a = -109;
int16_t b = a >> 5;
printf("%d %d\n", a,b);

I used GCC on linux, and clang on osx, same result.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
smhx
  • 2,246
  • 18
  • 22
  • Right shift a signed type often results in arithmetics shift right, which means the value is signed extended, so the result is always rounded towards negative infinity. If you need rounding towards zero, there are lots of question on this site addressing this, do a search http://stackoverflow.com/questions/1463736/right-shift-division-round-toward-zero – phuclv Apr 20 '14 at 01:56
  • 1
    Since `a` is a signed type and a negative value, the resulting value of a right shift is implementation-defined. Portable code can not depend on what value may result. See @Shafik Yaghmour – chux - Reinstate Monica Apr 20 '14 at 04:53
  • Your system isn't using sign/magnitude `int16_t`. In fact, the optional fixed-width types like `int16_t` are required to be 2's complement, if they exists at all; only types like `int` and `short` are allowed to be sign/magnitude on weird ancient hardware that actually works that way. – Peter Cordes Jan 01 '22 at 09:32

4 Answers4

4

The thing is you are not considering negative numbers representation correctly. With right shifting, the type of shift (arithmetic or logical) depends on the type of the value being shifted. If you cast your value to an unsigned value, you might get what you are expecting:

int16_t b = ((unsigned int)a) >> 5;

You are using -109 (16 bits) in your example. 109 in bits is:

00000000 01101101

If you take's 109 2's complement you get:

11111111 10010011

Then, you are right shifting by 5 the number 11111111 10010011:

__int16_t a = -109;
__int16_t b = a >> 5; // arithmetic shifting
__int16_t c = ((__uint16_t)a) >> 5; // logical shifting
printf("%d %d %d\n", a,b,c);

Will yield:

-109 -4 2044
Pablo Santa Cruz
  • 176,835
  • 32
  • 241
  • 292
  • thanks, will read more on this, and how signage affects shifting. – smhx Apr 19 '14 at 23:44
  • What is `__int16_t`? `int16_t` is already a standard type in ``, guaranteed to be 16-bit 2's complement if it exists at all (it's optional; C implementations for ancient/weird CPUs that use 1's complement or sign/magnitude normally wouldn't provide `int16_t` at all.) https://en.cppreference.com/w/c/types/integer – Peter Cordes Jan 01 '22 at 09:44
3

The result of right shifting a negative value is implementation defined behavior, from the C99 draft standard section 6.5.7 Bitwise shift operators paragraph 5 which says (emphasis mine going forward):

The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1 / 2E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.

If we look at gcc C Implementation-defined behavior documents under the Integers section it says:

The results of some bitwise operations on signed integers (C90 6.3, C99 and C11 6.5).

Bitwise operators act on the representation of the value including both the sign and value bits, where the sign bit is considered immediately above the highest-value value bit. Signed ‘>>’ acts on negative numbers by sign extension.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
1

That's pretty clear what's happening, when representing signed integers, negative integers have a property which is, sign extension, and the left most significant bit is the sign bit.

So, 1000 ... 0000 (32 bit) is the biggest negative number that you can represent, with 32 bits.

Because of this, when you have a negative number and you shift right, a thing called sign extension happens, which means that the left most significant bit is extended, in simple terms it means that, for a number like -109 this is what happens:

Before shifting you have (16bit):

1111 1111 1001 0011

Then you shift 5 bits right (after the pipe are the discarded bits):

1XXX X111 1111 1100 | 1 0011

The X's are the new spaces that appear in your integer bit representation, that due to the sign extension, are filled with 1's, which give you:

1111 1111 1111 1100 | 1 0011

So by shifting: -109 >> 5, you get -4 (1111 .... 1100) and not -3.

Confirming results with the 1's complement:

+3 = 0... 0000 0011

-3 = ~(0... 0000 0011) + 1 = 1... 1111 1100 + 1 = 1... 1111 1101

+4 = 0... 0000 0100

-4 = ~(0... 0000 0100) + 1 = 1... 1111 1011 + 1 = 1... 1111 1100

Note: Remember that the 1's complement is just like the 2's complement with the diference that you first must negate the bits of positive number and only then sum +1.

João Pinho
  • 3,725
  • 1
  • 19
  • 29
0

Pablo's answer is essentially correct, but there are two small bits (no pun intended!) that may help you see what's going on.

C (like pretty much every other language) uses what's called two's complement, which is simply a different way of representing negative numbers (it's used to avoid the problems that come up with other ways of handling negative numbers in binary with a fixed number of digits). There is a conversion process to turn a positive number in two's complement (which looks just like any other number in binary - except that the furthest most left bit must be 0 in a positive number; it's basically the sign place-holder) is reasonably simple computationally:

Take your number

00000000 01101101 (It has 0s padding it to the left because it's 16 bits. If it was long, it'd be padded with more zeros, etc.)

Flip the bits

11111111 10010010

Add one.

11111111 10010011.

This is the two's complement number that Pablo was referring to. It's how C holds -109, bitwise.

When you logically shift it to the right by five bits you would APPEAR to get

00000111 11111100.

This number is most definitely not -4. (It doesn't have a 1 in the first bit, so it's not negative, and it's way too large to be 4 in magnitude.) Why is C giving you negative 4 then?

The reason is basically that the ISO implementation for C doesn't specify how a given compiler needs to treat bit-shifting in negative numbers. GCC does what's called sign extension: the idea is to basically pad the left bits with 1s (if the initial number was negative before shifting), or 0s (if the initial number was positive before shifting).

So instead of the 5 zeros that happened in the above bit-shift, you instead get:

11111111 11111100. That number is in fact negative 4! (Which is what you were consistently getting as a result.)

To see that that is in fact -4, you can just convert it back to a positive number using the two's complement method again:

00000000 00000011 (bits flipped) 00000000 00000100 (add one).

That's four alright, so your original number (11111111 11111100) was -4.

  • ISO C explicitly does *not* guarantee 2's complement. (Except for the optional fixed-width types like `int16_t`; if they exist they're required to be 2's complement, and no padding bits. But signed right-shift is still implementation-defined, and signed overflow of such types is still fully undefined behaviour.) However, most modern mainstream implementations do use 2's complement, e.g. GNU C guaranteeing it for all ISAs it's portable to. And defines the behaviour of right shift on signed integers as arithmetic right shift, shifting in copies of the sign bit. – Peter Cordes Jan 01 '22 at 09:38
  • [C++20 guarantees](https://stackoverflow.com/questions/57363324/ramifications-of-c20-requiring-twos-complement) 2's complement `int`, finally dropping the one's complement and sign/magnitude implementation choices. But C17 didn't do that, and I haven't heard of ISO C planning to make the same change. – Peter Cordes Jan 01 '22 at 09:39