使用位域或按位运算符在一个字节内移动一点

时间:2011-06-21 19:44:43

标签: language-agnostic bit-manipulation puzzle bit

是否有优雅的方法在一个字节(或字/长)内移动一点。为简单起见,我们使用一个简单的8位字节,只需一位在字节内移动。

给定一个位数,基于0-7最小sig位到大多数sig位(或者如果你愿意的话,位1-8),我想从一个位置移动到另一个位置:

7654 3210 <bit position
0101 1010 <some binary value
--x- --y- <move bit from x to y
0111 0100 <new value with x moved to y and intervening bits shifted left

因此,位位置5的x在位位置1移动到y,位0,6,7保持不变。位2,3,4向左移动到“腾出空间”,位数从5移到2.这只是一个例子。

位移动很重要,而不是与目标交换。有许多比特可以交换,但这非常简单。

理想情况下,该解决方案将使用简单的bit-twiddling和bitwise运算符。假设语言不可知,位简单AND / OR / XOR,NOT,SHIFT左/右/ ROTATE或类似指令在任何组合中都可以,加上任何其他基本算术运算符,例如:mod,加/减等。甚至工作伪 - 代码没问题。或者,位阵列或位域类型结构可能很简单。

除了实际的位移动,我想找到一种方法:

  • 向上或向下移动任何位。
  • 以任何方便的格式指定位号源/目的地:例如:6> 2 意味着向下移位,3> 7向上移位或开始比特+/-偏移:6-4或3 + 4,或比特加权:比特6 = 64到比特3 = 8。
  • 可能从byte扩展到unsigned int,long等。
  • (理想情况下,可以扩展 一次多于一位,如果更容易,可能是相邻的位)

性能不是一个主要问题,但优雅的东西可能足够快。

我自己的niaive方法是识别源位和目标位位置,决定是上移还是下移,移位复制,屏蔽静态位并找到源位,合并静态位和移位位并以某种方式设置/清除目标位。然而,虽然这个理论似乎很好,但优雅的实现却超出了我的范围。

我意识到可以为一个字节构建一个预编译的查找表,但如果将其扩展为整数/长整数,这对我来说是不切实际的。

任何帮助表示赞赏。提前谢谢。

4 个答案:

答案 0 :(得分:4)

首先,观察一下原始问题以及您提到的后续扩展:

您描述的“移动位”操作实际上是连续位的旋转。在您的示例中,您将位数1-5(包括左侧)向左旋转一位:

  7   6   5   4   3   2   1   0          7   6   5   4   3   2   1   0
+---+---+---+---+---+---+---+---+      +---+---+---+---+---+---+---+---+
| 0 | 1 | 0<--1<--1<--0<--1 | 0 |  ->  | 0 | 1 | 1 | 1 | 0 | 1 | 0 | 0 |
+---+---+-|-+---+---+---+-^-+---+      +---+---+---+---+---+---+---+---+
          |               |
          +---------------+

