【问题标题】:clear all but the two most significant set bits in a word清除一个字中除了两个最重要的设置位之外的所有位
【发布时间】:2010-07-21 23:22:18
【问题描述】:

给定一个已知至少设置了 2 位的 32 位 int,有没有办法有效地清除除 2 个最高有效设置位之外的所有位?即我想确保输出正好设置了 2 位。

如果保证输入只有 2 或 3 位设置怎么办?

例子:

0x2040 -> 0x2040
0x0300 -> 0x0300
0x0109 -> 0x0108
0x5040 -> 0x5000

基准测试结果:

代码:

QueryPerformanceFrequency(&freq);
/***********/
value = (base =2)|1;
QueryPerformanceCounter(&start);
for (l=0;l<A_LOT; l++)
{
  //!!value calculation goes here
  junk+=value;    //use result to prevent optimizer removing it.

  //advance to the next 2|3 bit word
  if (value&0x80000000)
  {  if (base&0x80000000)
     {  base=6;
     }
     base*=2;
     value=base|1;
  }
  else
  {  value<<=1;
  }
}
QueryPerformanceCounter(&end);
time = (end.QuadPart - start.QuadPart);
time /= freq.QuadPart;
printf("--------- name\n");
printf("%ld loops took %f sec (%f additional)\n",A_LOT, time, time-baseline);
printf("words /sec = %f Million\n",A_LOT/(time-baseline)/1.0e6); 

在 Core2Duo E7500@2.93 GHz 上使用 VS2005 默认版本设置的结果:

--------- BASELINE
1000000 loops took 0.001630 sec
--------- sirgedas
1000000 loops took 0.002479 sec (0.000849 additional)
words /sec = 1178.074206 Million
--------- ashelly
1000000 loops took 0.004640 sec (0.003010 additional)
words /sec = 332.230369 Million
--------- mvds
1000000 loops took 0.005250 sec (0.003620 additional)
words /sec = 276.242030 Million
--------- spender
1000000 loops took 0.009594 sec (0.007964 additional)
words /sec = 125.566361 Million
--------- schnaader
1000000 loops took 0.025680 sec (0.024050 additional)
words /sec = 41.580158 Million

【问题讨论】:

  • 我们说的是 32 位还是 16 位?我们必须处理的数字是多少?即低于 10 或高于 100?
  • 32 位,以百万/秒的速度循环。
  • 那么如果我们有 10101,那是 10000 还是 10100?
  • 32 位 @ 百万:一定要去查找表!
  • 感谢您的回答 - 我明天会进行基准测试。

标签: algorithm language-agnostic bit-manipulation


【解决方案1】:

如果保证输入恰好有 2 或 3 位,则可以非常快速地计算出答案。我们利用表达式 x&(x-1) 等于 x 且 LSB 清零的事实。如果设置了 2 个或更少的位,则将该表达式两次应用于输入将产生 0。如果恰好设置了 2 位,我们返回原始输入。否则,我们返回原始输入并清除 LSB。

这是 C++ 中的代码:

// assumes a has exactly 2 or 3 bits set
int topTwoBitsOf( int a ) 
{
   int b = a&(a-1);         // b = a with LSB cleared
   return b&(b-1) ? b : a;  // check if clearing the LSB of b produces 0
}

如果你愿意,这可以写成一个令人困惑的单一表达式:

int topTwoBitsOf( int a )
{
   return a&(a-1)&((a&(a-1))-1) ? a&(a-1) : a;
}

【讨论】:

  • 您说的是“字节”,但您的意思是“位”。
【解决方案2】:

我会在循环中创建一个蒙版。开始时,掩码为 0。然后从 MSB 到 LSB,并将掩码中的每个对应位设置为 1,直到找到 2 个设置位。最后将值与此掩码相加。

#include <stdio.h>
#include <stdlib.h>

int clear_bits(int value) {

  unsigned int mask = 0;
  unsigned int act_bit = 0x80000000;
  unsigned int bit_set_count = 0;

  do {
    if ((value & act_bit) == act_bit) bit_set_count++;
    mask = mask | act_bit;
    act_bit >>= 1;
  } while ((act_bit != 0) && (bit_set_count < 2));

  return (value & mask);
}

int main() {
  printf("0x2040 => %X\n", clear_bits(0x2040));
  printf("0x0300 => %X\n", clear_bits(0x0300));
  printf("0x0109 => %X\n", clear_bits(0x0109));
  printf("0x5040 => %X\n", clear_bits(0x5040));
  return 0;
}

这相当复杂,但应该更有效,因为每次都在 32 位上使用 for 循环(并清除除 2 个最重要的集合之外的所有位)。无论如何,请务必在使用之前对不同的方法进行基准测试。

当然,如果内存不是问题,请使用一些推荐的查找表方法 - 这会快得多。

【讨论】:

  • 我有兴趣保留已设置的最高 2 位。在上面添加了示例。
  • 啊,我明白了。这是一个完全不同的问题。
  • 编辑了我的答案。现在是合适的。
  • gcc -O3,至强 L5520@2.27 GHz:100m 回合为 3.284 秒,而桌为 1.236 秒。 (顺便说一句,100m 随机回合的输出是相同的)
