4

I was thinking this world work, but it does not:

int a = -500;
a = a << 1;
a = (unsigned int)a >> 1;
//printf("%d",a) gives me "2147483148"

My thought was that the left-shift would remove the leftmost sign bit, so right-shifting it as an unsigned int would guarantee that it's a logical shift rather than arithmetic. Why is this incorrect?

Also:

int a = -500;
a = a << 1;
//printf("%d",a) gives me "-1000"
user1952500
  • 6,611
  • 3
  • 24
  • 37
Mike
  • 961
  • 6
  • 19
  • 43
  • Why not `abs(a)`? – DYZ Jan 18 '17 at 05:58
  • 2
    It does work, it sets the sign to 0. Maybe not in the way you wanted though. If you want to implement `abs` with bitmath, it's a bit more tricky than this. – harold Jan 18 '17 at 05:58
  • When I printf my variable, I get 2147483148 instead of the intended 500. And I can't use abs(a) because I am trying to store the absolute value into an unsigned int, and using abs(a) would have problems with -2,147,483,648, right? – Mike Jan 18 '17 at 06:02
  • I haven't tested it, but why don't you just use `a = a & (~0x80000000);` to clear the uppermost bit. – zx485 Jan 18 '17 at 06:05
  • 1
    Fair enough, using the standard `abs` for MIN_INT is a bit dangerous. Not inherently, but overflow and UB yada yada.. You can just do an unsigned `abs` though, eg `if (x < 0) return -(unsigned)x; else return (unsigned)x;` – harold Jan 18 '17 at 06:06
  • [How do you set, clear and toggle a single bit in C/C++?](http://stackoverflow.com/q/47981/995714) – phuclv Jan 18 '17 at 06:08
  • @zx485 that gives me the same result as my code above. hm. and at harold: that may solve my issue, but I would still like to know why the method I tried doesn't work – Mike Jan 18 '17 at 06:09
  • `(unsigned int)a` => `2^32 - 1000` => `4294966296` so `(unsigned int)a >> 1` => `4294966296 / 2` => `2147483148` – Stargateur Jan 18 '17 at 06:10
  • 1
    In C and in C++, `a << 1` means "multiply by 2", not "shift the bits in the underlying bit representation left by 1". –  Jan 18 '17 at 06:13
  • 2
    Because you asked to change the `SIGN` bit (x86 asm opCode: `NOT`) and not turning a negative number into a positive number(x86 asm opCode: `NEG`). These are two different things. The answer below explains this and is correct. – zx485 Jan 18 '17 at 06:15
  • 2
    @Hurkyl is there a difference? In any case if there is, it wasn't visible here – harold Jan 18 '17 at 06:16
  • You are not in sign-magnitude representation but probably 2's-complement. – Jean-Baptiste Yunès Jan 18 '17 at 07:11
  • Please edit into the question what your expected output was – M.M Jan 18 '17 at 07:28
  • 1
    @Hurkyl No, it shifts bits, including the sign bit. Thus the reason that many rules prohibit/specify certain shifts of signed integers. Shifting and mult/division by two are similar in some contexts. – 2501 Jan 18 '17 at 08:24

3 Answers3

7

TL;DR: the easiest way is to use the abs function from <stdlib.h>. The rest of the answer involves the representation of negative numbers on a computer.

Negative integers are (almost always) represented in 2's complement form. (see note below)

The method of getting the negative of a number is:

  1. Take the binary representation of the whole number (including leading zeroes for the data type, except the MSB which will serve as the sign bit).
  2. Take the 1's complement of the above number.
  3. Add 1 to the 1's complement.
  4. Prefix a sign bit.

Using 500 as an example,

  1. Take the binary representation of 500: _000 0001 1111 0100 (_ is a placeholder for the sign bit).
  2. Take the 1's-complement / inverse of it: _111 1110 0000 1011
  3. Add 1 to the 1's complement: _111 1110 0000 1011 + 1 = _111 1110 0000 1100. This is the same as 2147483148 that you obtained, when you replaced the sign-bit by zero.
  4. Prefix 0 to show a positive number and 1 for a negative number: 1111 1110 0000 1100. (This will be different from 2147483148 above. The reason you got the above value is because you nuked the MSB).

Inverting the sign is a similar process. You get leading ones if you use 16-bit or 32-bit numbers leading to the large value that you see. The LSB should be the same in each case.

Note: there are machines with 1's complement representation, but they are a minority. The 2's complement is usually preferred because 0 has the same representation, i.e., -0 and 0 are represented as all-zeroes in the 2's complement notation.

user1952500
  • 6,611
  • 3
  • 24
  • 37
3

Left-shifting negative integers invokes undefined behavior, so you can't do that. You could have used your code if you did a = (unsigned int)a << 1;. You'd get 500 = 0xFFFFFE0C, left-shifted 1 = 0xFFFFFC18.

a = (unsigned int)a >> 1; does indeed guarantee logical shift, so you get 0x7FFFFE0C. This is decimal 2147483148.

But this is needlessly complex. The best and most portable way to change the sign bit is simply a = -a. Any other code or method is questionable.

If you however insist on bit-twiddling, you could also do something like

(int32_t)a & ~(1u << 31)

This is portable to 32 bit systems, since (int32_t) guarantees two's complement, but 1u << 31 assumes 32 bit int type.

Demo:

#include <stdio.h>
#include <stdint.h>

int main (void)
{
  int a = -500;
  a = (unsigned int)a << 1;
  a = (unsigned int)a >> 1;
  printf("%.8X = %d\n", a, a);

  _Static_assert(sizeof(int)>=4, "Int must be at least 32 bits.");
  a = -500;
  a = (int32_t)a & ~(1u << 31);
  printf("%.8X = %d\n", a, a);

  return 0;
}
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 1
    Left-shift by 31 in your example is UB, because 2^31 isn't representable in int32_t. – 2501 Jan 18 '17 at 08:10
  • Edit: Assuming that int has the same representation as int32_t. – 2501 Jan 18 '17 at 08:22
  • @2501 The `lu` suffix should however not be necessary, since the assert is there. – Lundin Jan 18 '17 at 08:35
  • Right, I forgot about that. Still, int may have two padding bytes. :-) sizeof doesn't calculate the range. You should have used INT_MAX >= 2^31-1. lu avoids all that. – 2501 Jan 18 '17 at 08:36
