我有C代码,我在其中执行以下操作。
int nPosVal = +0xFFFF; // + Added for ease of understanding
int nNegVal = -0xFFFF; // - Added for valid reason
现在我尝试
printf ("%d %d", nPosVal >> 1, nNegVal >> 1);
我得到了
32767 -32768
这是预期的吗?
我能够想到像
这样的东西65535 >> 1 = (int) 32767.5 = 32767
-65535 >> 1 = (int) -32767.5 = -32768
即,-32767.5四舍五入为-32768。
这种理解是否正确?
答案 0 :(得分:38)
看起来您的实现可能正在使用两个补码进行算术位移。在这个系统中,它将所有位向右移动,然后用最后一位的副本填充高位。因此,对于您的示例,将int视为32位:
nPosVal = 00000000000000001111111111111111
nNegVal = 11111111111111110000000000000001
转变之后,你得到了:
nPosVal = 00000000000000000111111111111111
nNegVal = 11111111111111111000000000000000
如果将其转换回十进制,则分别得到32767和-32768。
实际上,右移向负无穷大转移。
编辑:根据最新draft standard的第6.5.7节,负数上的这种行为取决于实现:
E1的结果> > E2是E1右移E2位位置。如果E1具有无符号类型或者E1具有有符号类型和非负值,则结果的值是E1 / 2 E2 的商的整数部分。如果E1具有有符号类型和负值,则结果值是实现定义的。
他们声明的rational:
C89委员会肯定了K& R授予的实施自由,不要求签署权利转移操作签署延期,因为这样的要求可能减慢快速代码,因为符号扩展转换的有用性是微不足道的。 (转移负二的补码 整数算术右边一个地方不和除以2相同!)
所以它的实现依赖于理论。在实践中,我从未见过实现不在左操作数被签名时进行算术移位。
答案 1 :(得分:18)
不,在使用整数时,不会得到像0.5这样的小数。当您查看两个数字的二进制表示时,可以很容易地解释结果:
65535: 00000000000000001111111111111111
-65535: 11111111111111110000000000000001
位向右移位一位,并在左侧延伸(请注意,这取决于实现,感谢Trent):
65535 >> 1: 00000000000000000111111111111111
-65535 >> 1: 11111111111111111000000000000000
转换回十进制:
65535 >> 1 = 32767
-65535 >> 1 = -32768
答案 2 :(得分:7)
C规范未指定符号位是否被移位。它取决于实现。
答案 3 :(得分:3)
当你右移时,最低有效位被丢弃。
0xFFFF = 0 1111 1111 1111 1111,右移给0 0111 1111 1111 1111 = 0x7FFF
-0xFFFF = 1 0000 0000 0000 0001(2s补码),右移至1 1000 0000 0000 0000 = -0x8000
答案 4 :(得分:3)
A-1:是的。 0xffff>> 1是0x7fff或32767.我不确定-0xffff是做什么的。这很特别。
A-2:转移与分裂不是一回事。它是位移 - 一个原始的二进制操作。它有时可以用于某些类型的划分是方便的,但并不总是相同。
答案 5 :(得分:2)
在C级别下,计算机的CPU核心完全是整数或标量。虽然现在每个桌面CPU都有一个FPU,但情况并非总是如此,即使在今天,嵌入式系统也没有浮点指令。
今天的编程范例和CPU设计和语言可以追溯到FPU可能不存在的时代。
因此,CPU指令实现定点操作,通常被视为纯整数操作。只有当程序声明 float 或 double 的项目时,才会存在任何分数。 (好吧,你可以将CPU操作用于分数的“固定点”,但现在这种情况并非常罕见。)
无论多年前语言标准委员会要求什么,所有合理的机器都会在有符号数的右移上传播符号位。无符号值的右移在左侧以零的形式移位。向右移出的位落在地板上。
为了进一步了解,您需要调查“二次补码算术”。