简体   繁体   English

如何将字节数组移动 12 位

[英]How to shift an array of bytes by 12-bits

I want to shift the contents of an array of bytes by 12-bit to the left.我想将字节数组的内容向左移动 12 位。

For example, starting with this array of type uint8_t shift[10] :例如,从这个uint8_t shift[10]类型的数组开始:

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

I'd like to shift it to the left by 12-bits resulting in:我想将它向左移动 12 位导致:

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

Hurray for pointers!求指点!

This code works by looking ahead 12 bits for each byte and copying the proper bits forward.这段代码通过为每个字节向前看 12 位并向前复制正确的位来工作。 12 bits is the bottom half (nybble) of the next byte and the top half of 2 bytes away. 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;

Justin wrote:贾斯汀写道:
@Mike, your solution works, but does not carry. @Mike,您的解决方案有效,但不适用。

Well, I'd say a normal shift operation does just that (called overflow), and just lets the extra bits fall off the right or left.好吧,我想说一个正常的移位操作就是这样做的(称为溢出),只是让额外的位从右边或左边掉下来。 It's simple enough to carry if you wanted to - just save the 12 bits before you start to shift.如果您愿意,它很容易携带 - 只需在开始移位之前保存 12 位即可。 Maybe you want a circular shift, to put the overflowed bits back at the bottom?也许你想要一个循环移位,把溢出的位放回底部? Maybe you want to realloc the array and make it larger?也许您想重新分配数组并使其更大? Return the overflow to the caller?将溢出返回给调用者? Return a boolean if non-zero data was overflowed?如果非零数据溢出,返回一个布尔值? You'd have to define what carry means to you.你必须定义carry对你意味着什么。

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;

Here's my solution, but even more importantly my approach to solving the problem.这是我的解决方案,但更重要的是我解决问题的方法。

I approached the problem by我解决了这个问题

  • drawing the memory cells and drawing arrows from the destination to the source.绘制内存单元并绘制从目标到源的箭头。
  • made a table showing the above drawing.做了一个表格来显示上面的图。
  • labeling each row in the table with the relative byte address.用相对字节地址标记表中的每一行。

This showed me the pattern:这向我展示了模式:

  • let iL be the low nybble (half byte) of a[i]iLa[i]的低位 nybble(半字节)
  • let iH be the high nybble of a[i]iH成为a[i]的高位
  • iH = (i+1)L
  • iL = (i+2)H

This pattern holds for all bytes.此模式适用于所有字节。

Translating into C, this means:翻译成C,这意味着:

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

We now make three more observations:我们现在再做三个观察:

  • since we carry out the assignments left to right, we don't need to store any values in temporary variables.由于我们从左到右执行赋值,因此我们不需要在临时变量中存储任何值。
  • we will have a special case for the tail: all 12 bits at the end will be zero.我们将有一个尾部的特殊情况:末尾的所有12 bits都为零。
  • we must avoid reading undefined memory past the array.我们必须避免读取数组之外的未定义内存。 since we never read more than a[i+2] , this only affects the last two bytes因为我们从不读取超过a[i+2] ,这只会影响最后两个字节

So, we所以,我们

  • handle the general case by looping for N-2 bytes and performing the general calculation above通过循环N-2 bytes并执行上面的一般计算来处理一般情况
  • handle the next to last byte by it by setting iH = (i+1)L通过设置iH = (i+1)L来处理倒数第二个字节
  • handle the last byte by setting it to 0通过将最后一个字节设置为0来处理它

given a with length N , we get:给定a长度为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;

And there you have it... the array is shifted left by 12 bits .有了它……数组左移了12 bits It could easily be generalized to shifting N bits , noting that there will be M assignment statements where M = number of bits modulo 8 , I believe.它可以很容易地推广到移位N bits ,注意将有M赋值语句,其中M = number of bits modulo 8 ,我相信。

The loop could be made more efficient on some machines by translating to pointers通过转换为指针,可以在某些机器上提高循环效率

