简体   繁体   中英

Negative numbers: How can I change the sign bit in a signed int to a 0?

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"

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, ie, -0 and 0 are represented as all-zeroes in the 2's complement notation.

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;
}

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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM