我正在向-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上使用了相同的结果。
答案 0 :(得分:4)
问题是你没有正确考虑负数表示。通过右移,移位类型(算术或逻辑)取决于被移位的值的类型。如果将值转换为无符号值,则可能会得到您期望的值:
int16_t b = ((unsigned int)a) >> 5;
您在示例中使用-109
(16位)。位109
是:
00000000 01101101
如果你选择了109
2补集,你会得到:
11111111 10010011
然后,您正好将数字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
答案 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。