Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove nth bit from buffer, and shift the rest

Giving a uint8_t buffer of x length, I am trying to come up with a function or a macro that can remove nth bit (or n to n+i), then left-shift the remaining bits.

example #1:

for input 0b76543210 0b76543210 ... then output should be 0b76543217 0b654321 ...

example #2: if the input is:

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

the output without the first bit, should be

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

I have tried the following to remove the first bit, but it did not work for the second group of bits.

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

bits Update #1 In my case, the input will be uint64_t number, then I will use memmov to shift it one place to the left.

Update #2 The solution can be in C/C++, assembly(x86-64) or inline assembly.

like image 207
Mohamed El-Sayed Avatar asked Sep 19 '15 16:09

Mohamed El-Sayed


2 Answers

This is really 2 subproblems: remove bits from each byte and pack the results. This is the flow of the code below. I wouldn't use a macro for this. Too much going on. Just inline the function if you're worried about performance at that level.

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

This writes b5 ad 60, which by my reckoning is correct. A few other test cases work as well.

Oops I coded it the first time shifting the wrong way, but include that here in case it's useful to someone.

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

This writes d6 5a b. A few other test cases work as well.

like image 57
Gene Avatar answered Sep 30 '22 08:09

Gene


Something similar to this should work:

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

I just tested it on some basic cases so maybe something is not exactly correct but this should move you onto the right track.

like image 26
Jack Avatar answered Sep 30 '22 07:09

Jack