从缓冲区中删除第 n 位,然后移动其余位

Remove nth bit from buffer, and shift the rest

给出一个 uint8_t 长度为 x 的缓冲区,我试图想出一个可以删除第 n 位(或 n 到 n+i)的函数或宏,然后左-移动剩余位。

示例 #1:

对于输入 0b76543210 0b76543210 ... 那么输出应该是 0b76543217 0b654321 ...

示例 #2:如果输入是:

uint8_t input[8] = {
    0b00110011,
    0b00110011,
    ...
};

没有第一位的输出,应该是

uint8_t output[8] = {
    0b00110010,
    0b01100100,
    ...
};

我尝试了以下删除第一位的方法,但它对第二组位不起作用。

/* A macro to extract (a-b) range of bits without shifting */
#define BIT_RANGE(N,x,y) ((N) & ((0xff >> (7 - (y) + (x))) << ((x))))
void removeBit0(uint8_t *n) {
    for (int i=0; i < 7; i++) {
        n[i] = (BIT_RANGE(n[i], i + 1, 7)) << (i + 1) |
               (BIT_RANGE(n[i + 1], 1, i + 1)) << (7 - i); /* This does not extract the next element bits */
    }
    n[7] = 0;
}

更新 #1 在我的例子中,输入将是 uint64_t 数字,然后我将使用 memmov 将其向左移动一位。

更新#2 解决方案可以在C/C++、汇编(x86-64) 或内联汇编中。

类似于此的东西应该可以工作:

template<typename S> void removeBit(S* buffer, size_t length, size_t index)
{
  const size_t BITS_PER_UNIT = sizeof(S)*8;

  // first we find which data unit contains the desired bit
  const size_t unit = index / BITS_PER_UNIT;
  // and which index has the bit inside the specified unit, starting counting from most significant bit
  const size_t relativeIndex = (BITS_PER_UNIT - 1) - index % BITS_PER_UNIT;

  // then we unset that bit
  buffer[unit] &= ~(1 << relativeIndex);

  // now we have to shift what's on the right by 1 position
  // we create a mask such that if 0b00100000 is the bit removed we use 0b00011111 as mask to shift the rest
  const S partialShiftMask = (1 << relativeIndex) - 1;

  // now we keep all bits left to the removed one and we shift left all the others
  buffer[unit] = (buffer[unit] & ~partialShiftMask) | ((buffer[unit] & partialShiftMask) << 1);

  for (int i = unit+1; i < length; ++i)
  {
    //we set rightmost bit of previous unit according to last bit of current unit
    buffer[i-1] |= buffer[i] >> (BITS_PER_UNIT-1);
    // then we shift current unit by one
    buffer[i] <<= 1;
  }
}

我只是在一些基本情况下对其进行了测试,所以可能有些地方不完全正确,但这应该会让您走上正确的轨道。

这实际上是 2 个子问题:从每个字节中删除位并打包结果。这是下面代码的流程。我不会为此使用宏。发生的事情太多了。如果您担心该级别的性能,只需内联函数即可。

#include <stdio.h>
#include <stdint.h>

// Remove bits n to n+k-1 from x.
unsigned scrunch_1(unsigned x, int n, int k) {
  unsigned hi_bits = ~0u << n;
  return (x & ~hi_bits) | ((x >> k) & hi_bits);
}

// Remove bits n to n+k-1 from each byte in the buffer,
// then pack left. Return number of packed bytes.
size_t scrunch(uint8_t *buf, size_t size, int n, int k) {
  size_t i_src = 0, i_dst = 0;
  unsigned src_bits = 0; // Scrunched source bit buffer.
  int n_src_bits = 0;    // Initially it's empty.
  for (;;) {
    // Get scrunched bits until the buffer has at least 8.
    while (n_src_bits < 8) {
      if (i_src >= size) { // Done when source bytes exhausted.
        // If there are left-over bits, add one more byte to output.
        if (n_src_bits > 0) buf[i_dst++] = src_bits << (8 - n_src_bits);
        return i_dst;
      }
      // Pack 'em in.
      src_bits = (src_bits << (8 - k)) | scrunch_1(buf[i_src++], n, k);
      n_src_bits += 8 - k;
    }
    // Write the highest 8 bits of the buffer to the destination byte.
    n_src_bits -= 8;
    buf[i_dst++] = src_bits >> n_src_bits;
  }
}

int main(void) {
  uint8_t x[] = { 0xaa, 0xaa, 0xaa, 0xaa };
  size_t n = scrunch(x, 4, 2, 3);
  for (size_t i = 0; i < n; i++) {
    printf("%x ", x[i]);
  }
  printf("\n");
  return 0;
}

这写b5 ad 60,我认为是正确的。其他一些测试用例也适用。

哎呀我第一次编码时用错了方向,但把它写在这里以防它对某人有用。

#include <stdio.h>
#include <stdint.h>

// Remove bits n to n+k-1 from x.
unsigned scrunch_1(unsigned x, int n, int k) {
  unsigned hi_bits = 0xffu << n;
  return (x & ~hi_bits) | ((x >> k) & hi_bits);
}

// Remove bits n to n+k-1 from each byte in the buffer,
// then pack right. Return number of packed bytes.
size_t scrunch(uint8_t *buf, size_t size, int n, int k) {
  size_t i_src = 0, i_dst = 0;
  unsigned src_bits = 0; // Scrunched source bit buffer.
  int n_src_bits = 0;    // Initially it's empty.
  for (;;) {
    // Get scrunched bits until the buffer has at least 8.
    while (n_src_bits < 8) {
      if (i_src >= size) { // Done when source bytes exhausted.
        // If there are left-over bits, add one more byte to output.
        if (n_src_bits > 0) buf[i_dst++] = src_bits;
        return i_dst;
      }
      // Pack 'em in.
      src_bits |= scrunch_1(buf[i_src++], n, k) << n_src_bits;
      n_src_bits += 8 - k;
    }
    // Write the lower 8 bits of the buffer to the destination byte.
    buf[i_dst++] = src_bits;
    src_bits >>= 8;
    n_src_bits -= 8;
  }
}

int main(void) {
  uint8_t x[] = { 0xaa, 0xaa, 0xaa, 0xaa };
  size_t n = scrunch(x, 4, 2, 3);
  for (size_t i = 0; i < n; i++) {
    printf("%x ", x[i]);
  }
  printf("\n");
  return 0;
}

这样写d6 5a b。其他一些测试用例也适用。