使用按位移位操作进行符号扩展

时间:2013-03-31 13:19:39

标签: c++ c bit-manipulation

关注this Q& A我试着检查答案,所以我写道:

#include <stdio.h>

int main ()
{

        int t;int i;
        for (i=120;i<140;i++){
                t = (i - 128) >> 31;
                printf ("t = %X , i-128 = %X ,  ~t & i = %X , ~t = %X \n", t, i-128 , (~t &i), ~t);
        }

        return 0;
}

,输出为:

t = FFFFFFFF , i-128 = FFFFFFF8 ,  ~t & i = 0 , ~t = 0 
t = FFFFFFFF , i-128 = FFFFFFF9 ,  ~t & i = 0 , ~t = 0 
t = FFFFFFFF , i-128 = FFFFFFFA ,  ~t & i = 0 , ~t = 0 
t = FFFFFFFF , i-128 = FFFFFFFB ,  ~t & i = 0 , ~t = 0 
t = FFFFFFFF , i-128 = FFFFFFFC ,  ~t & i = 0 , ~t = 0 
t = FFFFFFFF , i-128 = FFFFFFFD ,  ~t & i = 0 , ~t = 0 
t = FFFFFFFF , i-128 = FFFFFFFE ,  ~t & i = 0 , ~t = 0 
t = FFFFFFFF , i-128 = FFFFFFFF ,  ~t & i = 0 , ~t = 0 
t = 0 , i-128 = 0 ,  ~t & i = 80 , ~t = FFFFFFFF 
t = 0 , i-128 = 1 ,  ~t & i = 81 , ~t = FFFFFFFF 
t = 0 , i-128 = 2 ,  ~t & i = 82 , ~t = FFFFFFFF 
t = 0 , i-128 = 3 ,  ~t & i = 83 , ~t = FFFFFFFF 
t = 0 , i-128 = 4 ,  ~t & i = 84 , ~t = FFFFFFFF 
t = 0 , i-128 = 5 ,  ~t & i = 85 , ~t = FFFFFFFF 
t = 0 , i-128 = 6 ,  ~t & i = 86 , ~t = FFFFFFFF 
t = 0 , i-128 = 7 ,  ~t & i = 87 , ~t = FFFFFFFF 
t = 0 , i-128 = 8 ,  ~t & i = 88 , ~t = FFFFFFFF 
t = 0 , i-128 = 9 ,  ~t & i = 89 , ~t = FFFFFFFF 
t = 0 , i-128 = A ,  ~t & i = 8A , ~t = FFFFFFFF 
t = 0 , i-128 = B ,  ~t & i = 8B , ~t = FFFFFFFF 

如果~t声明为整数,为什么-1 == 0xFFFFFFFF的任何负数为t

5 个答案:

答案 0 :(得分:4)

  

为什么t =(i-128)&gt;&gt; 31给每个数字零或-1?

当非负32位整数向右移位31个位置时,所有非零位都会移出,最高位将被0填充,因此最终得到0。

通常,当负32位整数向右移位31个位置时,最高有效位不会被0填充,而是将它们设置为数字的符号,因此符号传播到所有位并且2的补码表示所有位设置为1到-1。净效果就像你重复将数字除以2,但稍微扭曲......结果向-infinity舍入而不是向0.舍入为例如-2>>1==-1但是-3>>1==-2和{{ 1}}。这称为算术右移

当我说“通常”时,我的意思是C标准允许几种不同的行为用于负值的右移。最重要的是,它允许有符号整数的非2的补码表示。但是,通常情况下,你有2的补码表示和我在上面展示/解释过的行为。

答案 1 :(得分:3)

来自:Right shifting negative numbers in C

编辑:根据最新draft standard的第6.5.7节,负数上的这种行为取决于实现:

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

并且,您的实现可能正在使用两个补码

进行算术移位

运算符>>签名右移算术右移,将所有位向右移动指定的次数。重要的是>>将最左边的符号位(最高有效位MSB)填充到移位后最左边的位。这称为符号扩展,用于在向右移动时保留负数的符号

下面是我的图解表示,其中有一个示例来说明这是如何工作的(对于一个字节):
示例:

i = -5 >> 3;  shift bits right three time 

五分之二的补码形式是1111 1011记忆表示:

 MSB
+----+----+----+---+---+---+---+---+
|  1 |  1 | 1  | 1 | 1 | 0 | 1 | 1 |   
+----+----+----+---+---+---+---+---+
   7    6   5    4   3   2   1   0  
  ^  This seventh, the left most bit is SIGN bit  

以下是>>如何运作?当你-5 >> 3

                        this 3 bits are shifted 
                         out and loss
 MSB                   (___________)      
+----+----+----+---+---+---+---+---+
|  1 |  1 | 1  | 1 | 1 | 0 | 1 | 1 |   
+----+----+----+---+---+---+---+---+
  | \                 \  
  |  ------------|     ----------|
  |              |               |
  ▼              ▼               ▼
