【问题标题】:negative integer number >> 31 = -1 not 1? [duplicate]负整数 >> 31 = -1 不是 1? [复制]
【发布时间】:2013-06-21 00:13:21
【问题描述】:

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

-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。

【问题讨论】:

  • 很确定这仍然是 C 中未定义的行为。也就是说,对于其他系统会发生什么没有硬性规定。
  • @cHao 我记得听说是否要进行移位算法取决于实现,但是大多数人选择这样做
  • @aaronman:是的,显然标准(或我的草案副本,至少)说它是实现定义的。哎呀。 :)
  • 如果你想用它作为分支优化或其他东西的测试,只需在之后屏蔽它:(x >> 31) & 1。或者更好的是,将其视为未签名:(unsigned)x >> 31.
  • @paddy:或者使用(x < 0)

标签: c


【解决方案1】:

根据 C99 6.5.7 移位运算符:

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

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

【讨论】:

  • @aaronman:因为他的答案是唯一正确的。
  • @liori 实际上他甚至不是 OP 的答案
  • @aaronman:就 C 语言而言,这是唯一正确的答案......
  • @dgamma3 它的实现已定义,因此 OP 的实现将其定义为算术移位
  • @dgamma3:如果您要使用符号幅度或反码表示来实现有符号算术,那么这种转变意味着什么? “没有自然的最佳选择”这一事实是它明确实现定义的原因。实现可以选择最适合它的方法(例如,与硬件最接近)。
【解决方案2】:

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

C 2011 6.5.7 移位运算符

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

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

【讨论】:

  • +1 建议将unsigned int 作为解决方案
  • @Code-Guru 谢谢,老实说,我不明白为什么当被投票的答案甚至不是答案时,你的答案和另一个人却被否决了
  • 是的,@JeffWalden 的回答并没有真正解释为什么 OP 会看到这种特殊行为。
  • @dgamma3 unsigned int 不能为负数。所以没有“负值的二元解释”
  • @aaronman:+1,但你应该停止抱怨你的答案的困境。如果您正在回答问题,您的答案通常不会被选中(即使您认为它更好)。
【解决方案3】:

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

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

【讨论】:

  • 不,在 C 中右移一个负值是实现定义的行为。此外,在 C 中,>>> 运算符不存在。
  • @jxh 其他位来自前一位中的内容,这是左移的定义。显然,OP 不知道 MSB 的规则。
  • 不幸的是,我认为他们在 c 中没有 >>>
  • @jxh "这意味着无论您向左移动多少位,MSB 都将始终相同" 此语句仅对 MSB 有保证。我可以很容易地想象一个例子,其中x >>= 1 改变了x每个 位,除了MSB。
  • @Code-Guru:好多了,谢谢。 +1
【解决方案4】:

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


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

在谈论负数时会出现区别。存在几种负数表示,尽管目前最常用的整数是2's complement representation

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

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

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

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


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

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

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

在您的情况下,您的实现可能选择了算术移位,因为您在 x86 处理器上运行,该处理器使用 2 的补码算法和provides both bitwise and arithmetic shift as single CPU instructions


  1. 实际上,像 Java 这样的语言甚至具有分离的算术 位移运算符 - 这主要是因为它们没有 unsigned 类型,例如存储位域。

【讨论】:

    猜你喜欢
    • 2014-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多