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

0x9*_*x90 5 c c++ bit-manipulation

问答后,我试着检查答案,所以我写道:

#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;
}
Run Code Online (Sandbox Code Playgroud)

输出是:

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 
Run Code Online (Sandbox Code Playgroud)

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

Ale*_*nze 5

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

当一个非负的 32 位整数右移 31 个位置时,所有非零位都被移出并且最高有效位被填充为 0,因此最终为 0。

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

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


Gri*_*han 5

来自:C 中的负数右移

编辑: 根据最新草案标准的第 6.5.7 节,负数的这种行为取决于实现:

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

而且,您的实现可能正在使用二进制补码进行算术移位


运算符>>Signed right shift算术右移,将所有位右移指定的次数。重要的是>> 在移位后将最左边的符号位(Most Significant Bit MSB)填充到最左边的位。这称为符号扩展,用于在右移负数时保留负数的符号

下面是我的图解表示和一个例子来展示它是如何工作的(对于一个字节):
示例:

i = -5 >> 3;  shift bits right three time 
Run Code Online (Sandbox Code Playgroud)

五进二出的补码形式是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  
Run Code Online (Sandbox Code Playgroud)

下面是,如何>>工作?当你做-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
Run Code Online (Sandbox Code Playgroud)

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

[ANSWER]
在你的输出中

前八行

      ~t is 0
==>    t is FFFFFFFF 
==>    t is -1
Run Code Online (Sandbox Code Playgroud)

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

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

在 for 循环中

for (i=120;i<140;i++){
     t = (i - 128) >> 31;
Run Code Online (Sandbox Code Playgroud)

的值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
Run Code Online (Sandbox Code Playgroud)

因为在您的系统中 int 是 4 字节 = 32 位,所以大多数31位都被移出和丢失,并且由于符号位的传播,1对于负数,所有位值都变为1。(正如我在上图中显示的一个字节)

所以拳头八次:

    t =  -ve number  >> 31 ==  -1 
    t = -1
  and this gives 
    ~t = 0
Run Code Online (Sandbox Code Playgroud)

因此,~t 的拳头八次的输出为 0。

对于剩余的最后几行

      ~t is FFFFFFFF
==>   ~t is -1   
==>    t is 0 
Run Code Online (Sandbox Code Playgroud)

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

for (i=120;i<140;i++){
     t = (i - 128) >> 31;
Run Code Online (Sandbox Code Playgroud)

i 值均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
Run Code Online (Sandbox Code Playgroud)

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

所以这就是运算符有>> 符号右移所做的,并保留左操作数的符号

  • 不,C++ 只是在这里复制了 C,C 标准很明确。如果左侧操作数具有带符号类型并且为负,则结果由实现定义。(我相信,原因是并非所有处理器都有算术右移指令。) (2认同)