正确的位移给出错误的结果,有人可以解释

时间:2014-04-19 23:35:59

标签: c gcc clang bit-shift

我正在向-109移位-109​​,我期待-3,因为 -109 = -1101101(二进制) 向右移5位 -1101101>> 5 = -11(二进制)= -3

但是,我得到了-4。 有人可以解释什么是错的吗?

我使用的代码:

int16_t a = -109;
int16_t b = a >> 5;
printf("%d %d\n", a,b);

我在linux上使用了GCC,并在osx上使用了相同的结果。

4 个答案:

答案 0 :(得分:4)

问题是你没有正确考虑负数表示。通过右移,移位类型(算术或逻辑)取决于被移位的值的类型。如果将值转换为无符号值,则可能会得到您期望的值:

int16_t b = ((unsigned int)a) >> 5;

您在示例中使用-109(16位)。位109是:

00000000 01101101

如果你选择了109 2补集,你会得到:

11111111 10010011

然后,您正好将数字11111111 10010011

移动5
__int16_t a = -109;
__int16_t b = a >> 5; // arithmetic shifting
__int16_t c = ((__uint16_t)a) >> 5; // logical shifting
printf("%d %d %d\n", a,b,c);

将屈服:

-109 -4 2044

答案 1 :(得分:2)

右移负值的结果是实现定义的行为,来自C99标准草案部分6.5.7 按位移位运算符 5 ,其中说明了(< em>强调我的前进):

  

E1&gt;的结果&gt; E2是E1右移E2位位置。如果E1具有无符号类型   或者如果E1具有带符号类型和非负值,则结果的值为积分   E1 / 2E2商的一部分。 如果E1有签名类型和负值,则   结果值是实现定义的

如果我们查看C Implementation-defined behavior部分下的gcc Integers个文件,则说明:

  

对有符号整数进行一些按位运算的结果(C90 6.3,C99和C11 6.5)。

     

按位运算符作用于值的表示,包括符号和值位,其中符号位被认为位于最高值位之上。 签名'&gt;&gt;'按符号扩展名对负数行。

答案 2 :(得分:1)

很清楚发生了什么,当表示有符号整数时,负整数有一个属性,即符号扩展,而最左边的有效位是符号位。

因此,1000 ... 0000(32位)是您可以用32位表示的最大负数。

因此,当你有一个负数并向右移动时,会发生一个叫做符号扩展的事情,这意味着最左边的有效位被扩展,简单来说就意味着,对于像-109这样的数字是怎么回事:

在转换之前你有(16位):

  

1111 1111 1001 0011

然后你向右移5位(在管道被丢弃的位之后):

  

1XXX X111 1111 1100 | 1 0011

X是你的整数位表示中出现的新空格,由于符号扩展,用1&#39; s填充,它们为你提供:

  

1111 1111 1111 1100 | 1 0011

所以通过转移:-109&gt;&gt; 5,你得到-4(1111 .... 1100)而不是-3。

使用1补码确认结果:

  

+3 = 0 ... 0000 0011

     

-3 =〜(0 ... 0000 0011)+ 1 = 1 ... 1111 1100 + 1 = 1 ... 1111 1101

     

+4 = 0 ... 0000 0100

     

-4 =〜(0 ... 0000 0100)+ 1 = 1 ... 1111 1011 + 1 = 1 ... 1111 1100

注意:请记住1的补码就像2的补码一样,你首先必须否定正数的位,然后才加+1。

答案 3 :(得分:0)

Pablo的答案基本上是正确的,但有两个小位(没有双关语!)可以帮助你看看发生了什么。

C(就像几乎所有其他语言一样)使用所谓的二进制补码,这只是表示负数的一种不同方式(它用于避免出现其他方式处理二进制中带负数的问题。位数)。有一个转换过程可以在二进制补码中转换一个正数(看起来就像二进制中的任何其他数字 - 除了最左边的位在正数中必须为0;它基本上是符号占位符)相当简单计算:

拿你的号码

00000000 01101101(它有0s填充到左边,因为它是16位。如果它很长,它会被填充更多的零等。)

翻转位

11111111 10010010

添加一个。

11111111 10010011.

这是Pablo所指的两个补码。这就是C如何保持-109,按位。

当你在逻辑上将它向右移动五位时,你会看到

00000111 11111100。

这个数字绝对不是-4。 (它在第一位没有1,所以它不是负数,而且它的数量太大而不是4。)为什么C给你负4呢?

原因基本上是C的ISO实现没有指定给定编译器如何处理负数的位移。 GCC执行所谓的符号扩展:理念是基本填充左边的位为1(如果初始数字在移位前为负),或者为0(如果在移位之前初始数字为正)。

因此,不是在上面的位移中发生的5个零,而是得到:

11111111 11111100.这个数字实际上是负数4! (这是你一直得到的结果。)

要看到实际上是-4,你可以再次使用二进制补码方法将其转换回正数:

00000000 00000011(位翻转) 00000000 00000100(加一)。

这四个没问题,所以原来的号码(11111111 11111100)是-4。