[英]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(如果可用)。 但這是一個實現細節。
這是我對另一個問題的回答的解決方案,該問題無需循環即可計算下一個位反轉索引。 不過,它在很大程度上依賴於位操作。
關鍵思想是遞增數字只是翻轉一系列最低有效位,例如從nnnn0111
到nnnn1000
。 因此,為了計算下一個位反轉索引,您必須翻轉一系列最高有效位。 如果您的目標平台有 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,然后移至下一個(較不重要的)位。 您可以通過對字節進行操作來加快速度:
編輯:當然,原始海報的問題是要增加(反向)一個,這比添加兩個隨機值更簡單。 所以 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.