2010-11-29 198 views
16

我有下面的代码:为什么-1 >> 1是-1?而1 >> 1是0!

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 

但是,为什么?

-11111 1111(1字节),-1 >> 1必须是:1011 1111,这是不是-10! (我知道标志位没有移位)

有人能告诉我这是如何工作的吗?

+0

每一位都被移位,并且在二进制补码中没有明确的符号位。 – jalf 2010-11-30 01:24:38

回答

24

标准5.8/3(移位运算符):

E1 E2 >>的值是E1 右移E2比特位置。如果E1 具有一个无符号类型,或者E1具有 符号类型和具有非负值,所述 结果的值是E1 的商通过升高至 电源E2的数量除以2的 组成部分。 如果E1的签名类型为 且结果为负值,则结果值 的值是实现定义的。

所以对于“为什么?”这个问题,标准答案是:为什么不呢。

+8

标准答案是:“RTFM for your compiler” – 2010-11-29 22:00:22

17

将负数右移是实现定义的。

将符号扩展位移入最左边位的实现按照您的报告工作。

至于为什么做这样,那是因为向右移位,可以使用2的幂与舍入,向负无穷大(例如像floor())舍入行为来划分:

(-8 >> 2) == -2 
(-9 >> 2) == -3 
(-10 >> 2) == -3 
(-11 >> 2) == -3 
(-12 >> 2) == -3 

See this SO question.

3

-1 >> 1必须是:1011 1111

如果这是真的,-10 >> 1。将10111011 == -69的补码。不是一个非常有用的结果!

虽然语言行为未定义(所以结果,有用或其他不能依赖的结果),但是行为(以及在这种情况下表现出来的行为)是执行“符号扩展”。

这是不正确的“签位不发生移位”,它移位,腾出的空位充满等于符号位的值。这种行为既保留了符号,又提供了除了-1之外的所有值所观察到的'预期'除2运算。对于负值,-1是右移的'终值',因为0是正值。也就是说,负数的右移趋于-1,正数趋于零。

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有两种不同的右移运算符:>>是算术而>>>是逻辑的。

+0

@laalto:哦,谢谢编辑。当我输入时,它在我的屏幕上可读。:-( – Jay 2010-11-30 16:06:49

相关问题