简体   繁体   English

负数:如何将signed int中的符号位更改为0?

[英]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> . TL; DR:最简单的方法是使用<stdlib.h>abs函数。 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 . 负整数(几乎总是)以2的补码形式表示 (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). 取整数的二进制表示(包括数据类型的前导零,除了将用作符号位的MSB)。
  2. Take the 1's complement of the above number. 取上述数字的1的补码。
  3. Add 1 to the 1's complement. 在1的补码中加1
  4. Prefix a sign bit . 前缀符号位

Using 500 as an example, 500为例,

  1. Take the binary representation of 500 : _000 0001 1111 0100 ( _ is a placeholder for the sign bit). 取二进制表示500_000 0001 1111 0100_是符号位的占位符)。
  2. Take the 1's-complement / inverse of it: _111 1110 0000 1011 取1的补码/倒数: _111 1110 0000 1011
  3. Add 1 to the 1's complement: _111 1110 0000 1011 + 1 = _111 1110 0000 1100 . 在1的补码中加1_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. 当您将符号位替换为零时,这与您获得的2147483148相同。
  4. Prefix 0 to show a positive number and 1 for a negative number: 1111 1110 0000 1100 . 前缀0表示正数, 1表示负数: 1111 1110 0000 1100 (This will be different from 2147483148 above. The reason you got the above value is because you nuked the MSB). (这与上面的2147483148不同。你得到上述值的原因是因为你获得了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. 如果您使用16位或32位数字导致您看到的大值,那么您将得到领先的数字。 The LSB should be the same in each case. 在每种情况下LSB应该相同。

Note: there are machines with 1's complement representation, but they are a minority. 注意:有些机器具有1的补码表示,但它们是少数。 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. 2的补码通常是首选,因为0具有相同的表示,即-00在2的补码表示法中表示为全零。

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; 你可以使用你的代码,如果你做a = (unsigned int)a << 1; . You'd get 500 = 0xFFFFFE0C , left-shifted 1 = 0xFFFFFC18 . 你得到500 = 0xFFFFFE0C ,左移1 = 0xFFFFFC18

a = (unsigned int)a >> 1; does indeed guarantee logical shift, so you get 0x7FFFFE0C . 确实保证逻辑移位,所以你得到0x7FFFFE0C This is decimal 2147483148. 这是小数2147483148。

But this is needlessly complex. 但这是不必要的复杂。 The best and most portable way to change the sign bit is simply a = -a . 更改符号位的最佳和最便携的方法是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. 这可以移植到32位系统,因为(int32_t)保证二进制补码,但1u << 31假设32位int类型。

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. 当您放入“另外”部分时,在第一次左移1位后,DOES按预期反映-1000。

The issue is in your cast to unsigned int. 问题在于你的转换为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). 如上所述,负数表示为2的补码,意味着符号由最左位(最高有效位)确定。 When cast to an unsigned int, that value no longer represents sign but increases the maximum value your int can take. 当转换为unsigned int时,该值不再表示符号,但会增加int可以采用的最大值。

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. 假设32位整数,MSB用于表示-2 ^ 31(= -2147483648),现在在无符号整数中表示正2147483648,增加2 * 2147483648 = 4294967296.将此值添加到原始值-1000并且您得到4294966296.右移将此除以2,然后到达2147483148。

Hoping this may be helpful: (modified printing func from Print an int in binary representation using C ) 希望这可能会有所帮助:( 使用C二进制表示中打印一个int修改打印函数)

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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