+----+----+----+---+---+---+---+---+
|  1 |  1 | 1  | 1 | 1 | 1 | 1 | 1 |
+----+----+----+---+---+---+---+---+
(______________)
 The sign is        
 propagated

注意:最左边的三个位是1,因为每个移位符号位都被保留,每个位也是正确的。我写了符号传播因为所有这三个位都是因为符号(而不是数据)。

<强> [ANSWER]

的输出中

前八行

      ~t is 0
==>    t is FFFFFFFF 
==>    t is -1

(注意: 2的-1的补码是FFFFFFFF,因为1 = 00000001,1的1的补码是FFFFFFFE,2的补码= 1的补码+1是: FFFFFFFE + 00000001 = FFFFFFFF

所以t始终在循环中的前八次评估-1,怎么样?

for for循环

for (i=120;i<140;i++){
     t = (i - 128) >> 31;

前八次i的值为i = 120, 121, 122, 123, 124, 125, 126 ,127,所有八个值小于128 。所以返回(i - 128) = -8, -7, -6, -5, -4, -3, -2, -1。因此,在前八次表达式t = (i - 128) >> 31中,移位权利为负数。

t =   (i - 128)  >> 31
t =  -ve number  >> 31

因为在你的系统中int是4字节= 32位,所以最右边的31位是移出和丢失,而是由于符号位传播1为负数字所有位值变为1 。 (正如我在上图中显示的一个字节)

所以对于拳头八次:

    t =  -ve number  >> 31 ==  -1 
    t = -1
  and this gives 
    ~t = 0

因此~t的第八次输出为0.

剩余的最后一行

      ~t is FFFFFFFF
==>   ~t is -1   
==>    t is 0 

对于剩余的最后一行,在for循环中

for (i=120;i<140;i++){
     t = (i - 128) >> 31;

我的值128, 129, 130, 132, 133, 134, 135, 136, 137, 138, 139, 都大于或等于 128.且符号位为0

因此(i - 128)剩余的最后一行是>=0,所有这个MSB符号位= 0。并且因为再次向右移动31次除了所有比特,然后叹息位移出并且符号位0传播并用0填充所有位,并且幅度变为0

我认为如果我也为一个正数写一个例子会很好。我们举一个例子5 >> 3,五个是一个字节是0000 0101

                        this 3 bits are shifted 
                         out and loss
 MSB                   (___________)      
+----+----+----+---+---+---+---+---+
|  0 |  0 | 0  | 0 | 0 | 1 | 0 | 1 |   
+----+----+----+---+---+---+---+---+
  | \                 \  
  |  ------------|     ----------|
  |              |               |
  ▼              ▼               ▼
+----+----+----+---+---+---+---+---+
|  0 |  0 | 0  | 0 | 0 | 0 | 0 | 0 |
+----+----+----+---+---+---+---+---+
(______________)
 The sign is        
 propagated

再次看到我写标志传播,所以最左边的三个零是由于符号位。

这就是操作符>> 签名右移的操作,保留左操作数的符号

答案 2 :(得分:2)

Becaue t为0或-1,〜t也始终为-1或0.

这是由于(实现定义的)行为或(i - 128) >> 31,它基本上复制了(i-128)的最高位[假设32位整数]。如果i是&gt; 128,它将导致顶部位为零。如果i小于128,则结果为负,因此设置了最高位。

由于~t是“t的所有位”,如果t为零,您可以预期t始终为0xffffffff。

答案 3 :(得分:1)

>>运算符右移是大多数编译器中的算术右移,意味着除以2。

所以,如果,例如, int i ==-4(0xfffffffc),然后i>>1 == -2(0xfffffffe)。

话虽如此,我建议您检查代码的汇编 例如x86有2个单独的指令 - shr&amp; sar,表示逻辑转变&amp;算术移位 通常,编译器使用shr(逻辑移位)来表示无符号变量&amp;签名变量的sar(算术移位)。


以下是C代码&amp;相应的程序集,使用gcc -S

生成

交流转换器:

int x=10;
unsigned int y=10;

int main(){
    unsigned int z=(x>>1)+(y>>1);
    return 0;
}

A.S:

    .file   "a.c"
.globl x
    .data
    .align 4
    .type   x, @object
    .size   x, 4
x:
    .long   10
.globl y
    .align 4
    .type   y, @object
    .size   y, 4
y:
    .long   10
    .text
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $16, %esp
    movl    x, %eax
    sarl    %eax ; <~~~~~~~~~~~~~~~~ Arithmetic shift, for signed int
    movl    y, %edx
    shrl    %edx ; <~~~~~~~~~~~~~~~~ Logical shift, for unsigned int
    addl    %edx, %eax
    movl    %eax, -4(%ebp)
    movl    $0, %eax
    leave
    ret
    .size   main, .-main
    .ident  "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"
    .section    .note.GNU-stack,"",@progbits

答案 4 :(得分:0)

C和C ++中的规则是负值右移的结果是实现定义的。请阅读编译器的文档。您获得的各种解释都是有效的方法,但这些都不是语言定义所强制要求的。