了解 128 位数字的左右按位移位

Posted

技术标签:

【中文标题】了解 128 位数字的左右按位移位【英文标题】:Understanding Left and Right Bitwise shift on a 128-bit number 【发布时间】:2011-11-30 17:21:00 【问题描述】:

洋蓟101asked this:

假设我有一个由 4 个 32 位整数组成的数组,用于存储 128 位数字

如何对这个 128 位数字进行左移和右移?"

我的问题与 Remus Rusanu 给出的答案有关:

void shiftl128 (
    unsigned int& a,
    unsigned int& b,
    unsigned int& c,
    unsigned int& d,
    size_t k)

    assert (k <= 128);
    if (k > 32)
    
        a=b;
        b=c;
        c=d;
        d=0;
        shiftl128(a,b,c,d,k-32);
    
    else
    
        a = (a << k) | (b >> (32-k));
        b = (b << k) | (c >> (32-k));
        c = (c << k) | (d >> (32-k));
        d = (d << k);
    


void shiftr128 (
    unsigned int& a,
    unsigned int& b,
    unsigned int& c,
    unsigned int& d,
    size_t k)

    assert (k <= 128);
    if (k > 32)
    
        d=c;
        c=b;
        b=a;
        a=0;
        shiftr128(a,b,c,d,k-32);
    
    else
    
        d = (c << (32-k)) | (d >> k); \
        c = (b << (32-k)) | (c >> k); \
        b = (a << (32-k)) | (b >> k); \
        a = (a >> k);
    

让我们只关注一个班次,比如说左班次。具体来说,

a = (a << k) | (b >> (32-k));
b = (b << k) | (c >> (32-k));
c = (c << k) | (d >> (32-k));
d = (d << k);

如何左移 128 位数字?我知道什么是位移,

a = (a &lt;&lt; k) | (b &gt;&gt; (32-k)) 如何正确移动 128 位数字的第一部分 (32)?

【问题讨论】:

我建议不要硬编码32,而是使用sizeof(unsigned int)。它应该是32,但至少你会确定它。 sizeof(unsigned int) 将是 4。:) 所以,你需要 sizeof(unsigned int)*CHAR_BIT 【参考方案1】:

这种技术有点惯用。让我们简化为ab。我们开始:

+----------+----------+
|    a     |    b     |
+----------+----------+

我们想左移一些来获得:

+----------+----------+
|  a    :  |  b    :  |  c  ...
+----------+----------+
|<--x-->|  |
      ->|y |<-

所以X 就是a &lt;&lt; kybk msbs,在单词中右对齐。您可以使用b &gt;&gt; (32-k) 获得该结果。

总的来说,你得到:

a = x | y
  = (a << k) | (b >> (32-k))

[注意:这种方式只对1 k

【讨论】:

因此,上述代码中的守卫在 k 大于 32 时递归调用自身。这不是最快的方法,但它就是这样 ;) @Michael Dorgan:它需要检查k &gt;= 32。而且它不处理 k == 0 的其他特殊情况。 哇,这个可视化的例子很有帮助。非常感谢!【参考方案2】:

a 的位向左移动时,必须有东西填充右端剩余的空间。由于ab 在概念上彼此相邻,因此通过移动a 的位而留下的空白将被移出b 末尾的位填充。表达式b &gt;&gt; (32-k) 计算从b 移出的位。

【讨论】:

感谢您添加来源! :D【参考方案3】:

您需要记住,在转移过程中“丢失”数据是可以接受的。

理解移位的最简单方法是想象一个窗口。例如,让我们处理字节。您可以将一个字节视为:

  0 0 0 0 0 0 0 0 a b c d e f g h 0 0 0 0 0 0 0 0
                 [      B        ]

现在,移动就是移动这个窗口:

  0 0 0 0 0 0 0 0 a b c d e f g h 0 0 0 0 0 0 0 0
 [     B >> 8    ]
   [     B >> 7    ]
     [     B >> 6    ]
       [     B >> 5    ]
  0 0 0 0 0 0 0 0 a b c d e f g h 0 0 0 0 0 0 0 0
         [     B >> 4    ]
           [     B >> 3    ]
             [     B >> 2    ]
               [     B >> 1    ]
  0 0 0 0 0 0 0 0 a b c d e f g h 0 0 0 0 0 0 0 0
                   [     B << 1    ]
                     [     B << 2    ]
                       [     B << 3    ]
                         [     B << 4    ]
  0 0 0 0 0 0 0 0 a b c d e f g h 0 0 0 0 0 0 0 0
                           [     B << 5    ]
                             [     B << 6    ]
                               [     B << 7    ]
                                 [     B << 8    ]
  0 0 0 0 0 0 0 0 a b c d e f g h 0 0 0 0 0 0 0 0

如果你看箭头的方向,你可以认为它有一个固定的窗口和一个移动的内容......就像你喜欢的手机触摸屏一样!