【解决方案3】:

在什么延迟下有多少内存可用?我会提出一个查找表;-)

但说真的:如果您要对 100 个数字执行此操作,那么您可能只需要一个提供 2 msb 的 8 位查找表和另一个提供 1 msb 的 8 位查找表。根据处理器的不同,这可能会超过真正的位数。

为了速度,我会创建一个查找表,将输入字节映射到

如果设置了 1 或 0 位,则 M(I)=0

M(I)=B' 否则,其中 B' 是设置了 2 msb 位的 B 的值。

您的 32 位 int 是 4 个输入字节 I1 I2 I3 I4。
查找 M(I1),如果非零,则完成。
比较 M(I1)==0,如果为零,对 I2 重复上一步。
否则,在具有 1 个 MSB 位的第二个查找表中查找 I2,如果非零,则完成。 否则,对 I3 重复上一步。

等等等等。实际上不要在 I1-4 上循环任何东西,而是完全展开它。

总结:2 个包含 256 个条目的查找表,一次查找解决了 247/256 个案例,两次查找解决了大约 8/256,等等。

编辑:表,为清楚起见(输入,位表 2 MSB,位表 1 MSB)

  I    table2    table1
  0  00000000  00000000
  1  00000000  00000001
  2  00000000  00000010
  3  00000011  00000010
  4  00000000  00000100
  5  00000101  00000100
  6  00000110  00000100
  7  00000110  00000100
  8  00000000  00001000
  9  00001001  00001000
 10  00001010  00001000
 11  00001010  00001000
 12  00001100  00001000
 13  00001100  00001000
 14  00001100  00001000
 15  00001100  00001000
 16  00000000  00010000
 17  00010001  00010000
 18  00010010  00010000
 19  00010010  00010000
 20  00010100  00010000
 ..
250  11000000  10000000
251  11000000  10000000
252  11000000  10000000
253  11000000  10000000
254  11000000  10000000
255  11000000  10000000

【讨论】:

  • 我的目标是低内存平台。 64K 条目太多了。我可以处理 256 个条目,但我看不到将多个查找的结果拼接在一起的好方法。
  • 编辑了答案以考虑 RAM 小于 2^32*4 字节的平台。
  • 我不明白:对于第三个示例,第一个表将给出 [0,1,0,9]。然后呢?
  • 添加了粗略的实现。您查找第一个字节,如果它设置了 2 位,您就完成了(结果将是这个字节和 0x000000)。如果没有,表格列出了 0,因此您必须检查:第一个字节为 0(0 位)或设置了 1 位,在这种情况下,您必须在第二个输入字节中找到第一位。等等等等。
  • 这是一种非常强大的方法,可以处理许多其他棘手的位处理问题。这会将您的每位掩码和比较循环转换为仅使用 512 字节 ROM 的一小段加载比较指令。
【解决方案4】:

这是另一个尝试(没有循环、没有查找、没有条件)。这次成功了:

var orig=0x109;
var x=orig;
x |= (x >> 1);
x |= (x >> 2);
x |= (x >> 4);
x |= (x >> 8);
x |= (x >> 16);
x = orig & ~(x & ~(x >> 1));
x |= (x >> 1);
x |= (x >> 2);
x |= (x >> 4);
x |= (x >> 8);
x |= (x >> 16);
var solution=orig & ~(x >> 1);
Console.WriteLine(solution.ToString("X")); //0x108

可能会被比我聪明的人缩短。

