如何将字节数组移位12位

时间:2008-08-27 03:13:12

标签: c arrays bit-shift bitset

我想将字节数组的内容向左移12位。

例如,从这个类型为uint8_t shift[10]的数组开始:

{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xBC}

我想将它向左移12位,结果是:

{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAB, 0xC0, 0x00}

7 个答案:

答案 0 :(得分:8)

欢呼指点!

此代码的工作原理是向前看每个字节的12位并向前复制正确的位。 12位是下一个字节的下半部分(nybble)和2个字节的上半部分。

unsigned char length = 10;
unsigned char data[10] = {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0A,0xBC};
unsigned char *shift = data;
while (shift < data+(length-2)) {
    *shift = (*(shift+1)&0x0F)<<4 | (*(shift+2)&0xF0)>>4;
    shift++;
}
*(data+length-2) = (*(data+length-1)&0x0F)<<4;
*(data+length-1) = 0x00;
  

贾斯汀写道:
  @Mike,你的解决方案有效,但不能携带。

好吧,我会说正常的移位操作就是这样(称为溢出),只是让额外的位从右边或左边掉下来。如果您愿意,它很容易携带 - 只需在开始移动之前保存12位。也许你想要一个循环移位,将溢出的位置放回到底部?也许你想重新分配数组并使其更大?将溢出返回给调用者?如果非零数据溢出,则返回布尔值?你必须为你定义携带方式。

unsigned char overflow[2];
*overflow = (*data&0xF0)>>4;
*(overflow+1) = (*data&0x0F)<<4 | (*(data+1)&0xF0)>>4;
while (shift < data+(length-2)) {
    /* normal shifting */
}  
/* now would be the time to copy it back if you want to carry it somewhere */
*(data+length-2) = (*(data+length-1)&0x0F)<<4 | (*(overflow)&0x0F);
*(data+length-1) = *(overflow+1);  

/* You could return a 16-bit carry int, 
 * but endian-ness makes that look weird 
 * if you care about the physical layout */
unsigned short carry = *(overflow+1)<<8 | *overflow;

答案 1 :(得分:4)

这是我的解决方案,但更重要的是我解决问题的方法。

我通过

解决了这个问题
  • 绘制内存单元格并将箭头从目标绘制到源。
  • 制作了一张显示上图的表格。
  • 使用相对字节地址标记表中的每一行。

这显示了我的模式:

  • iL成为a[i]
  • 的低nybble(半字节)
  • iH成为a[i]
  • 的高级别
  • iH = (i+1)L
  • iL = (i+2)H

此模式适用于所有字节。

翻译成C,这意味着:

a[i] = (iH << 4) OR iL
a[i] = ((a[i+1] & 0x0f) << 4) | ((a[i+2] & 0xf0) >> 4)

我们现在再做三个观察:

  • 因为我们从左到右执行分配,所以我们不需要在临时变量中存储任何值。
  • 尾部会有一个特例:最后的所有12 bits都为零。
  • 我们必须避免在数组之外读取未定义的内存。因为我们从未读过a[i+2]以上,这只影响最后两个字节

所以,我们

  • 通过循环N-2 bytes并执行上面的常规计算
  • 来处理一般情况
  • 通过设置iH = (i+1)L
  • 来处理它的倒数第二个字节
  • 通过将最后一个字节设置为0
  • 来处理它

给定长度为a的{​​{1}},我们得到:

N

