代码之家  ›  专栏  ›  技术社区  ›  artificialidiot

计数,反向位模式

  •  7
  • artificialidiot  · 技术社区  · 17 年前

    我试图找到一个从0到2计数的算法 n -1,但它们的位模式相反。我只关心一个单词的n个LSB。如你所料,我失败了。

    对于n=3:

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

    你明白了。

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

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

    编辑:我忘了添加,我已经有了一个简单的实现,它只是稍微反转一个计数变量。从某种意义上说,这种方法并不真正算数。

    13 回复  |  直到 17 年前
        1
  •  3
  •   Alnitak    17 年前

    我认为这是最简单的比特操作,尽管你说这不是首选

    假设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.

        2
  •  2
  •   Steve Jessop    17 年前

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

    关键思想是,递增一个数字只会翻转一个最低有效位序列,例如 nnnn0111 nnnn1000 因此,为了计算下一个比特反转索引,你必须翻转一个最高有效比特序列。如果你的目标平台有一个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);
        }
    }
    
        3
  •  2
  •   community wiki 8 revs Bill K    17 年前

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

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

    无论你是在bit操作、字符串还是其他什么中这样做,都取决于你。如果你在bitops中这样做,那么在 ~value 可能是最有效的方法:在可用的情况下内置clz。但这是一个实施细节。

        4
  •  1
  •   nwellnhof    8 年前

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

    作为二进制,它更有意义,至少乘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
    
        5
  •  0
  •   Adam Liss    17 年前
    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);
    }
    
        6
  •  0
  •   Assaf Lavie    17 年前

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

        7
  •  0
  •   Andru Luvisi    17 年前

    以下是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
  •  0
  •   Evan Teran    17 年前

    这里有一个解决方案,它实际上并没有尝试进行任何加法,而是利用了序列的开/关模式(大多数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
  •  0
  •   Alexander    17 年前

    如果需要的话,在最高有效位加1,然后转到下一个(较低有效位)怎么样。您可以通过对字节进行操作来加快速度:

    1. 预计算一个查找表,用于从0到256进行反向计数(00000000->10000000,10000000->01000000,…,11111111->00000000)。
    2. 将多字节数中的所有字节设置为零。
    3. 使用查找表递增最高有效字节。如果字节为0,则使用查找表递增下一个字节。如果字节为0,则递增下一个字节。..
    4. 转到步骤3。
        10
  •  0
  •   Svante    17 年前

    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))))))
    
        11
  •  0
  •   Benoit Wickramarachi    13 年前

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

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

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

        12
  •  0
  •   eXtranium    8 年前

    编辑:当然,原始海报的问题是要递增(反向)一,这比添加两个随机值更简单。所以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位倒1(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