Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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"
like image 287
Mike Avatar asked Oct 15 '25 16:10

Mike


2 Answers

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.

like image 198
user1952500 Avatar answered Oct 18 '25 07:10

user1952500


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;
}
like image 43
Lundin Avatar answered Oct 18 '25 08:10

Lundin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!