7

所以,假设我有一个有符号整数(几个例子):

-1101363339 = 10111110 01011010 10000111 01110101 in binary.
-2147463094 = 10000000 00000000 01010000 01001010 in binary.
-20552      = 11111111 11111111 10101111 10111000 in binary.

现在:-1101363339 >> 31例如,应该等于 1 对吗?但在我的电脑上,我得到-1。如果 x = 负数,无论我选择什么负整数,x >> 31 = -1. 为什么?显然在二进制中它应该是 1。

4

4 回答 4

20

根据 C99 6.5.7 移位运算符:

如果 E1 具有带符号类型和负值,则结果值是实现定义的。

其中 E1 是移位表达式的左侧。所以这取决于你的编译器你会得到什么。

于 2013-06-21T00:17:34.420 回答
11

在大多数语言中,当您向右移动时,它会进行算术移位,这意味着它会保留最高有效位。因此,在您的情况下,您的二进制全为 1,十进制为 -1。如果你使用 anunsigned int你会得到你正在寻找的结果。

根据C 2011 6.5.7 移位运算符

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

基本上,负符号整数的右移是实现定义的,但大多数实现选择将其作为算术移位。

于 2013-06-21T00:15:56.037 回答
5

您看到的行为称为算术移位,即右移扩展符号位。这意味着 MSB 将携带与原始符号位相同的值。换句话说,负数在左移操作后总是为负数。

请注意,此行为是实现定义的,并且不能用不同的编译器来保证。

于 2013-06-21T00:15:29.960 回答
2

您看到的是算术移位,与您期望的按位移位相反;即,编译器不是“粗暴地”移动位,而是传播符号位,从而除以 2 N


当谈到unsigned ints 和正ints 时,右移是一种非常简单的操作——无论它们的含义如何,这些位都会向右移动一个位置(在左侧插入 0)。在这种情况下,该操作相当于除以 2 N(实际上 C 标准是这样定义的)。

在谈论负数时会出现区别。存在几种负数表示,尽管目前对于整数最常用的是2 的补码表示

对于初学者来说,这里“残酷”的按位移位的问题是,其中一个位以某种方式用于表示符号;因此,无论负整数表示如何,移动二进制数字都会产生意想不到的结果。

例如,通常在 2 的表示中,最高有效位是 1 表示负数,0 表示正数;对负数应用按位移位(在左侧插入零)将(在其他事物之间)使其为正数,而不导致(通常预期的)除以 2 N

因此,引入了算术移位;用 2 的补码表示的负数有一个有趣的特性:如果不是从左侧插入零,而是插入与原始符号位具有相同值的位,则可以保留移位的除以 2 N行为。

通过这种方式,只需在移位中增加一点额外的逻辑,就可以执行 2 N的有符号除法,而无需求助于成熟的除法例程。


现在,是否保证有符号整数的算术移位?在某些语言中是1,但在 C 中并非如此 - 移位运算符在处理负整数时的行为被保留为实现定义的细节。

通常情况下,这是由于对操作的硬件支持不同;C 用在非常不同的平台上,特别是在过去,根据平台的不同,操作的“成本”存在很大差异。

例如,如果处理器不提供算术右移指令,编译器将被要求发出某种慢得多的DIV指令,这可能是较慢处理器的内部循环中的问题。由于这些原因,C 标准让实现者为当前平台做最合适的事情。

在您的情况下,您的实现可能选择了算术移位,因为您在 x86 处理器上运行,该处理器使用 2 的补码算术并提供按位和算术移位作为单 CPU 指令


  1. 实际上,像 Java 这样的语言甚至具有分离的算术位移运算符——这主要是因为它们没有unsigned类型来存储位域。
于 2013-06-21T01:48:14.687 回答