Answers:
向左移动时,算术移位和逻辑移位之间没有区别。向右移位时,移位的类型取决于要移位的值的类型。
(作为不熟悉该差异的读者的背景知识,“逻辑”右移1位会将所有位向右移,并用0填充最左边的位。“算术”移位将原始值保留在最左位。在处理负数时,差异变得很重要。)
移位无符号值时,C中的>>运算符是逻辑移位。移位有符号值时,>>运算符是算术移位。
例如,假设使用32位计算机:
signed int x1 = 5;
assert((x1 >> 1) == 2);
signed int x2 = -5;
assert((x2 >> 1) == -3);
unsigned int x3 = (unsigned int)-5;
assert((x3 >> 1) == 0x7FFFFFFD);
考虑i
和n
分别作为移位运算符的左操作数和右操作数;i
整数提升后的类型为T
。假设n
处于[0, sizeof(i) * CHAR_BIT)
-否则未定义-我们有以下几种情况:
| Direction | Type | Value (i) | Result |
| ---------- | -------- | --------- | ------------------------ |
| Right (>>) | unsigned | ≥ 0 | −∞ ← (i ÷ 2ⁿ) |
| Right | signed | ≥ 0 | −∞ ← (i ÷ 2ⁿ) |
| Right | signed | < 0 | Implementation-defined† |
| Left (<<) | unsigned | ≥ 0 | (i * 2ⁿ) % (T_MAX + 1) |
| Left | signed | ≥ 0 | (i * 2ⁿ) ‡ |
| Left | signed | < 0 | Undefined |
†大多数编译器将其实现为算术移位
•如果value溢出结果类型T,则为undefined;我的晋升类型
首先是从数学的角度来看逻辑和算术转换之间的差异,而不必担心数据类型的大小。逻辑移位始终仅对左移位用零填充被丢弃的比特,而算术移位仅对左移位用零填充它,但对于右移位它复制MSB从而保留操作数的符号(假定负值用二进制补码编码)。
换句话说,逻辑移位将移位后的操作数仅视为比特流,并对其进行移动,而不必担心结果值的符号。算术移位将其视为一个(有符号的)数字,并在进行移位时保留该符号。
X左移n等于将X乘以2 n,因此等于逻辑左移。逻辑上的转换也将产生相同的结果,因为无论如何MSB最终都将失败,并且没有任何可保留的东西。
X的右算术移位n等于X除以2 为负才等于X除以2 n的整数!整数除法不过是数学除法,并且朝0(trunc)舍入。
对于以二进制补码表示的负数,右移n位具有将其数学除以2 n并舍入为-∞(floor)的效果。因此,对于非负值和负值,右移是不同的。
对于X≥0,X >> n = X / 2 n = trunc(X÷2 n)
对于X <0,X >> n = floor(X÷2 n)
其中÷
,数学除法/
是整数除法。让我们看一个例子:
37)10 = 100101)2
37÷2 = 18.5
37/2 = 18(向1取整18.5)= 10010)2 [算术右移的结果]
-37)10 = 11011011)2(考虑二进制补码,8位表示)
-37÷2 = -18.5
-37 / 2 = -18(向0舍入18.5)= 11101110)2 [不是算术右移的结果]
-37 >> 1 = -19(向-∞舍入18.5)= 11101101)2 [算术右移的结果]
正如Guy Steele指出的,这种差异导致了不止一个编译器的错误。在这里,非负数(数学)可以映射为无符号和有符号的非负数(C);两者的处理方法相同,并通过整数除法将它们右移。
因此,逻辑和算术在左移时是等效的,对于非负值在右移时是等效的;它们在负值的右移方面存在差异。
标准C99§6.5.7:
每个操作数应具有整数类型。
对每个操作数执行整数提升。结果的类型是提升后的左操作数的类型。如果右操作数的值为负或大于或等于提升后的左操作数的宽度,则行为是不确定的。
short E1 = 1, E2 = 3;
int R = E1 << E2;
在以上代码段中,两个操作数都变为int
(由于整数提升);如果E2
为负,E2 ≥ sizeof(int) * CHAR_BIT
则操作不确定。这是因为移位多于可用位肯定会溢出。有R
被宣布为short
,该int
移位操作的结果将被隐式转换为short
; 缩小的转换,如果该值无法在目标类型中表示,则可能导致实现定义的行为。
E1 << E2的结果是E1左移E2位位置;空位用零填充。如果E1具有无符号类型,则结果的值为E1×2 E2,比结果类型中可表示的最大值模减少1。如果E1具有带符号的类型和非负值,并且E1×2 E2在结果类型中可表示,则这是结果值;否则,行为是不确定的。
由于两者的左移相同,因此空出的位仅用零填充。然后,它指出,对于无符号和有符号类型,这都是算术转换。我将其解释为算术移位,因为逻辑移位不会影响位表示的值,它只是将其视为位流。但是该标准不是根据位来讨论的,而是根据E1与2 E2的乘积获得的值进行定义的。
需要注意的是,对于有符号类型,该值应为非负值,并且结果值在结果类型中应可表示。否则,该操作是不确定的。结果类型将是应用积分提升后的E1类型,而不是目的地(将保留结果的变量)类型。结果值隐式转换为目标类型。如果无法用该类型表示,则转换是实现定义的(C99§6.3.1.3/ 3)。
如果E1是带负值的带符号类型,则左移行为不确定。这是通往未定义行为的简单途径,该行为很容易被忽略。
E1 >> E2的结果是E1右移E2位的位置。如果E1具有无符号类型,或者E1具有带符号类型且非负值,则结果的值是E1 / 2 E2商的整数部分。如果E1具有带符号的类型和负值,则结果值是实现定义的。
无符号和有符号非负值的右移非常简单;空位用零填充。对于带负号的负值,右移的结果由实现定义。也就是说,大多数实现(例如GCC和Visual C ++)都通过保留符号位来将右移作为算术移位来实现。
不像Java,它有一个特殊的操作者>>>
为除了通常的逻辑移位>>
和<<
,C和C与一些区域未定义和实现定义++只有算术移位。我之所以将它们视为算术运算,是因为标准的数学运算方式不是将移位的操作数视为位流;这也许就是为什么将这些区域保留为未定义/未实现定义的原因,而不仅仅是将所有情况都定义为逻辑转移的原因。
-Inf
,负数和正数都向右舍入。舍入为正数的0是舍入为的私人情况-Inf
。截断时,您始终会丢弃正加权值,因此,您会从其他精确结果中减去。
好吧,我在Wikipedia上查询了一下,他们说:
但是,C仅具有一个右移运算符>>。许多C编译器根据要移位的整数类型选择要执行的右移。通常,使用算术移位对有符号整数进行移位,而使用逻辑移位对无符号整数进行移位。
因此,听起来好像取决于您的编译器。同样在该文章中,请注意,算术和逻辑左移相同。我建议对边界情况(当然是高位集)使用一些带符号和无符号的数字进行简单测试,然后查看编译器上的结果。我还建议避免依赖于一个或另一个,因为看起来C没有标准,至少在合理且有可能避免这种依赖的情况下。
左移 <<
这很容易,每当您使用shift运算符时,它总是按位操作,因此我们不能将其与double和float操作一起使用。每当我们左移一位零时,它总是被加到最低有效位(LSB
)。
但是在右移中,>>
我们必须遵循另一条规则,该规则称为“符号位复制”。“符号位复制”的含义是,如果最高有效位(MSB
)被置位,则在再次右移MSB
后将被置位,如果被复位,则将被再次复位,这意味着如果先前的值为零,则在再次移位后,如果前一位为1,则该位为零,然后在移位后再次为1。此规则不适用于左移。
在右移上最重要的示例是,如果将任何负数移至右移,则在将值进行一些移位后最终达到零,然后在此之后将该值移位-1任意次数,则该值将保持不变。请检查。
海湾合作委员会
-ve->算术移位
对于+ ve->逻辑移位
据许多 C 编译器:
<<
是算术左移或按位左移。>>
是算术右移或按位右移。>>
算术还是按位(逻辑)?” 您回答“ >>
是算术运算还是按位运算”。那不能回答问题。
<<
和>>
运营商的逻辑,不是算术