你有它......数组向左移动for (i = 0; i < N - 2; ++i) { a[i] = ((a[i+1] & 0x0f) << 4) | ((a[i+2] & 0xf0) >> 4); } a[N-2] = (a[N-1) & 0x0f) << 4; a[N-1] = 0; 。它可以很容易地推广到12 bits,注意到N bits M我会相信M = number of bits modulo 8。{/ p>

通过转换为指针

,可以在某些机器上提高循环效率
for (p = a, p2=a+N-2; p != p2; ++p) {
    *p = ((*(p+1) & 0x0f) << 4) | (((*(p+2) & 0xf0) >> 4);
}

并使用CPU支持的最大整数数据类型。

(我刚刚输入了这个,所以现在是个人审查代码的好时机,特别是因为有点容易出错。)

答案 2 :(得分:3)

让它成为在8位整数数组中移位N位的最佳方法。

N            - Total number of bits to shift
F = (N / 8) - Full 8 bit integers shifted
R = (N % 8) - Remaining bits that need to be shifted

我想从这里你必须找到最佳的方法来利用这些数据在数组中移动整数。通用算法是通过从数组右侧开始并移动每个整数F索引来应用完整的整数移位。零填充新空的空间。然后最后从右边开始对所有索引执行R位移位。

在将0xBC移位R位的情况下,您可以通过按位AND计算溢出,并使用bitshift运算符进行移位:

// 0xAB shifted 4 bits is:
(0xAB & 0x0F) >> 4   // is the overflow      (0x0A)
0xAB << 4            // is the shifted value (0xB0)

请记住,4位只是一个简单的掩码:0x0F或只是0b00001111。这很容易计算,动态构建,或者甚至可以使用简单的静态查找表。

我希望这足够通用。我完全不熟悉C / C ++,所以也许有人可以清理我的语法或更具体。

奖励:如果你对你的C很狡猾,你可以将多个数组索引捏合成一个16位,32位甚至64位整数并执行移位。但这可能不是很便携,我建议不要这样做。只是一个可能的优化。

答案 3 :(得分:2)

这是一个使用临时变量的工作解决方案:

void shift_4bits_left(uint8_t* array, uint16_t size)
{
    int i;
    uint8_t shifted = 0x00;    
    uint8_t overflow = (0xF0 & array[0]) >> 4;

    for (i = (size - 1); i >= 0; i--)
    {
        shifted = (array[i] << 4) | overflow;
        overflow = (0xF0 & array[i]) >> 4;
        array[i] = shifted;
    }
}

将此功能调用3次以进行12位移位。

由于使用了临时变量,迈克的解决方案可能更快。

答案 4 :(得分:1)

32位版本...... :-)处理1&lt; = count&lt; = num_words

#include <stdio.h>

unsigned int array[] = {0x12345678,0x9abcdef0,0x12345678,0x9abcdef0,0x66666666};

int main(void) {
  int count;
  unsigned int *from, *to;
  from = &array[0];
  to = &array[0];
  count = 5;

  while (count-- > 1) {
    *to++ = (*from<<12) | ((*++from>>20)&0xfff);
  };
  *to = (*from<<12);

  printf("%x\n", array[0]);
  printf("%x\n", array[1]);
  printf("%x\n", array[2]);
  printf("%x\n", array[3]);
  printf("%x\n", array[4]);

  return 0;
}

答案 5 :(得分:0)

@Joseph,注意变量是8位宽,而移位是12位宽。您的解决方案仅适用于N <=可变大小。

如果您可以假设您的数组是4的倍数,您可以将数组转换为uint64_t数组,然后对其进行处理。如果它不是4的倍数,您可以尽可能多地使用64位块,并逐个处理其余部分。 这可能需要更多编码,但我认为它最终会更加优雅。

答案 6 :(得分:0)

有几个边缘案例使这个问题很简单:

  • 输入数组可能为空
  • 需要特别处理最后一位和倒数第二位,因为它们将零位移入其中

这是一个简单的解决方案,它循环遍历数组,将下一个字节的低位半字节复制到其高位半字节,并将下一个下一个(+2)字节的高位半字节复制到其低位蚕食。为了保存两次取消引用前瞻指针,它维护一个带有“last”和“next”字节的双元素缓冲区:

void shl12(uint8_t *v, size_t length) {
  if (length == 0) {
    return; // nothing to do
  }

  if (length > 1) {
    uint8_t last_byte, next_byte;
    next_byte = *(v + 1);

    for (size_t i = 0; i + 2 < length; i++, v++) {
      last_byte = next_byte;
      next_byte = *(v + 2);
      *v = ((last_byte & 0x0f) << 4) | (((next_byte) & 0xf0) >> 4);
    }

    // the next-to-last byte is half-empty
    *(v++) = (next_byte & 0x0f) << 4;
  }

  // the last byte is always empty
  *v = 0;
}

考虑边界情况,它连续激活函数的更多部分:

  • length为零时,我们会在没有触及记忆的情况下纾困。
  • length为1时,我们将唯一的元素设置为零。
  • length为2时,我们将第一个字节的高位半字节设置为第二个字节的低位半字节(即位12-16),将第二个字节设置为零。我们不会激活循环。
  • length大于2时,我们点击循环,将字节拖过双元素缓冲区。

如果效率是您的目标,答案可能很大程度上取决于您的机器架构。通常,您应该维护双元素缓冲区,但一次处理一个机器字(32/64位无符号整数)。如果您正在移动大量数据,那么将前几个字节视为一种特殊情况是值得的,这样您就可以使机器字指针字对齐。如果访问属于机器字边界,则大多数CPU更有效地访问存储器。当然,必须特别处理尾随字节,这样你就不会触及超出数组末尾的内存。