7

我正在尝试找到一种从 0 计数到 2 n -1 的算法,但它们的位模式颠倒了。我只关心一个词的 n LSB。你可能已经猜到我失败了。

对于 n=3:

000 -> 0
100 -> 4
010 -> 2
110 -> 6
001 -> 1
101 -> 5
011 -> 3
111 -> 7

你明白了。

伪代码的答案很棒。欢迎使用任何语言的代码片段,首选没有位操作的答案。

请不要只发布一个片段,甚至没有简短的解释或指向源的指针。

编辑:我忘了补充,我已经有一个简单的实现,它只是对计数变量进行位反转。从某种意义上说,这种方法并不算数。

4

13 回答 13

3

这是,我认为最简单的位操作,即使你说这不是首选

假设是 32 位整数,这里有一段漂亮的代码可以反转所有位,而无需分 32 步完成:

 unsigned int i;
 i = (i & 0x55555555) <<  1 | (i & 0xaaaaaaaa) >>  1;
 i = (i & 0x33333333) <<  2 | (i & 0xcccccccc) >>  2;
 i = (i & 0x0f0f0f0f) <<  4 | (i & 0xf0f0f0f0) >>  4;
 i = (i & 0x00ff00ff) <<  8 | (i & 0xff00ff00) >>  8;
 i = (i & 0x0000ffff) << 16 | (i & 0xffff0000) >> 16;
 i >>= (32 - n);

本质上,这会对所有位进行交错洗牌。每次值中大约一半的位与另一半交换。

最后一行是重新对齐位所必需的,以便 bin“n”是最高有效位。

如果“n”<= 16 或 <= 8,则可以使用更短的版本

于 2008-11-03T16:53:50.007 回答
2

该解决方案最初是二进制的,并按照请求者的指定转换为常规数学。

作为二进制更有意义,至少乘以 2 和除以 2 应该是 << 1 和 >> 1 以提高速度,加法和减法可能无关紧要。

如果您传入掩码而不是 nBits,并使用位移而不是乘法或除法,并将尾递归更改为循环,这可能是您会发现的最高效的解决方案,因为其他所有调用都只是一个添加,它只会像 Alnitak 的解决方案一样慢,每 4 次,甚至 8 次调用一次。

int incrementBizarre(int initial, int nBits)
    // in the 3 bit example, this should create 100
    mask=2^(nBits-1)
    // This should only return true if the first (least significant) bit is not set
    // if initial is 011 and mask is 100
    //                3               4, bit is not set
    if(initial < mask)
        // If it was not, just set it and bail.
        return initial+ mask // 011 (3) + 100 (4) = 111 (7)
    else
        // it was set, are we at the most significant bit yet?
        // mask 100 (4) / 2 = 010 (2), 001/2 = 0 indicating overflow
        if(mask / 2) > 0
            // No, we were't, so unset it (initial-mask) and increment the next bit
            return incrementBizarre(initial - mask, mask/2)
        else
            // Whoops we were at the most significant bit.  Error condition
            throw new OverflowedMyBitsException()

哇,结果有点酷。直到最后一秒,我才想到递归。

