【问题标题】:Finding N contiguous zero bits in an integer to the left of the MSB position of another integer在另一个整数的 MSB 位置左侧的整数中查找 N 个连续的零位
【发布时间】:2010-05-07 21:52:31
【问题描述】:

问题是:给定一个整数val1 找到最高位集合的位置(Most Significant Bit)然后,给定第二个整数val2 在从产生的位置左侧找到一个未设置位的连续区域第一个整数。 width 指定必须在连续中找到的未设置位的最小 数量(即width 没有其中的零)。

这是我的解决方案的 C 代码:

#include <limits.h> /* for CHAR_BIT - number of bits in a char */

typedef unsigned int t;
unsigned const t_bits = sizeof(t) * CHAR_BIT;

_Bool test_fit_within_left_of_msb(  unsigned width,
                                    t val1, /* integer to find MSB of */
                                    t val2, /* integer to find width zero bits in */
                                    unsigned* offset_result)
{
    unsigned offbit = 0; /* 0 starts at high bit */
    unsigned msb = 0;
    t mask;
    t b;

    while(val1 >>= 1) /* find MSB! */
        ++msb;

    while(offbit + width < t_bits - msb)
    {
        /* mask width bits starting at offbit */
        mask = (((t)1 << width) - 1) << (t_bits - width - offbit);
        b = val2 & mask;

        if (!b) /* result! no bits set, we can use this */
        {
            *offset_result = offbit;
            return true;
        }

        if (offbit++) /* this conditional bothers me! */
            b <<= offbit - 1;

        while(b <<= 1)
            offbit++; /* increment offbit past all bits set */
    }
    return false; /* no region of width zero bits found, bummer. */
}

除了找到第一个整数的 MSB 的更快方法之外,对零 offbit 的注释测试似乎有点无关紧要,但如果设置了 t 类型的最高位,则必须跳过它。无条件地将b 左移offbit - 1 位将导致无限循环,并且掩码永远不会超过 val2 高位中的 1(否则,如果高位为零也没问题)。

我也实现了类似的算法,但在第一个数字的 MSB 右侧工作,因此它们不需要这个看似额外的条件。

我怎样才能摆脱这种额外的条件,甚至,是否有更优化的解决方案?

编辑:一些背景不是严格要求的。偏移结果是从高位开始的位数,而不是从低位开始的计数。这将是更广泛算法的一部分,该算法扫描 2D 阵列以查找 2D 零位区域。 在这里,为了测试,算法已经被简化。 val1 表示在二维数组的一行中没有设置所有位的第一个整数。由此二维版本将向下扫描,这就是val2 所代表的内容。

以下是一些显示成功和失败的输出:

t_bits:32
     t_high: 10000000000000000000000000000000 ( 2147483648 )
---------

-----------------------------------
*** fit within left of msb test ***
-----------------------------------
      val1:  00000000000000000000000010000000 ( 128 )
      val2:  01000001000100000000100100001001 ( 1091569929 )
msb:   7
offbit:0 + width: 8 = 8
      mask:  11111111000000000000000000000000 ( 4278190080 )
      b:     01000001000000000000000000000000 ( 1090519040 )
offbit:8 + width: 8 = 16
      mask:  00000000111111110000000000000000 ( 16711680 )
      b:     00000000000100000000000000000000 ( 1048576 )
offbit:12 + width: 8 = 20
      mask:  00000000000011111111000000000000 ( 1044480 )
      b:     00000000000000000000000000000000 ( 0 )
offbit:12
iters:10


***** found room for width:8 at offset: 12 *****

-----------------------------------
*** fit within left of msb test ***
-----------------------------------
      val1:  00000000000000000000000001000000 ( 64 )
      val2:  00010000000000001000010001000001 ( 268469313 )
msb:   6
offbit:0 + width: 13 = 13
      mask:  11111111111110000000000000000000 ( 4294443008 )
      b:     00010000000000000000000000000000 ( 268435456 )
offbit:4 + width: 13 = 17
      mask:  00001111111111111000000000000000 ( 268402688 )
      b:     00000000000000001000000000000000 ( 32768 )
 ***** mask: 00001111111111111000000000000000 ( 268402688 )
offbit:17
iters:15


***** no room found for width:13 *****

(iters 是内部while循环的迭代次数,b 是结果val2 &amp; mask

【问题讨论】:

  • 您要查找的内容并不完全清楚。我怀疑这与您之前关于块放置的问题有关,并且您正在尝试使用位域来执行此操作,但我仍然不确定该函数应该做什么。
  • @nategoose,只是在编辑以提供您评论的一些背景。
  • 目前还不清楚你想要什么。这个问题的标题以“来自另一个”结尾——来自另一个什么?我认为您正在尝试做的是在整数(哪个?)中找到width 0 位的区域。变量val1val2 的命名非常糟糕。 CHAR_BIT 未定义。

标签: c bit-manipulation


【解决方案1】:

这个http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious 有几种方法可以计算无符号整数的无符号整数对数基数 2(这也是最高位集合的位置)。

我认为这是您想要的一部分。我怀疑如果我真的知道您想要什么,我可以建议一种更好的计算方法或达到相同目的的方法。

【讨论】:

  • 我现在已经对问题进行了数千次编辑,希望代码 cmets、问题的改写以及包含(稍微)更好的测试用例将有助于您更好地理解问题- 哪一个,我开始相信我会得到零答案,并浪费了几个小时试图澄清一些已经有效的东西!
【解决方案2】:

count_leading_zero_bits 通常是编译器为其提供内联函数的单个指令。否则把它放在一个循环中。

count_trailing_zero_bits 可以使用 count_leading_zero_bits(x&-x) 或 debruijn 查找(如果前者是循环)。

为简单起见,我假设为 32 位值。

int offset_of_zero_bits_over_msb_of_other_value( unsigned width , unsigned value , unsigned other ) {
  int count = 0;
  int offset = -1;
  int last = 1;
  int lz = count_leading_zero_bits( other );
  other |= ((1<<(32-lz2))-1); // set all bits below msb
  if ( value & ~other ) {
    value |= other; // set all bits below msb of other
    value = ~value; // invert so zeros are ones
    while ( value && count < width ) {
      count += 1; // the widest run of zeros
      last = value; // for counting trailing zeros
      value &= value >> 1; // clear leading ones from groups
    }
    offset = count_trailing_zero_bits( last );
  } else {
    count = lz2;
    offset = 32 - lz2;
  }
  return ( count < width ) ? -1 : offset;
}

代码背后的想法是这样的:

  val1:  00000000000000000000000010000000 ( 128 )
  val2:  01000001000100000000100100001001 ( 1091569929 )
  lz1:   24
  lz2:   1
  val2:  01000001000100000000100011111111 // |= ((1<<(32-lz1))-1);
  val2:  10111110111011111111011100000000 // = ~val2
  val2:  00011110011001111111001100000000 // &= val2>>1 , count = 1
  val2:  00001110001000111111000100000000 // &= val2>>1 , count = 2
  val2:  00000110000000011111000000000000 // &= val2>>1 , count = 3
  val2:  00000010000000001111000000000000 // &= val2>>1 , count = 4
  val2:  00000000000000000111000000000000 // &= val2>>1 , count = 5
  val2:  00000000000000000011000000000000 // &= val2>>1 , count = 6
  val2:  00000000000000000001000000000000 // &= val2>>1 , count = 7
  val2:  00000000000000000000000000000000 // &= val2>>1 , count = 8

因此,在每一步中,所有零范围,现在是零,都从右侧缩小。当该值为零时,所采取的步数是最宽运行的宽度。在任何时候,计算尾随零的数量都​​会将偏移量提供到至少count 零的最近范围。

如果在任何时候计数超过宽度,您可以停止迭代。因此,最大迭代次数是宽度,而不是字长。您可以使宽度为 O(log n),因为只要不超过宽度,您就可以在每次迭代时将移位量加倍。

这是一个 DeBruijn 查找,用于计算 32 位值的尾随零位。

static const int MultiplyDeBruijnBitPosition[32] = {
  0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 
  31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};
r = MultiplyDeBruijnBitPosition[((uint32_t)((v & -v) * 0x077CB531U)) >> 27];

我注意到在您的两个示例中,val1 只设置了一个位。如果是这种情况,您可以使用 DeBruijn 技巧来查找 MSB。

【讨论】:

  • 这段代码给我的代码带来了非常不同的结果——为简单起见,我只是用从我的找到while循环的2行MSB替换了对count_leading_zero_bits的每次调用。我是否正确地假设valuevar1 相关,而other 与我的代码中的var2 相关?另外,您能否说明这是否找到了包含在设置位中的零区域?
  • 您的 msb 循环是否具有破坏性?在您的原始帖子中,它会改变输入值。我在循环的每次迭代中添加了预期值。
  • val1 只有一个位集是一种人为行为,以使找到零位区域的可能性更大。
  • +1 表示努力,并且让我努力思考以找到解决方案。谢谢。
  • 我一直很喜欢位操作。您想出什么作为最终解决方案?
【解决方案3】:

这是我的新算法和改进算法:

int test_fit_within_left_of_msb(  unsigned width,
                                  unsigned val1,
                                  unsigned val2 )
{
    int offset = 32;
    int msb = 0;
    unsigned mask;
    unsigned b;

    msb = 32 - __builtin_clz(val1); /* GCC builtin to count Leading Zeros */

    while(offset - width > msb)
    {
        mask = (((unsigned)1 << width) - 1) << (offset - width);
        b = val2 & mask;

        if (!b)
            return 32 - offset;

        offset = __builtin_ctz(b); /* GCC builtin to Count Trailing Zeros */
    }

    return -1;
}

与我最初的实现相比,这段代码有很多改进。主要是通过简单地计算尾随零位来移除内部while 循环。其次,我还使算法使用了使用自然位位置值的偏移量,因此删除了我原来使用的一些加法和减法运算,直到成功返回语句。您可以选择从 32 中减去偏移量。

代码中的重点是算法——我意识到存在可移植性问题以及对类型和大小的假设。将页面返回到输出,其中在 10 次迭代中执行的位置 12 处可以找到宽度 8,新的 alogirthm 在循环的 2 次迭代中执行相同的操作。

为了方便起见,我在这里使用了 GCC 内置函数,drawonward 提供的 MultiplyDeBruijnBitPosition 代码(来自:http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightMultLookup)可用于替换 __builtin_ctz,而 __bultin_clz 可以替换为来自同一页。

不过,这里有一个问题,是我用来测试的数据(具有稀疏设置的位)使这个算法性能更好,这可能看起来不太好具有更密集设置位的整数。(不正确 - 通过计算尾随零可以避免这种坏情况)。

【讨论】:

    【解决方案4】:

    在实现了我之前的答案但为 MSB 的右侧工作后,我看到除了非常小的差异之外,左侧和右侧版本完全相同。这导致实现算法根本不需要使用某个先验值的 MSB。

    所以虽然这个答案不符合问题的规格,但它是正确的答案,因为规格不正确。

    #include<stdint.h>
    
    /* returns bit position within a 32bit integer, where
       a region of contiguous zero bits can be found whose
       count is equal to or greater than width. it returns
       -1 on failure.
    */
    
    int binary_width_fit( unsigned width, uint32_t val )
    {
        int offset = 32;
        uint32_t mask;
        uint32_t b;
    
        while(offset >= width)
        {
            mask = (((uint32_t)1 << width) - 1) << (offset - width);
            b = val & mask;
            if (!b)
                return offset;
            offset = __builtin_ctz(b); /* GCC builtin to Count Trailing Zeros */
        }
        return -1;
    }
    

    【讨论】:

    • 这对于小宽度来说不是很好。改进的版本使用 GCC 内置在应用掩码之前计算领先的,以进一步减少循环的迭代次数。
    • 我希望您在原始问题中发表了该评论。
    • @nategoose:您的意思是说没有真正要求在某些 MSB 的左侧而不是整个位范围内工作的评论吗?如果是这样,我可以将其添加为编辑,如果您提出更好的解决方案,如果确实更好,我将很乐意接受它......当我问这个问题时,我并没有弄清楚一切,它似乎花了很长时间才到这个阶段,我意识到将过程限制在 MSB 的一侧(等)。
    • 代码中的注释向我解释了你想要什么,即使你交换了你正在工作的单词的结尾。顺便说一句,如果有更多位有用的话,您可以使用int offset = 8*sizeof(unsigned long); 并使其利用更大的本机字长并且可移植。
    【解决方案5】:

    1(快速)方法是对每个 8 位字节使用预先计算的 LOOKUP TABLES (LUT):

    PosOfFirst1、PosOfLast1、PosOfFirst0、PosOfLast0 -- 所有 256 字节的数组

    使用以下方法预先计算表格:(soz 代表糟糕的帕斯卡利什伪代码)

    PosOfLast1:

    FOR EACH ByteVal (0..255):
    
    if byteVal>127 return 8
    elseif byteVal>63 return 7
    ...
    elseif byteVal>0 return 1
    else return 0
    
    PosOfFirst1:
    
    c:=0;
    while c<8 do
    begin
    bv = byteVal and 1; 
    if bv=1 then return c
    else byteval shr 1;     
    inc (c);
    end;
    

    我对这些算法使用简单的汇编程序。 PosOfFirst0 和 PosOfLast0 LUT 也可以使用这 2 个表预先计算 - TRAILING & LEADING 0(或 1)计数也可以。预先计算这些表的“减 1”版本也很有用....

    然后您可以使用(对于 8 位字节) var InputByte: 字节; FirstBit:=PosOfFirst1[InputByte] // v.fast

    对于较大的尺寸(0、16、24、32 +++++),请使用检查每个组成的 8 位字节的过程和循环。可能需要对 LUT 进行内存访问,但这种方法仍然更快:

    a) 无需过程调用即可轻松使用。 b) 扫描一个 32 位数字需要 1 次移位 & 比较到 0 每个字节需要 1 次查找(如果找到非零字节)而不是 n (0..32) 次移位,ands 和比较...... c) 如果编程良好将在找到第一个/最后一个 1 后停止

    LUT 原理适用于“人口计数”+ 其他位操作。例行公事...

    干杯,PrivateSi

    越快越好?!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-03-02
      • 1970-01-01
      • 1970-01-01
      • 2014-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多