簡體   English   中英

計數,反轉位模式

[英]Counting, reversed bit pattern

我試圖找到一種從 0 到 2 n -1 計數的算法,但它們的位模式顛倒了。 我只關心一個詞的 n LSB。 你可能已經猜到我失敗了。

對於 n=3:

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

你明白了。

偽代碼中的答案很棒。 歡迎使用任何語言的代碼片段,首選沒有位操作的答案。

請不要只發布一個片段,甚至沒有簡短的解釋或指向源的指針。

編輯:我忘了補充,我已經有一個簡單的實現,它只是對計數變量進行位反轉。 從某種意義上說,這種方法並不算數。

這是,我認為最簡單的位操作,即使你說這不是首選

假設是 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 應該是 << 1 和 >> 1 以提高速度,加法和減法可能並不重要。

如果您傳入掩碼而不是 nBits,並使用位移而不是乘法或除法,並將尾遞歸更改為循環,這可能是您會發現的性能最高的解決方案,因為其他所有調用都只是一個另外,每 4 次,甚至 8 次調用,它只會像 Alnitak 的解決方案一樣慢。

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

在每一步,找到您的值最左邊的 0 位。 設置它,並清除其左側的所有數字。 如果你沒有找到 0 數字,那么你已經溢出了:返回 0,或者停止,或者崩潰,或者任何你想要的。

這是正常二進制增量時發生的情況(我的意思是它是效果,而不是它在硬件中的實現方式),但我們在左側而不是右側進行。

無論您是在位操作、字符串還是其他方式中執行此操作,都取決於您。 如果您在 bitops 中執行此操作,則~value上的 clz(或調用等效的 hibit 樣式函數)可能是最有效的方法:__builtin_clz(如果可用)。 但這是一個實現細節。

這是我對另一個問題的回答的解決方案,該問題無需循環即可計算下一個位反轉索引。 不過,它在很大程度上依賴於位操作。

關鍵思想是遞增數字只是翻轉一系列最低有效位,例如從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);
    }
}

當您將0 to 2^n-1反轉0 to 2^n-1但它們的位模式反轉時,您幾乎涵蓋了整個0-2^n-1序列

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

O(1)操作。 無需進行位反轉

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

也許從 0 增加到 N(“通常”的方式)並為每次迭代執行 ReverseBitOrder()。你可以在這里找到幾個實現(我最喜歡 LUT 一個)。應該很快。

這是 Perl 中的答案。 你沒有說全一模式之后是什么,所以我只返回零。 我去掉了按位運算,這樣它應該很容易翻譯成另一種語言。

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

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

這是一個解決方案,它實際上並沒有嘗試進行任何添加,而是利用了序列的開/關模式(每次交替最多的 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));
        }                       
    }
}

如有必要,如何將最高有效位加 1,然后移至下一個(較不重要的)位。 您可以通過對字節進行操作來加快速度:

  1. 預先計算一個查找表,用於從 0 到 256 的位反轉計數(00000000 -> 10000000, 10000000 -> 01000000, ..., 11111111 -> 00000000)。
  2. 將多字節數字中的所有字節設置為零。
  3. 使用查找表遞增最高有效字節。 如果字節為 0,則使用查找表遞增下一個字節。 如果字節為 0,則遞增下一個字節...
  4. 轉到步驟 3。

編輯:當然,原始海報的問題是要增加(反向)一個,這比添加兩個隨機值更簡單。 所以 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

假設編號為 1110101,我們的任務是找到下一個。

1) 在最高位置找到零並將位置標記為index

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

它沒有滿足您對“無位”操作的要求,但是我擔心現在有辦法在沒有它們的情況下實現類似的目標。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM