在C语言中对负数进行右移操作

43

我有一段 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。

我的理解正确吗?


右移位功能作为“地板除法”。它总是向负无穷大四舍五入。在Python语法中:-5 // 4 = -2 - Aaron Franke
6个回答

59

看起来你的实现可能在使用补码数字进行算术位移。在这种系统中,它会将所有位向右移动,然后用最后一位的一个副本填充上部位。所以对于你的示例,在这里将int视为32位:

nPosVal = 00000000000000001111111111111111
nNegVal = 11111111111111110000000000000001

转换后,你得到:

nPosVal = 00000000000000000111111111111111
nNegVal = 11111111111111111000000000000000

如果将其转换回十进制,分别得到32767和-32768。
有效地,右移向负无穷大舍入。
编辑:根据最新的草案标准第6.5.7节,负数的这种行为取决于实现:
E1 >> E2的结果是E1向右移动E2位。如果E1具有无符号类型或E1具有带符号类型且非负值,则结果的值是E1 / 2E2商的整数部分。如果E1具有带符号类型和负值,则得到的值是实现定义的。
他们对此的理由是:
C89委员会确认了K&R授予的实现自由,不要求带符号的右移操作进行符号扩展,因为这样的要求可能会减慢快速代码的速度,并且符号扩展移位的用处很小。(算术右移一个负的二进制补码整数一个位置不等于除以二!)
所以理论上它是实现相关的。实际上,当左操作数为带符号数时,我从未见过实现执行算术右移。

+1,这正是我想知道的。右移操作向负无穷大舍入。但是它有文档记录吗? - Alphaneo
3
这取决于具体的实现。(请参见我上面的编辑。)尽管如此,我从未见过在此方面有所不同的实现,但理论上可能会有差异。 - Boojum

23

不,当使用整数时,您不会得到像0.5这样的分数。当您查看这两个数字的二进制表示时,可以很容易地解释结果:

      65535: 00000000000000001111111111111111
     -65535: 11111111111111110000000000000001

将位向右移动一位,并在左侧扩展(请注意,这取决于实现方式,感谢Trent):

 65535 >> 1: 00000000000000000111111111111111
-65535 >> 1: 11111111111111111000000000000000

转换回十进制:

 65535 >> 1 = 32767
-65535 >> 1 = -32768

9
请注意,向左扩展是与具体实现相关的。 - Trent
3
标准规定:“如果右操作数的值为负数或大于或等于升级后的左操作数的宽度,则行为未定义。” - Gonzalo
@Trent:你确定吗?我认为符号扩展取决于左操作数的有符号性。 - Gunther Piez
2
@Gonzalo:标准确实是这样说的,但在这种情况下,右操作数是1。它不是负数,也不大于int的宽度。 - Steve Jessop

10

C规范并没有规定符号位是否会移位,这取决于具体的实现。


问题1问是否预期结果。我的答案表明,没有,您不能在未首先查阅编译器文档的情况下预期负数的右移结果。 - Trent
你能给我一个标准的链接吗?以便我查看相关内容。 - Gunther Piez

3
当你进行右移操作时,最低有效位将被丢弃。
0xFFFF = 0 1111 1111 1111 1111,右移后得到 0 0111 1111 1111 1111 = 0x7FFF。
-0xFFFF = 1 0000 0000 0000 0001(二进制补码),右移后得到 1 1000 0000 0000 0000 = -0x8000。

3

A-1: 是的。0xffff >> 1 的结果是0x7fff或32767。我不确定-0xffff会发生什么。这很奇怪。

A-2: 移位操作并不等同于除法。它是一种原始的二进制操作。虽然有时可以用于某些类型的除法,但并不总是相同的。


整数字面量是有符号整数,因此“-0xFFFF”取反了“0xFFFF”。也就是说,它等于“(~0xFFFF)-1”,被解释为有符号整数。 - outis

2
在C级别以下,机器有一个CPU核心,完全是整数或标量。虽然现在每个桌面CPU都有FPU,但并非总是如此,即使今天嵌入式系统也没有浮点指令。
当今的编程范例、CPU设计和语言都来自于可能甚至不存在FPU的时代。
因此,CPU指令实现固定点操作,一般被视为纯整数操作。只有当程序声明了float或double类型的项目时,才会存在任何小数部分。(好吧,你可以使用带小数的"固定点"CPU操作,但这在现在和以前都相当罕见。)
不管语言标准委员会多年前需要什么,所有合理的机器都在有符号数的右移位运算中传播符号位。无符号值的右移位向左移入零。右移出的位将被丢弃。
为了进一步理解,您需要研究"二进制补码算术"。

我认为你对“合理的机器”的定义过于狭隘了。 - Trent
如果我的定义太狭隘,请指出一个单一的机器,使用C语言中的x>>1可以将负数转换为正数。 - DigitalRoss
Microchip C18编译器(用户指南链接:http://tinyurl.com/ybt2svs - 参见B.4节) - Trent
嘿,好的。我只能说除非特例,在非特殊情况下证明规则,也被称为“证明规则的例外”,参见:http://en.wikipedia.org/wiki/Exception_that_proves_the_rule。 - DigitalRoss
+1 ;-),感谢您对FP操作和整数操作的解释。实际上,我一直在想关于2的补码数字右移的奇怪行为。这与除法不同。 - Alphaneo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接