我有下一个代码:
std::cout << (-10 >> 1) << std::endl;
std::cout << (-9 >> 1) << std::endl;
std::cout << (-8 >> 1) << std::endl;
std::cout << (-7 >> 1) << std::endl;
std::cout << (-6 >> 1) << std::endl;
std::cout << (-5 >> 1) << std::endl;
std::cout << (-4 >> 1) << std::endl;
std::cout << (-3 >> 1) << std::endl;
std::cout << (-2 >> 1) << std::endl;
std::cout << (-1 >> 1) << std::endl;
结果是:
-5
-5
-4
-4
-3
-3
-2
-2
-1
-1
但为什么呢?
-1
为1111 1111
(1个字节),-1 >>
1必须为:1011 1111
且不是-1
或0
! (符号位没有移位,我知道)
有人可以向我解释这是如何运作的吗?
答案 0 :(得分:24)
标准5.8 / 3(轮班操作员):
E1的值&gt;&gt; E2是E1 右移E2位位置。如果E1 有一个无符号类型或E1有一个 签名类型和非负值, 结果的值是 E1的商的组成部分 除以提高的数量2 权力E2。 如果E1有签名类型 由此产生的负值 值是实现定义的。
所以对于“为什么?”这个问题,标准答案是:为什么不呢。
答案 1 :(得分:17)
对负数进行右移是实现定义的。
将符号扩展位移到最左边位的实现按照您的报告工作。
至于为什么这样做,这是因为右移可以用2除以圆的朝向负无穷大(例如像floor()
)舍入行为来划分:
(-8 >> 2) == -2
(-9 >> 2) == -3
(-10 >> 2) == -3
(-11 >> 2) == -3
(-12 >> 2) == -3
答案 2 :(得分:3)
-1&gt;&gt; 1必须是:1011 1111
如果是真的,-10&gt;&gt;在2的补码中,1将是10111011 == -69。不是一个非常有用的结果!
虽然语言行为未定义(因此结果,有用或无法依赖),常见行为(以及本案例中展示的行为)是执行“符号扩展”。
“符号位未移位”, 移位,并且空位被填充等于符号位的值。这种行为既保留了符号,又提供了除了-1之外的所有值都观察到的“预期的”二分频操作。对于负值,-1是右移的“终值”,因为零是正数。也就是说,负数的右移倾向于-1,正数趋于零。
答案 3 :(得分:3)
通常,右移定义为实现为“算术”或“逻辑”。不同之处在于,对于逻辑右移,最左侧的位始终设置为零。使用算术右移,最左边的位是前一个值的副本。
例如,让我们假装值只有8位,以便更容易跟踪。然后:
逻辑:
0111 1111 >> 1 = 0011 1111
1111 1111 >> 1 = 0111 1111
1111 1110 >> 1 = 0111 1111
算术:
0111 1111 >> 1 = 0011 1111
1111 1111 >> 1 = 1111 1111
1111 1110 >> 1 = 1111 1111
算术右移相当于除以2并向负无穷大舍入。
在C ++中,右移运算符是逻辑运算符还是算术运算符是特定于实现的。也就是说,每个编译器编写者都可以自己决定,可能是根据他正在进行的计算机体系结构更容易做的事情。
您的符号位未移位的说法不正确。符号位的移位与所有其他位一样。唯一的问题是取代它的原因。
Java有两个不同的右移运算符:&gt;&gt;是算术和&gt;&gt;&gt;是合乎逻辑的。