【讨论】:

    【解决方案5】:

    跟进我之前的回答,这里是完整的实现。我认为它尽可能快。 (很抱歉展开整个事情 ;-)

    #include <stdio.h>
    unsigned char bittable1[256];
    unsigned char bittable2[256];
    
    unsigned int lookup(unsigned int);
    void gentable(void);
    
    int main(int argc,char**argv)
    {
        unsigned int challenge = 0x42341223, result;
        gentable();
    
        if ( argc > 1 ) challenge = atoi(argv[1]);
    
        result = lookup(challenge);
    
        printf("%08x --> %08x\n",challenge,result);
    }
    
    unsigned int lookup(unsigned int i)
    {
        unsigned int ret;
    
        ret = bittable2[i>>24]<<24; if ( ret ) return ret;
        ret = bittable1[i>>24]<<24;
        if ( !ret )
        {
                ret = bittable2[i>>16]<<16; if ( ret ) return ret;
                ret = bittable1[i>>16]<<16;
                if ( !ret )
                {
                        ret = bittable2[i>>8]<<8; if ( ret ) return ret;
                        ret = bittable1[i>>8]<<8;
                        if ( !ret )
                        {
                                return bittable2[i] | bittable1[i];
                        } else {
                                return (ret | bittable1[i&0xff]);
                        }
                } else {
                        if ( bittable1[(i>>8)&0xff] )
                        {
                                return (ret | (bittable1[(i>>8)&0xff]<<8));
                        } else {
                                return (ret | bittable1[i&0xff]);
                        }
                }
        } else {
                if ( bittable1[(i>>16)&0xff] )
                {
                        return (ret | (bittable1[(i>>16)&0xff]<<16));
                } else if ( bittable1[(i>>8)&0xff] ) {
                        return (ret | (bittable1[(i>>8)&0xff]<<8));
                } else {
                        return (ret | (bittable1[i&0xff]));
                }
        }
    }
    
    void gentable()
    {
        int i;
        for ( i=0; i<256; i++ )
        {
                int bitset = 0;
                int j;
                for ( j=128; j; j>>=1 )
                {
                        if ( i&j )
                        {
                                bitset++;
                                if ( bitset == 1 ) bittable1[i] = i&(~(j-1));
                                else if ( bitset == 2 ) bittable2[i] = i&(~(j-1));
                        }
                }
                //printf("%3d %02x %02x\n",i,bittable1[i],bittable2[i]);
        }
    }
    

    【讨论】:

    • 有一个假设:必须有 2 位(如要求中所述)它会在最后一个字节中仅设置 1 位的情况下失败。 -- 固定
    • 一个关于优化的有趣说明:我认为拥有查找表 unsigned int 和预移位 &lt;&lt; 24 会使事情变得更快(将一个 &lt;&lt;24 排除在循环之外),但这并没有什么区别完全...
    【解决方案6】:

    使用this 的变体,我想出了以下内容:

    var orig=56;
    var x=orig;
    x |= (x >> 1);
    x |= (x >> 2);
    x |= (x >> 4);
    x |= (x >> 8);
    x |= (x >> 16);
    Console.WriteLine(orig&~(x>>2));
    

    在 c# 中,但应该很容易翻译。

    编辑

    我不太确定我是否已回答您的问题。这需要最高位并保留它和它旁边的位,例如。 101 => 100

    【讨论】:

    • 不是我正在寻找的答案 - 它仅适用于 2 个连续位。例如,它在0x0109 -&gt; 0x0108 上失败。
    【解决方案7】:

    这是一些应该可以工作的python:

    def bit_play(num):
        bits_set = 0
        upper_mask = 0
        bit_index = 31
        while bit_index >= 0:
            upper_mask |= (1 << bit_index)
            if num & (1 << bit_index) != 0:
                bits_set += 1
                if bits_set == 2:
                    num &= upper_mask
                    break
            bit_index -= 1
        return num
    

    它对数字进行了一次传递。它构建了一个它穿过的位的掩码,因此它可以在到达第二个最重要的位时立即掩蔽底部位。 一旦找到第二位,它就会继续清除低位。您应该能够创建一个高位掩码和 &amp;= 它,而不是第二个 while 循环。也许我会破解它并编辑帖子。

    【讨论】:

      【解决方案8】:

      我也会使用基于表格的方法,但我相信仅一个表格就足够了。以 4 位的情况为例。如果您的输入保证有 2 位或 3 位,那么您的输出只能是 6 个值之一

            0011
            0101
            0110
            1001
            1010
            1100
      

      将这些可能的值放入按大小排序的数组中。从最大值开始,找到等于或小于目标值的第一个值。这是你的答案。对于 8 位版本,您将有更多可能的返回值,但仍然很容易小于 8*7 的最大可能排列。

      public static final int [] MASKS = {
              0x03, //0011
              0x05, //0101
              0x06, //0110
              0x09, //1001
              0x0A, //1010
              0x0C, //1100
      };
      
      
          for (int i = 0; i < 16; ++i) {
              if (countBits(i) < 2) {
                  continue;
              }
              for (int j = MASKS.length - 1; j >= 0; --j) {
                  if (MASKS[j] <= i) {
                      System.out.println(Integer.toBinaryString(i) + " " + Integer.toBinaryString(MASKS[j]));
                      break;
                  }
              }
          }
      

      【讨论】:

        【解决方案9】:

        这是我在 C# 中的实现

        uint OnlyMostSignificant(uint value, int count) {
            uint newValue = 0;
            int c = 0;
        
            for(uint high = 0x80000000; high != 0 && c < count; high >>= 1) {
                if ((value & high) != 0) {
                    newValue = newValue | high;
                    c++;
                }
            }
        
            return newValue;
        }
        

        使用计数,您可以使其成为最重要的(计数)位。

        【讨论】:

          【解决方案10】:

          我的解决方案:

          使用"The best method for counting bits in a 32-bit integer",如果答案为 3,则清除低位。仅当输入限制为 2 或 3 位设置时才有效。

          unsigned int c; // c is the total bits set in v
          unsigned int v = value;
          v = v - ((v >> 1) & 0x55555555);                    
          v = (v & 0x33333333) + ((v >> 2) & 0x33333333);     // temp
          c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24; // count
          
          crc+=value&value-(c-2);
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-06-05
            • 2011-08-13
            • 2017-11-18
            • 2013-09-17
            • 1970-01-01
            • 2018-03-22
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多