简体   繁体   English

反转位数组中的位顺序

[英]Reverse the order of bits in a bit array

I have a long sequence of bits stored in an array of unsigned long integers, like this 我有一个很长的位序列存储在一个无符号长整数数组中,就像这样

struct bit_array
{
    int size; /* nr of bits */
    unsigned long *array; /* the container that stores bits */
}

I am trying to design an algorithm to reverse the order of bits in *array. 我试图设计一种算法来反转*数组中的位顺序。 Problems: 问题:

  • size can be anything, ie not necessarily a multiple of 8 or 32 etc, so the first bit in the input array can end up at any position within the unsigned long in the output array; size可以是任何东西,即不一定是8或32等的倍数,因此输入数组中的第一位可以在输出数组中的unsigned long内的任何位置结束;
  • the algorithm should be platform-independent, ie work for any sizeof(unsigned long) . 算法应该是平台无关的,即适用于任何sizeof(unsigned long)

Code, pseudocode, algo description etc. -- anything better than bruteforce ("bit by bit") approach is welcome. 代码,伪代码,算法描述等 - 比bruteforce(“一点一滴”)方法更好的方法是受欢迎的。

My favorite solution is to fill a lookup-table that does bit-reversal on a single byte (hence 256 byte entries). 我最喜欢的解决方案是填充一个在单个字节上进行位反转的查找表(因此是256个字节的条目)。

You apply the table to 1 to 4 bytes of the input operand, with a swap. 您将表应用于输入操作数的1到4个字节,并使用交换。 If the size isn't a multiple of 8, you will need to adjust by a final right shift. 如果大小不是8的倍数,则需要通过最终右移来调整。

This scales well to larger integers. 这可以很好地扩展到更大的整数。

Example: 例:

11 10010011 00001010 -> 01010000 11001001 11000000 -> 01 01000011 00100111

To split the number into bytes portably, you need to use bitwise masking/shifts; 要将数字拆分为可移植的字节,您需要使用按位屏蔽/移位; mapping of a struct or array of bytes onto the integer can make it more efficient. 将结构或字节数组映射到整数可以使其更有效。

For brute performance, you can think of mapping up to 16 bits at a time, but this doesn't look quite reasonable. 对于粗暴的性能,您可以考虑一次最多映射16位,但这看起来不太合理。

I like the idea of lookup table. 我喜欢查找表的想法。 Still it's also a typical task for log(n) group bit tricks that may be very fast. 它仍然是log(n)组位技巧的典型任务,可能非常快。 Like: 喜欢:

unsigned long reverseOne(unsigned long x) {
  x = ((x & 0xFFFFFFFF00000000) >> 32) | ((x & 0x00000000FFFFFFFF) << 32);
  x = ((x & 0xFFFF0000FFFF0000) >> 16) | ((x & 0x0000FFFF0000FFFF) << 16);
  x = ((x & 0xFF00FF00FF00FF00) >> 8)  | ((x & 0x00FF00FF00FF00FF) << 8);
  x = ((x & 0xF0F0F0F0F0F0F0F0) >> 4)  | ((x & 0x0F0F0F0F0F0F0F0F) << 4);
  x = ((x & 0xCCCCCCCCCCCCCCCC) >> 2)  | ((x & 0x3333333333333333) << 2);
  x = ((x & 0xAAAAAAAAAAAAAAAA) >> 1)  | ((x & 0x5555555555555555) << 1);
  return x;
}

The underlying idea is that when we aim to reverse the order of some sequence we may swap the head and tail halves of this sequence and then separately reverse each of halves (which is done here by applying the same procedure recursively to each half). 根本的想法是,当我们的目标是颠倒某个序列的顺序时,我们可以交换该序列的头部和尾部,然后分别反转每一半(这里通过递归地对每一半应用相同的过程来完成)。

Here is a more portable version supporting unsigned long widths of 4,8,16 or 32 bytes. 这是一个更便携的版本,支持4,8,16或32字节的unsigned long宽度。

#include <limits.h>

#define ones32 0xFFFFFFFFUL
#if (ULONG_MAX >> 128)
#define fill32(x) (x|(x<<32)|(x<<64)|(x<<96)|(x<<128)|(x<<160)|(x<<192)|(x<<224))
#define patt128 (ones32|(ones32<<32)|(ones32<<64) |(ones32<<96))
#define patt64  (ones32|(ones32<<32)|(ones32<<128)|(ones32<<160))
#define patt32  (ones32|(ones32<<64)|(ones32<<128)|(ones32<<192))
#else
#if (ULONG_MAX >> 64)
#define fill32(x) (x|(x<<32)|(x<<64)|(x<<96))
#define patt64  (ones32|(ones32<<32))
#define patt32  (ones32|(ones32<<64))
#else
#if (ULONG_MAX >> 32)
#define fill32(x) (x|(x<<32))
#define patt32  (ones32)
#else
#define fill32(x) (x)
#endif
#endif
#endif

