正确的位移给出错误的结果,有人可以解释
我正在向-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。