【问题标题】:Negative numbers: How can I change the sign bit in a signed int to a 0?负数:如何将有符号整数中的符号位更改为 0?
【发布时间】:2017-01-18 05:56:29
【问题描述】:

我以为这个世界可以正常工作,但事实并非如此:

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

我的想法是左移会删除最左边的符号位,因此将其右移为无符号整数将保证它是逻辑移位而不是算术移位。为什么这是不正确的?

还有:

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

【问题讨论】:

  • 它确实有效,它将符号设置为 0。但也许不是你想要的方式。如果你想用 bitmath 来实现abs,那就比这更棘手了。
  • 很公平,对 MIN_INT 使用标准 abs 有点危险。不是天生的,而是溢出和 UB yada yada.. 你可以只做一个未签名的abs,例如if (x &lt; 0) return -(unsigned)x; else return (unsigned)x;
  • 在 C 和 C++ 中,a &lt;&lt; 1 表示“乘以 2”,而不是“将底层位表示中的位左移 1”。
  • 因为您要求更改SIGN 位(x86 asm 操作码:NOT)而不是将负数转换为正数(x86 asm 操作码:NEG)。这是两个不同的东西。下面的答案解释了这一点并且是正确的。
  • @Hurkyl 有区别吗?无论如何,如果有,它在这里不可见

标签: c bit-manipulation


【解决方案1】:

TL;DR:最简单的方法是使用来自&lt;stdlib.h&gt;abs 函数。剩下的答案涉及到负数在计算机上的表示。

负整数(几乎总是)用2's complement form 表示。 (见下文注释)

求负数的方法是:

  1. 取整数的二进制表示(包括数据类型的前导零,除了用作符号位的 MSB)。
  2. 取上述号码的1's complement
  3. 1 添加到 1 的补码中。
  4. 前缀sign bit

500为例,

  1. 500 的二进制表示:_000 0001 1111 0100_ 是符号位的占位符)。
  2. 取它的 1 的补码/倒数:_111 1110 0000 1011
  3. 1 添加到1 的补码中:_111 1110 0000 1011 + 1 = _111 1110 0000 1100。这与您将符号位替换为零时获得的2147483148 相同。
  4. 前缀0 表示正数,1 表示负数:1111 1110 0000 1100。 (这将与上面的2147483148 不同。您获得上述值的原因是因为您对 MSB 进行了核攻击)。

反转符号是一个类似的过程。如果您使用 16 位或 32 位数字导致您看到的大值,您将获得领先的数字。每种情况下的 LSB 应该相同。

注意:有机器用 1 的补码表示,但它们是少数。通常首选 2 的补码,因为 0 具有相同的表示,即 -00 在 2 的补码表示法中表示为全零。

【讨论】:

  • 我正在研究 gnss 坐标,需要我找出它是 0 还是 -0?我如何识别-0?
【解决方案2】:

左移负整数会调用未定义的行为,因此您不能这样做。如果您使用了a = (unsigned int)a &lt;&lt; 1;,您本可以使用您的代码。你会得到 500 = 0xFFFFFE0C,左移 1 = 0xFFFFFC18

a = (unsigned int)a &gt;&gt; 1; 确实保证了逻辑移位,所以你得到0x7FFFFE0C。这是十进制的 2147483148。

但这是不必要的复杂。 更改符号位的最佳和最便携的方法就是a = -a。任何其他代码或方法都是有问题的。

但是如果你坚持要玩弄比特,你也可以做类似的事情

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

这可移植到 32 位系统,因为 (int32_t) 保证二进制补码,但 1u &lt;&lt; 31 假定为 32 位 int 类型。

演示:

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

【讨论】:

  • 在您的示例中左移 31 是 UB,因为 2^31 在 int32_t 中无法表示。
  • 编辑:假设 int 具有与 int32_t 相同的表示。
  • @2501 lu 后缀不是必需的,因为断言在那里。
  • 对,我忘了。尽管如此, int 可能有两个填充字节。 :-) sizeof 不计算范围。您应该使用 INT_MAX >= 2^31-1。 lu 避免了这一切。
【解决方案3】:

正如您在“也”部分中输入的那样,在您第一次左移 1 位后,a 确实反映了预期的 -1000。

问题在于您将其转换为 unsigned int。如上所述,负数表示为 2 的补码,这意味着符号由最左边的位(最高有效位)确定。当转换为 unsigned int 时,该值不再表示符号,而是增加了 int 可以采用的最大值。

假设 32 位整数,MSB 用于表示 -2^31 (= -2147483648),现在在无符号整数中表示正 2147483648,增加 2* 2147483648 = 4294967296。将此添加到您的原始值 - 1000 得到 4294966296。右移除以 2 得到 2147483148。

希望这可能会有所帮助:(修改打印功能来自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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-01
    • 2017-07-21
    • 1970-01-01
    • 1970-01-01
    • 2014-02-20
    相关资源
    最近更新 更多