unsigned long reverseOne(unsigned long x) {
#if (ULONG_MAX >> 32)
#if (ULONG_MAX >> 64)
#if (ULONG_MAX >> 128)
  x = ((x & ~patt128) >> 128) | ((x & patt128) << 128);
#endif
  x = ((x & ~patt64) >> 64) | ((x & patt64) << 64);
#endif
  x = ((x & ~patt32) >> 32) | ((x & patt32) << 32);
#endif
  x = ((x & fill32(0xffff0000UL)) >> 16) | ((x & fill32(0x0000ffffUL)) << 16);
  x = ((x & fill32(0xff00ff00UL)) >> 8)  | ((x & fill32(0x00ff00ffUL)) << 8);
  x = ((x & fill32(0xf0f0f0f0UL)) >> 4)  | ((x & fill32(0x0f0f0f0fUL)) << 4);
  x = ((x & fill32(0xccccccccUL)) >> 2)  | ((x & fill32(0x33333333UL)) << 2);
  x = ((x & fill32(0xaaaaaaaaUL)) >> 1)  | ((x & fill32(0x55555555UL)) << 1);
  return x;
}

In a collection of related topics which can be found here , the bits of an individual array entry could be reversed as follows. 这里可以找到的相关主题的集合中,单个数组条目的位可以如下反转。

unsigned int v;     // input bits to be reversed
unsigned int r = v; // r will be reversed bits of v; first get LSB of v
int s = sizeof(v) * CHAR_BIT - 1; // extra shift needed at end

for (v >>= 1; v; v >>= 1)
{   
  r <<= 1;
  r |= v & 1;
  s--;
}
r <<= s; // shift when v's highest bits are zero

The reversal of the entire array could be done afterwards by rearranging the individual positions. 之后可以通过重新排列各个位置来完成整个阵列的反转。

You must define what is the order of bits in an unsigned long . 您必须定义unsigned long的位顺序。 You might assume that bit n is corresponds to array[x] & (1 << n) but this needs to be specified. 您可以假设位n对应于array[x] & (1 << n)但这需要指定。 If so, you need to handle the byte ordering (little or big endian) if you are going to use access the array as bytes instead of unsigned long. 如果是这样,如果要使用数组作为字节而不是unsigned long,则需要处理字节顺序(小端或大端)。

I would definitely implement brute force first and measure whether the speed is an issue. 我肯定会首先实施蛮力并测量速度是否是一个问题。 No need to waste time trying to optimize this if it is not used a lot on large arrays. 如果在大型阵列上没有大量使用,则无需浪费时间尝试优化它。 An optimized version can be tricky to implement correctly. 优化版本可能很难正确实现。 If you end up trying anyway, the brute force version can be used to verify correctness on test values and benchmark the speed of the optimized version. 无论如何最终都会尝试,可以使用强力版本来验证测试值的正确性,并对优化版本的速度进行基准测试。

The fact that the size is not multiple of sizeof(long) is the hardest part of the problem. 大小不是sizeof(long)倍数这一事实是问题中最难的部分。 This can result in a lot of bit shifting. 这可能导致大量的位移。

But, you don't have to do that if you can introduce new struct member: 但是,如果您可以引入新的struct成员,则不必这样做:

struct bit_array
{
    int size; /* nr of bits */
    int offset; /* First bit position */
    unsigned long *array; /* the container that stores bits */
}

Offset would tell you how many bits to ignore at the beginning of the array. 偏移量会告诉您在数组开头要忽略多少位。

Then you only only have to do following steps: 那你只需要做以下步骤:

  1. Reverse array elements. 反向数组元素。
  2. Swap bits of each element. 交换每个元素的位。 There are many hacks for in the other answers, but your compiler might also provide intrisic functions to do it in fewer instructions (like RBIT instruction on some ARM cores). 在其他答案中有许多黑客,但您的编译器也可能提供内在函数来执行更少的指令(如某些ARM内核上的RBIT指令)。
  3. Calculate new starting offset. 计算新的起始偏移量。 This is equal to unused bits the last element had. 这等于最后一个元素的未使用位。

I would split the problem into two parts. 我会将问题分成两部分。

First, I would ignore the fact that the number of used bits is not a multiple of 32. I would use one of the given methods to swap around the whole array like that. 首先,我会忽略这样一个事实,即使用的位数不是32的倍数。我会使用给定方法之一来交换整个数组。

pseudocode: 伪代码:

for half the longs in the array:
    take the first longword;
    take the last longword;
    swap the bits in the first longword
    swap the bits in the last longword;

    store the swapped first longword into the last location;
    store the swapped last longword into the first location;

and then fix up the fact that the first few bits (call than number n ) are actually garbage bits from the end of the longs: 然后修复一个事实,即前几位(调用数字n )实际上是longs末尾的垃圾位:

for all of the longs in the array:
    split the value in the leftmost n bits and the rest;
    store the leftmost n bits into the righthand part of the previous word;
    shift the rest bits to the left over n positions (making the rightmost n bits zero);
    store them back;

You could try to fold that into one pass over the whole array of course. 您可以尝试将其折叠成整个阵列中的一个传递。 Something like this: 像这样的东西:

for half the longs in the array:
    take the first longword;
    take the last longword;
    swap the bits in the first longword
    swap the bits in the last longword;

    split both value in the leftmost n bits and the rest;

    for the new first longword:
        store the leftmost n bits into the righthand side of the previous word;
        store the remaining bits into the first longword, shifted left;

    for the new last longword:
        remember the leftmost n bits for the next iteration;
        store the remembered leftmost n bits, combined with the remaining bits, into the last longword;

    store the swapped first longword into the last location;
    store the swapped last longword into the first location;

I'm abstracting from the edge cases here (first and last longword), and you may need to reverse the shifting direction depending on how the bits are ordered inside each longword. 我从这里的边缘情况(第一个和最后一个长字)中抽象出来,你可能需要根据每个长字内部的位排序方式来反转移位方向。

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

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