最快的方法是根据位掩码从值中异或所有位?

时间:2015-09-23 18:48:28

标签: c++ c bit-manipulation xor

我有一个有趣的问题,让我寻找一种更有效的做事方式。

假设我们有一个值(二进制)

(VALUE) 10110001
(MASK)  00110010
----------------
(AND)   00110000

现在,我需要能够对(AND)值中设置的(MASK)值中的任何位进行异或运算(始终从最低位到最高位):

(RESULT) AND1(0) xor AND4(1) xor AND5(1) = 0

现在,在纸面上,这肯定很快,因为我可以看到掩码中设置了哪些位。在我看来,在程序上我需要保持正确移位MASK,直到我找到一个设置位,将其与一个单独的值进行异或,并循环直到整个字节完成。

有人能想到更快的方式吗?我正在寻找使用最少数量的操作和存储值来实现此目的的方法。

4 个答案:

答案 0 :(得分:5)

如果我理解你,你有

result = value & mask

并且您希望将mask & result的1位异或。一系列位的XOR与计数位数和检查该计数是偶数还是奇数相同。如果它是奇数,则XOR为1;如果均匀,XOR将给出0。

count_bits(mask & result) % 2 != 0

mask & result可以简化为result。您不需要再次与mask进行AND运算。 % 2 != 0可以交替写为& 1

count_bits(result) & 1

至于如何计算位数,Bit Twiddling Hacks网页会提供一些bit counting algorithms

  

计数位设置,Brian Kernighan的方式

unsigned int v; // count the number of bits set in v
unsigned int c; // c accumulates the total bits set in v
for (c = 0; v; c++)
{
  v &= v - 1; // clear the least significant bit set
}
     

Brian Kernighan的方法经历了尽可能多的迭代   设置位。因此,如果我们有一个只有高位设置的32位字,那么   它只会循环一次。

如果您要使用该实现,则可以进一步优化它。如果你考虑一下,你就不需要完整的位数。您只需要跟踪他们的奇偶校验。不是计算位数,而是每次迭代都可以翻转c

unsigned bit_parity(unsigned v) {
  unsigned c;
  for (c = 0; v; c ^= 1) {
    v &= v - 1;
  }
}

(感谢Slava提出建议。)

答案 1 :(得分:5)

如果我正确理解了这个问题,你想要的是从MASK中设置的VALUE中获取每一位,并计算这些位的XOR。

首先,请注意,将值设为0并不会改变结果。因此,要忽略某些位,我们可以将它们视为零。

因此,对VALUE中设置的MASK中的位进行异或运算相当于对VALUE& MASK中的位进行异或运算。

现在请注意,如果设置位数是偶数,则结果为0,如果是奇数,则结果为1.

这意味着我们想要计算设置位数。某些体系结构/编译器可以快速计算此值。例如,在GCC上,可以使用__builtin_popcount获得。

所以在GCC上,这可以通过以下方式计算:

int set_bits = __builtin_popcount(value & mask);

return set_bits % 2;

如果您希望代码可移植,那么这不会做。但是,this answer中的评论表明某些编译器可以内联std::bitset::count来有效地获得相同的结果。

答案 2 :(得分:1)

使用带有0的XOR不会改变任何东西,可以应用掩码,然后无条件地将所有位异或,这可以以并行前缀方式完成。所以像这样(未经测试):

x = m & v;
x ^= x >> 16;
x ^= x >> 8;
x ^= x >> 4;
x ^= x >> 2;
x ^= x >> 1;
result = x & 1

您可以根据需要使用更多(或更少)步骤,这是32位。

答案 3 :(得分:0)

如果在代码正文中使用v &= v - 1,则需要注意的一个重要问题是,在进行计数时,它会将v的值更改为0。使用其他方法,该值将更改为1的数量。虽然计数逻辑通常被包装为一个函数,但这不再是一个问题,如果您需要在代码的主体中显示计数逻辑,则必须保留v的副本(如果该值为需要再次。

除了提出的其他两种方法之外,以下是另一种最受欢迎​​的方法,它通常比大数字的循环方法具有更好的性能:

/* get the population 1's in the binary representation of a number */
unsigned getn1s (unsigned int v)
{
    v = v - ((v >> 1) & 0x55555555);
    v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
    v = (v + (v >> 4)) & 0x0F0F0F0F;
    v = v + (v << 8);
    v = v + (v << 16);
    return v >> 24;
}