【问题标题】:Counting, reversed bit pattern计数,反转位模式
【发布时间】:2008-11-03 16:39:47
【问题描述】:

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

对于 n=3:

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

你明白了。

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

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

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

【问题讨论】:

  • 我可以想出一种非常笨拙的方法来使用字符串和一堆可怕的 if 语句......希望其他人有更好的东西,因为我会不好意思发布它:)
  • 要求位从“位 n”开始,还是可以从最高有效位开始?
  • 我不明白您所说的“但它们的位模式反转”是什么意思。请您解释一下序列的条件吗?
  • ATTN GOOGLER:跳至 nwellnhof 的回答。其余的都是垃圾。

标签: algorithm bit-manipulation


【解决方案1】:

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

假设是 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”为 ,则可以使用更短的版本

【讨论】:

  • 我并没有说 this 实现是最简单的,只是位操作通常是最简单的 :) 这只是最快的算法。 ;)
  • 注意:上面的代码将单词中的位反转,以防有人认不出来。
  • 总而言之,天真的解决方案再次击败了花哨的解决方案。
  • 我认为将这种位交换称为“天真”是夸大其词 :-) 我认为我会支持我的解决方案以提高 ARM 上的速度和代码大小,特别是如果 n 在编译时已知时间,但在 x86 上可能没有那么多。
  • 这可能是位反转单个整数的最快算法,但有更快的方法来迭代位反转索引。看我的回答。
【解决方案2】:

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

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

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

【讨论】:

  • 嗯,找到 value 最左边的 0 等价于找到 (~value) 最左边的 1。或者在这种情况下,实际上是 (~value) & (1
【解决方案3】:

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

二进制更有意义,至少乘以 2 和除以 2 应该是 > 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

【讨论】:

    【解决方案4】:

    这是我的answer to a different question 的一个解决方案,它计算下一个位反转索引而不循环。不过,它在很大程度上依赖于位操作。

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

    使用 GCC 的 __builtin_ctz 的 C 示例:

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

    【讨论】:

      【解决方案5】:
      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);
      }
      

      【讨论】:

      • 这个答案的问题在于,随着 nBits 的增加,算法时间也会增加(线性)。
      【解决方案6】:

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

      【讨论】:

        【解决方案7】:

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

        【讨论】:

          【解决方案8】:

          这是一个解决方案,它实际上并没有尝试做任何加法,而是利用序列的开/关模式(大多数 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));
                  }                       
              }
          }
          

          【讨论】:

          • 随机否决发布后 9 年有效的答案?当然,这是有道理的:-P
          【解决方案9】:

          如果需要,如何将最高有效位加 1,然后进入下一个(较低有效位)位。您可以通过对字节进行操作来加快速度:

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

          【讨论】:

            【解决方案10】:

            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
            

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

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

            (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))))))
            

            【讨论】:

              【解决方案11】:

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

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

              O(1) 操作。无需进行位反转

              【讨论】:

                【解决方案12】:

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


                将两个位反转值相加

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

                旁注:请参阅this question 了解如何转移负值。

                至于测试;从零开始,将值递增 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
                

                【讨论】:

                  【解决方案13】:

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

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

                  11101010(第 4 位,所以 index = 4)

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

                  00001010

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

                  00011010

                  就是这样。这是迄今为止最快的算法,因为大多数 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;
                  }
                  

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

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2010-10-28
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2011-06-15
                    • 1970-01-01
                    • 1970-01-01
                    • 2017-11-18
                    相关资源
                    最近更新 更多