如果你认为这个操作的更一般形式是“用一定量旋转一定数量的位”,有三个参数:

  1. 要包含在轮播
  2. 中的最低有效位
  3. 要包含在轮播中的最重要的位
  4. 要按
  5. 旋转的位数

    然后它成为一个基本原语,可以执行所有你要做的事情:

    • 你显然可以移动任何一点(选择适当的最小/最重要的位参数);
    • 你可以向左或向右旋转,因为如果你正在旋转 n 位的范围,那么通过 k 位向右旋转与向左旋转相同通过 n - k 位;
    • 它可以简单地推广到任何位宽;
    • 根据定义,我们可以一次旋转多个位。

    所以现在,所需要的就是构建这个原语......


    首先,我们几乎可以肯定需要为我们关心的位设置掩码。

    我们可以通过向左移动1乘 n + 1位,然后减去1来形成位0 - n 的掩码。位0-5的掩码将是(二进制):

    00111111
    

    ......可以通过1:

    形成
    00000001
    

    ...向左移动5 + 1 = 6位:

    01000000
    

    ...并减去1给出:

    00111111
    

    在C中,这将是(1 << (bit + 1)) - 1。但至少对C来说这里有一个微妙之处(当你把这个标记为与语言无关时,我为这个题外话道歉,但这很重要,其他语言也可能存在类似的问题):您的类型(或更多)的宽度导致未定义的行为。因此,如果我们尝试为8位类型的0-7位构造掩码,则计算将为(1 << 8) - 1,这将是未定义的。 (它可能适用于某些系统和某些编译器,但不可移植。)在最终转移到符号位的情况下,签名类型也存在未定义的行为问题。

    幸运的是,在C中,我们可以通过使用unsigned类型并将表达式编写为(1 << bit) + (1 << bit) - 1来避免这些问题。具有无符号 n 位值的算术由标准定义为模2 n ,并且所有单独的操作都是明确定义的,因此我们保证得到正确答案。

    (离题结束。)

    好的,现在我们有一个0位的掩码 - msb 。我们想为位 lsb - msb 创建一个掩码,我们可以通过减去位0的掩码来做 - ( lsb -1) ,(1 << lsb) - 1。 e.g。

      00111111      mask for bits 0-5:  (1 << 5) + (1 << 5) - 1
    - 00000001      mask for bits 0-0:  (1 << 1) - 1
      --------                         -------------------------------
      00111110      mask for bits 1-5:  (1 << 5) + (1 << 5) - (1 << 1)
    

    因此掩码的最终表达式为:

    mask = (1 << msb) + (1 << msb) - (1 << lsb);
    

    可以使用掩码按位AND选择要旋转的位:

    to_rotate = value & mask;
    

    ...并且可以通过AND使用反转掩码选择将保持不变的位:

    untouched = value & ~mask;
    

    旋转本身可以分两部分轻松完成:首先,我们只需向左旋转to_rotate并丢弃掉落在掩模外的任何位,即可获得旋转部分的最左边位:

    left = (to_rotate << shift) & mask;
    

    要获取最右边的位,请通过( n - shift )位旋转to_rotate ,其中 n 是我们正在旋转的位数(此 n 可以计算为msb + 1 - lsb):

    right = (to_rotate >> (msb + 1 - lsb - shift)) & mask;
    

    最终结果可以通过组合untouchedleftright中的所有位来获得:

    result = untouched | left | right;
    

    您的原始示例将如下工作(msb为5,lsb为1,shift为1):

        value = 01011010
    
        mask  = 00111110   from (1 << 5) + (1 << 5) - (1 << 1)
    
                01011010   value
              & 00111110   mask
              ----------
    to_rotate = 00011010
    
                01011010   value
              & 11000001   ~mask  (i.e. inverted mask)
              ----------
    untouched = 01000000
    
                00110100   to_rotate << 1
              & 00111110   mask
              ----------
         left = 00110100
    
                00000001   to_rotate >> 4  (5 + 1 - 1 - 1 = 4)
              & 00111110   mask
              ----------
        right = 00000000
    
                01000000   untouched
                00110100   left
              | 00000000   right
              ----------
       result = 01110100
    

    这是一个16位输入值的不同示例,msb = 15,lsb = 4,shift = 4(旋转4的前3位十六进制数字) -digit十六进制值):

        value = 0101011001111000   (0x5678)
    
        mask  = 1111111111110000   from (1 << 15) + (1 << 15) - (1 << 4)
    
                0101011001111000   value
              & 1111111111110000   mask
              ------------------
    to_rotate = 0101011001110000
    
                0101011001111000   value
              & 0000000000001111   ~mask
              ------------------
    untouched = 0000000000001000
    
                0110011100000000   to_rotate << 4
              & 1111111111110000   mask
              ------------------
         left = 0110011100000000
    
                0000000001010110   to_rotate >> 8  (15 + 1 - 4 - 4 = 8)
              & 1111111111110000   mask
              ------------------
        right = 0000000001010000
    
                0000000000001000   untouched
                0110011100000000   left
              | 0000000001010000   right
              ------------------
       result = 0110011101011000   =  0x6758
    

答案 1 :(得分:2)

这是C中的一个工作实现,它没有经过高度优化,但至少可以作为任何进一步实现的起点。它适用于整数,但您可以根据任何字大小调整它,或者只使用按原样并屏蔽掉任何不需要的高位(例如,如果您使用的是单个字节)。我将功能分解为两个较低级别的例程,用于提取一些位并插入一点 - 这些可能有其他用途,我想。

//
// bits.c
//

#include <stdio.h>
#include <stdlib.h>

//
// extract_bit
//
// extract bit at given index and move less significant bits left
//

int extract_bit(int *word, int index)
{
    int result = (*word & (1 << index)) != 0;
    int mask = (1 << index) + (1 << index) - 1;
    *word = ((*word << 1) & mask) | (*word & ~mask);
    return result;
}

//
// insert_bit
//
// insert bit at given index and move less significant bits right
//

void insert_bit(int *word, int index, int val)
{
    int mask1 = (1 << index) + (1 << index) - 1;
    int mask2 = (1 << index) - 1;
    *word = ((*word >> 1) & mask2) | (*word & ~mask1) | (val << index);
}

//
// move_bit
//
// move bit from given src index to given dest index
//

int move_bit(int *word, int src_index, int dest_index)
{
    int val = extract_bit(word, src_index);
    insert_bit(word, dest_index, val);
    return val;
}

int main(int argc, char * argv[])
{
    if (argc > 2)
    {
        int test = 0x55555555;
        int index1 = atoi(argv[1]);
        int index2 = atoi(argv[2]);

        printf("test (before) = %#x\n", test);
        printf("index (src) = %d\n", index1);
        printf("index (dest) = %d\n", index2);

        move_bit(&test, index1, index2);

        printf("test (after) = %#x\n", test);
    }

    return 0;
}

答案 2 :(得分:1)

这可能不符合“优雅”的条件,但是如果这是你的那种话,你可能会把它塞进一行?计划是将数字分成四部分(对于位操作不应该很难,对吗?),对它们做适当的事情,然后将这三部分重新组合在一起。

              Number: 01x1 10y1
       P1 (before x): 0100 0000
     P2 (just bit x): 00x0 0000
P3 (between x and y): 0001 10y0
        P4 (after y): 0000 0001

然后您想要的号码是[P1] + [P3 shifted up by 1] + [P2 shifted down by 4] + [P4]

                  P1: 0100 0000
P2 shifted down by 3: 0000 00x0
  P3 shifted up by 1: 0011 0y00
                  P4: 0000 0001

                 Sum: 0111 0yx1               

答案 3 :(得分:0)

你使用比特来节省空间吗?真的需要吗?

使用列表类可能会更好,您可以在列表中删除和插入项目。在你的情况下,项目将是布尔人。