那么,a = (a &lt;&lt; k) | (b &gt;&gt; (32-k)) 表达式中发生了什么?

a &lt;&lt; k 选择a 的最右边的32 - k 位并将它们向左移动,在右侧创建k 0 的空间 b &gt;&gt; (32-k) 选择b 的最左边的k 位并将它们向右移动,在左侧创建32 - k 0 的空间 两者合并在一起

回到使用字节长度的位:

假设a[a7, a6, a5, a4, a3, a2, a1, a0] 假设b[b7, b6, b5. b4, b3, b2, b1, b0] 假设k3

代表的数字是:

// before
 a7 a6 a5 a4 a3 a2 a1 a0 b7 b6 b5 b4 b3 b2 b1 b0
[           a           ]
                        [           b           ]

// after (or so we would like)
 a7 a6 a5 a4 a3 a2 a1 a0 b7 b6 b5 b4 b3 b2 b1 b0
         [           a           ]
                                 [           b           ]

所以:

a &lt;&lt; 3 实际上变成了a4 a3 a2 a1 a0 0 0 0 b &gt;&gt; (8 - 3) 变为 0 0 0 0 0 b7 b6 b5 结合|我们得到a4 a3 a2 a1 a0 b7 b6 b5

冲洗并重复 :)

【讨论】:

【参考方案4】:

请注意,在 else 情况下,k 保证为 32 或更少。因此,您的较大数字的每个部分实际上都可以移动 k 位。但是,将其向左或向右移动会使 k 高/低位为 0。要移动整个 128 位数字,您需要用相邻数字“移出”的位填充这些 k 位。

在左移k的情况下,高数的低k位需要用低数的高k位填充。为了得到这些高 k 位,我们将(32 位)数字右移 32-k 位,现在我们将这些位放在正确的位置以从较高的数字中填充零 k 位。

顺便说一句:上面的代码假定 unsigned int 正好是 32 位。那不是便携式的。

【讨论】:

【参考方案5】:

为简化起见,考虑一个 16 位无符号 short,我们将高字节和低字节分别存储为 unsigned char h, l。 为了进一步简化,让我们将其左移一位,看看结果如何。

我将其写为 16 个连续位,因为这就是我们要建模的内容:

[h7 h6 h5 h4 h3 h2 h1 h0 l7 l6 l5 l4 l3 l2 l1 l0]

所以,[h, l] &lt;&lt; 1 将是

[h6 h5 h4 h3 h2 h1 h0 l7 l6 l5 l4 l3 l2 l1 l0 0]

(高位,h7 已从高位旋转,低位用零填充)。 现在让我们将其分解为hl ...

[h, l] = [h6 h5 h4 h3 h2 h1 h0 l7 l6 l5 l4 l3 l2 l1 l0 0]
=> h = [h6 h5 h4 h3 h2 h1 h0 l7]
     = (h << 1) | (l >> 7)

等等

【讨论】:

【参考方案6】:

我在 little endian 环境中对 128 位数字进行逻辑左移的变体:

typedef struct  unsigned int component[4];  vector4; 
vector4 shift_left_logical_128bit_le(vector4 input,unsigned int numbits) 
    vector4 result;
    if(n>=128) 
         result.component[0]=0;
         result.component[1]=0;
         result.component[2]=0;
         result.component[3]=0;
         return r;
    
    result=input;
    while(numbits>32) 
        numbits-=32;
        result.component[0]=0;
        result.component[1]=result.component[0];
        result.component[2]=result.component[1];
        result.component[3]=result.component[2];
    
    unsigned long long temp;
    result.component[3]<<=numbits;
    temp=(unsigned long long)result.component[2];
    temp=(temp<<numbits)>>32;
    result.component[3]|=(unsigned int)temp;
    result.component[2]<<=numbits;
    temp=(unsigned long long)result.component[1];
    temp=(temp<<numbits)>>32;
    result.component[2]|=(unsigned int)temp;
    result.component[1]<<=numbits;
    temp=(unsigned long long)result.component[0];
    temp=(temp<<numbits)>>32;
    result.component[1]|=(unsigned int)temp;
    result.component[0]<<=numbits;
    return result;

【讨论】:

以上是关于了解 128 位数字的左右按位移位的主要内容,如果未能解决你的问题,请参考以下文章

Java位移运算

java int怎么位移取前几位数字

如何在 VB.NET 中按位移位?

带 2 个箭头和 3 个箭头的按位移位有啥区别? [复制]

Java位运算:位异或运算位与运算位或运算位取反运算左位移运算右位移运算无符号右移运算不用额外变量交换两个整数的值(使用位异或运算)只出现一次的数字

Java位运算:位异或运算位与运算位或运算位取反运算左位移运算右位移运算无符号右移运算不用额外变量交换两个整数的值(使用位异或运算)只出现一次的数字