-1

As you put in the your "Also" section, after your first left shift of 1 bit, a DOES reflect -1000 as expected.

The issue is in your cast to unsigned int. As explained above, the negative number is represented as 2's complement, meaning the sign is determined by the left most bit (most significant bit). When cast to an unsigned int, that value no longer represents sign but increases the maximum value your int can take.

Assuming 32 bit ints, the MSB used to represent -2^31 (= -2147483648) and now represents positive 2147483648 in an unsigned int, for an increase of 2* 2147483648 = 4294967296. Add this to your original value of -1000 and you get 4294966296. Right shift divides this by 2 and you arrive at 2147483148.

Hoping this may be helpful: (modified printing func from Print an int in binary representation using C)

void int2bin(int a, char *buffer, int buf_size) {
    buffer += (buf_size - 1);

    for (int i = buf_size-1; i >= 0; i--) {
        *buffer-- = (a & 1) + '0';

        a >>= 1;
    }
}

int main() {
    int test = -500;
    int bufSize = sizeof(int)*8 + 1;
    char buf[bufSize];
    buf[bufSize-1] = '\0';
    int2bin(test, buf, bufSize-1);
    printf("%i (%u): %s\n", test, (unsigned int)test,  buf);
    //Prints:   -500 (4294966796): 11111111111111111111111000001100


    test = test << 1;
    int2bin(test, buf, bufSize-1);
        printf("%i (%u): %s\n", test, (unsigned int)test, buf);
    //Prints:   -1000 (4294966296): 11111111111111111111110000011000


    test = 500;
    int2bin(test, buf, bufSize-1);
    printf("%i (%u): %s\n", test, (unsigned int)test, buf);
    //Prints:   500 (500): 00000000000000000000000111110100


    return 0;
}
Community
  • 1
  • 1
mcbachman1
  • 16
  • 3