感觉不对 - 就像有些操作不应该工作,但它们确实是因为你正在做的事情的性质(就像当你在左边的一些位和一些位上操作时感觉你应该遇到麻烦是非零的,但事实证明,除非左边的所有位都为零,否则您永远无法对位进行操作——这是一个非常奇怪的情况,但确实如此。

从 110 到 001 的流程示例(向后 3 到向后 4):

mask 100 (4), initial 110 (6); initial < mask=false; initial-mask = 010 (2), now try on the next bit
mask 010 (2), initial 010 (2); initial < mask=false; initial-mask = 000 (0), now inc the next bit
mask 001 (1), initial 000 (0); initial < mask=true;  initial + mask = 001--correct answer
于 2008-11-03T16:49:27.300 回答
2

在每一步,找到你的值的最左边的 0 位。设置它,并清除它左边的所有数字。如果你没有找到 0 位,那么你已经溢出:返回 0,或者停止,或者崩溃,或者任何你想要的。

这是在正常二进制增量上发生的情况(我的意思是它是效果,而不是它在硬件中的实现方式),但我们是在左侧而不是右侧进行。

是否使用位操作、字符串或其他方式执行此操作取决于您。如果您在 bitops 中执行此操作,则 clz (或调用等效的 hibit 样式函数)~value可能是最有效的方法: __builtin_clz 如果可用。但这是一个实现细节。

于 2008-11-03T17:09:26.200 回答
2

这是我对另一个问题的回答的解决方案,该问题计算下一个位反转索引而不循环。不过,它在很大程度上依赖于位操作。

关键思想是增加一个数字只是翻转一系列最低有效位,例如 fromnnnn0111nnnn1000。因此,为了计算下一个位反转索引,您必须翻转一系列最高有效位。如果您的目标平台有 CTZ(“计数尾随零”)指令,这可以有效地完成。

使用 GCC 的 C 语言示例__builtin_ctz

void iter_reversed(unsigned bits) {
    unsigned n = 1 << bits;

    for (unsigned i = 0, j = 0; i < n; i++) {
        printf("%x\n", j);

        // Compute a mask of LSBs.
        unsigned mask = i ^ (i + 1);
        // Length of the mask.
        unsigned len = __builtin_ctz(~mask);
        // Align the mask to MSB of n.
        mask <<= bits - len;
        // XOR with mask.
        j ^= mask;
    }
}

如果没有 CTZ 指令,您也可以使用整数除法:

void iter_reversed(unsigned bits) {
    unsigned n = 1 << bits;

    for (unsigned i = 0, j = 0; i < n; i++) {
        printf("%x\n", j);

        // Find least significant zero bit.
        unsigned bit = ~i & (i + 1);
        // Using division to bit-reverse a single bit.
        unsigned rev = (n / 2) / bit;
        // XOR with mask.
        j ^= (n - 1) & ~(rev - 1);
    }
}
于 2017-08-06T11:54:48.810 回答
0
void reverse(int nMaxVal, int nBits)
{
   int thisVal, bit, out;

   // Calculate for each value from 0 to nMaxVal.
   for (thisVal=0; thisVal<=nMaxVal; ++thisVal)
   {
      out = 0;

      // Shift each bit from thisVal into out, in reverse order.
      for (bit=0; bit<nBits; ++bit)
         out = (out<<1) + ((thisVal>>bit) & 1)

   }
   printf("%d -> %d\n", thisVal, out);
}
于 2008-11-03T16:52:33.753 回答
0

也许从 0 增加到 N(“通常”的方式“)并为每次迭代执行 ReverseBitOrder()。你可以在这里找到几个实现(我最喜欢 LUT)。应该很快。

于 2008-11-03T17:12:48.017 回答
0

这是 Perl 的答案。你没有说全1模式之后会发生什么,所以我只返回零。我去掉了按位运算,这样应该很容易翻译成另一种语言。

sub reverse_increment {
  my($n, $bits) = @_;

  my $carry = 2**$bits;
  while($carry > 1) {
    $carry /= 2;
    if($carry > $n) {
      return $carry + $n;
    } else {
      $n -= $carry;
    }
  }
  return 0;
}
于 2008-11-03T19:27:25.093 回答
0

使用 n 作为 2 的幂, x 是要步进的变量:

(defun inv-step (x n)       ; the following is a function declaration
  "returns a bit-inverse step of x, bounded by 2^n"    ; documentation
  (do ((i (expt 2 (- n 1))  ; loop, init of i
          (/ i 2))          ; stepping of i
       (s x))               ; init of s as x
      ((not (integerp i))   ; breaking condition
       s)                   ; returned value if all bits are 1 (is 0 then)
    (if (< s i)                         ; the loop's body: if s < i
        (return-from inv-step (+ s i))  ;     -> add i to s and return the result
        (decf s i))))                   ;     else: reduce s by i

我对它进行了彻底的评论,因为您可能不熟悉这种语法。

编辑:这是尾递归版本。如果你有一个带有尾调用优化的编译器,它似乎会快一点。

(defun inv-step (x n)
  (let ((i (expt 2 (- n 1))))
    (cond ((= n 1)
           (if (zerop x) 1 0))         ; this is really (logxor x 1)                                                 
          ((< x i)
           (+ x i))
          (t
           (inv-step (- x i) (- n 1))))))
于 2008-11-03T19:32:55.050 回答
0

这是一个解决方案,它实际上并没有尝试做任何添加,而是利用序列的开/关模式(大多数 sig 位每次交替,下一个大多数 sig 位每隔一段时间交替一次,等等),根据需要调整 n:

#define FLIP(x, i) do { (x) ^= (1 << (i)); } while(0)

int main() {
    int n   = 3;
    int max = (1 << n);
    int x   = 0;

    for(int i = 1; i <= max; ++i) {
        std::cout << x << std::endl;
        /* if n == 3, this next part is functionally equivalent to this:
         *
         * if((i % 1) == 0) FLIP(x, n - 1);
         * if((i % 2) == 0) FLIP(x, n - 2);
         * if((i % 4) == 0) FLIP(x, n - 3);
         */
        for(int j = 0; j < n; ++j) {
            if((i % (1 << j)) == 0) FLIP(x, n - (j + 1));
        }                       
    }
}
于 2008-11-03T19:57:11.243 回答
0

如有必要,如何将最高有效位加 1,然后传送到下一个(次重要)位。您可以通过对字节进行操作来加快速度:

  1. 预先计算一个查找表,用于从 0 到 256 (00000000 -> 10000000, 10000000 -> 01000000, ..., 11111111 -> 00000000) 的位反转计数。
  2. 将多字节数中的所有字节设置为零。
  3. 使用查找表增加最高有效字节。如果字节为 0,则使用查找表递增下一个字节。如果字节为 0,则递增下一个字节...
  4. 转到第 3 步。
于 2008-11-03T21:42:41.280 回答
0

当您反转0 to 2^n-1但它们的位模式反转时,您几乎涵盖了整个0-2^n-1序列

Sum = 2^n * (2^n+1)/2

O(1)手术。无需进行位反转

于 2013-01-19T16:09:02.907 回答
0

编辑:当然原始发帖人的问题是要增加(反转)一个,这使得事情比添加两个随机值更简单。所以 nwellnhof 的答案已经包含了算法。


将两个位反转值相加

这是php中的一种解决方案:

function RevSum ($a,$b) {

    // loop until our adder, $b, is zero
    while ($b) {

        // get carry (aka overflow) bit for every bit-location by AND-operation
        // 0 + 0 --> 00   no overflow, carry is "0"
        // 0 + 1 --> 01   no overflow, carry is "0"
        // 1 + 0 --> 01   no overflow, carry is "0"
        // 1 + 1 --> 10   overflow! carry is "1"

        $c = $a & $b;


        // do 1-bit addition for every bit location at once by XOR-operation
        // 0 + 0 --> 00   result = 0
        // 0 + 1 --> 01   result = 1
        // 1 + 0 --> 01   result = 1
        // 1 + 1 --> 10   result = 0 (ignored that "1", already taken care above)

        $a ^= $b;


        // now: shift carry bits to the next bit-locations to be added to $a in
        // next iteration.
        // PHP_INT_MAX here is used to ensure that the most-significant bit of the
        // $b will be cleared after shifting. see link in the side note below.

        $b = ($c >> 1) & PHP_INT_MAX;

    }

    return $a;
}

旁注:请参阅有关转移负值的问题。

至于测试;从零开始并按 8 位反转一 (10000000) 递增值:

$value = 0;
$add = 0x80;    // 10000000 <-- "one" as bit reversed

for ($count = 20; $count--;) {      // loop 20 times
    printf("%08b\n", $value);       // show value as 8-bit binary
    $value = RevSum($value, $add);  // do addition
}

...将输出:

 00000000
 10000000
 01000000
 11000000
 00100000
 10100000
 01100000
 11100000
 00010000
 10010000
 01010000
 11010000
 00110000
 10110000
 01110000
 11110000
 00001000
 10001000
 01001000
 11001000
于 2017-08-06T11:11:13.820 回答
0

假设编号为 1110101,我们的任务是找到下一个。

1)在最高位置找到零并将位置标记为索引

111 0 1010(第 4 位,所以索引= 4)

2) 将位置高于index的所有位设置为零。

000 01010

3) 将步骤 1) 中的建立零更改为“1”

000 1 1010

就是这样。这是迄今为止最快的算法,因为大多数 cpu 都有指令可以非常有效地实现这一点。这是一个 C++ 实现,它以反向模式递增 64 位数字。

#include <intrin.h>
unsigned __int64 reversed_increment(unsigned __int64 number) 
{
  unsigned long index, result;
  _BitScanReverse64(&index, ~number); // returns index of the highest '1' on bit-reverse number (trick to find the highest '0')
  result = _bzhi_u64(number, index); // set to '0' all bits at number higher than index position
  result |= (unsigned __int64) 1 << index; // changes to '1' bit on index position
  return result;
}

它没有达到您对“无位”操作的要求,但是我担心现在有办法在没有它们的情况下实现类似的东西。

于 2019-12-06T17:21:40.197 回答