for (p = a, p2=a+N-2; p != p2; ++p) {
    *p = ((*(p+1) & 0x0f) << 4) | (((*(p+2) & 0xf0) >> 4);
}

and by using the largest integer data type supported by the CPU.并使用 CPU 支持的最大整数数据类型。

(I've just typed this in, so now would be a good time for somebody to review the code, especially since bit twiddling is notoriously easy to get wrong.) (我刚刚输入了这个,所以现在是有人审查代码的好时机,特别是因为众所周知,有点容易出错。)

Lets make it the best way to shift N bits in the array of 8 bit integers.让我们让它成为在 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

I guess from here you would have to find the most optimal way to make use of this data to move around ints in an array.我想从这里开始,您必须找到利用这些数据在数组中移动整数的最佳方法。 Generic algorithms would be to apply the full integer shifts by starting from the right of the array and moving each integer F indexes.通用算法是通过从数组的右侧开始并移动每个整数F索引来应用完整的整数移位。 Zero fill the newly empty spaces.零填充新的空白空间。 Then finally perform an R bit shift on all of the indexes, again starting from the right.然后最后对所有索引执行R位移,再次从右侧开始。

In the case of shifting 0xBC by R bits you can calculate the overflow by doing a bitwise AND, and the shift using the bitshift operator:在将0xBC移位R位的情况下,您可以通过按位与计算溢出,并使用移位运算符进行移位:

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

Keep in mind that the 4 bits is just a simple mask: 0x0F or just 0b00001111.请记住,这 4 位只是一个简单的掩码:0x0F 或只是 0b00001111。 This is easy to calculate, dynamically build, or you can even use a simple static lookup table.这很容易计算、动态构建,或者您甚至可以使用简单的静态查找表。

I hope that is generic enough.我希望这足够通用。 I'm not good with C/C++ at all so maybe someone can clean up my syntax or be more specific.我根本不擅长 C/C++,所以也许有人可以清理我的语法或更具体。

Bonus: If you're crafty with your C you might be able to fudge multiple array indexes into a single 16, 32, or even 64 bit integer and perform the shifts.奖励:如果您对 C 很熟练,您可能能够将多个数组索引捏造成一个 16 位、32 位甚至 64 位整数并执行移位。 But that is prabably not very portable and I would recommend against this.但这可能不是很便携,我建议不要这样做。 Just a possible optimization.只是一个可能的优化。

Here a working solution, using temporary variables:这是一个工作解决方案,使用临时变量:

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;
    }
}

Call this function 3 times for a 12-bit shift.为 12 位移位调用此函数 3 次。

Mike's solution maybe faster, due to the use of temporary variables.由于使用了临时变量,Mike 的解决方案可能更快。

The 32 bit version... :-) Handles 1 <= count <= num_words 32 位版本... :-) 处理 1 <= count <= 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;
}

@Joseph, notice that the variables are 8 bits wide, while the shift is 12 bits wide. @Joseph,请注意变量为 8 位宽,而移位为 12 位宽。 Your solution works only for N <= variable size.您的解决方案仅适用于 N <= 可变大小。

If you can assume your array is a multiple of 4 you can cast the array into an array of uint64_t and then work on that.如果您可以假设您的数组是 4 的倍数,您可以将数组转换为 uint64_t 数组,然后进行处理。 If it isn't a multiple of 4, you can work in 64-bit chunks on as much as you can and work on the remainder one by one.如果它不是 4 的倍数,您可以尽可能多地处理 64 位块,并逐一处理其余部分。 This may be a bit more coding, but I think it's more elegant in the end.这可能会多一点编码,但我认为它最终更优雅。

There are a couple of edge-cases which make this a neat problem:有几个边缘情况使这成为一个巧妙的问题:

  • the input array might be empty输入数组可能为空
  • the last and next-to-last bits need to be treated specially, because they have zero bits shifted into them需要特别对待最后一位和倒数第二位,因为它们有零位移入其中

Here's a simple solution which loops over the array copying the low-order nibble of the next byte into its high-order nibble, and the high-order nibble of the next-next (+2) byte into its low-order nibble.这是一个简单的解决方案,它遍历数组,将下一个字节的低位半字节复制到其高位半字节中,并将下一个 (+2) 字节的高位半字节复制到其低位半字节中。 To save dereferencing the look-ahead pointer twice, it maintains a two-element buffer with the "last" and "next" bytes:为了避免两次取消引用前瞻指针,它维护了一个包含“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;
}

Consider the boundary cases, which activate successively more parts of the function:考虑边界情况,它们连续激活函数的更多部分:

  • When length is zero, we bail out without touching memory.length为零时,我们在不接触内存的情况下退出。
  • When length is one, we set the one and only element to zero.length为 1 时,我们将唯一的元素设置为零。
  • When length is two, we set the high-order nibble of the first byte to low-order nibble of the second byte (that is, bits 12-16), and the second byte to zero.length为2时,我们将第一个字节的高位半字节设置为第二个字节的低位半字节(即第12-16位),将第二个字节设置为零。 We don't activate the loop.我们不激活循环。
  • When length is greater than two we hit the loop, shuffling the bytes across the two-element buffer.length大于 2 时,我们进入循环,在两个元素的缓冲区中混洗字节。

If efficiency is your goal, the answer probably depends largely on your machine's architecture.如果效率是您的目标,那么答案可能在很大程度上取决于您机器的架构。 Typically you should maintain the two-element buffer, but handle a machine word (32/64 bit unsigned integer) at a time.通常,您应该维护两个元素的缓冲区,但一次处理一个机器字(32/64 位无符号整数)。 If you're shifting a lot of data it will be worthwhile treating the first few bytes as a special case so that you can get your machine word pointers word-aligned.如果您要移动大量数据,将前几个字节视为特殊情况是值得的,这样您就可以使机器字指针字对齐。 Most CPUs access memory more efficiently if the accesses fall on machine word boundaries.如果访问落在机器字边界上,则大多数 CPU 访问内存的效率更高。 Of course, the trailing bytes have to be handled specially too so you don't touch memory past the end of the array.当然,尾随字节也必须特别处理,这样你就不会触及数组末尾的内存。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM