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

我正在向-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上使用了相同的结果。

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

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

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

 00000000 01101101 

如果你拿到109 2的补码,你会得到:

 11111111 10010011 

然后,你正确地移动5号码11111111 10010011

 __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 

右移右值的结果是实施定义的行为,来自C99标准草案第6.5.7节“ 按位移位算子”5段所述( 强调我的前进 ):

E1 >> E2的结果是E1右移E2位的位置。 如果E1具有无符号类型或者E1具有有符号类型和非负值,则结果的值是E1 / 2E2的商的整数部分。 如果E1具有带符号类型和负值,则结果值是实现定义的

如果我们看一下整数部分下的gcc C实现定义的行为文档,它说:

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

按位运算符作用于值的表示,包括符号和值位,其中符号位被认为紧接在最高值位之上。 签名“>>”通过符号扩展对负数进行处理。

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

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

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

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

1111 1111 1001 0011

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

1XXX X111 1111 1100 | 1 0011

X是整数位表示中出现的新空格,由于符号扩展,由1填充,这为您提供:

1111 1111 1111 1100 | 1 0011

所以通过移动:-109 >> 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。

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来填充左边的位(如果在移位之前初始数字是负数),或者是0s(如果在移位之前初始数字是正数)。

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

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

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

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

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