按位间隔算法

时间:2010-04-12 07:03:34

标签: algorithm language-agnostic bit-manipulation

我最近在D新闻组上读到interesting thread,基本上要求了,

  

给定两个(带符号)整数 a ∈[ a min a max ], b ∈[ b min b max ],什么是 a |的最紧密间隔 B'/ em>的

我认为如果区间算术可以应用于一般的位运算符(假设无限位)。按位NOT和移位是微不足道的,因为它们只对应于-1 - x 和2 n x 。但由于按位和算术属性的混合,按位AND / OR非常棘手。

是否有多项式时间算法来计算按位-AND / OR的间隔?


注意:

  • 假设所有按位运算都以线性时间(位数)运行,并且测试/设置位是恒定时间。
  • 蛮力算法以指数时间运行。
  • 因为~(a | b) = ~a & ~b,解决按位AND和-NOT问题意味着按位OR完成。
  • 虽然该帖子的内容建议min { a | b } = max( a min b min ),它不是最严格的约束。只需考虑[2, 3] | [8, 9] = [10, 11]。)
  • 实际上处理无符号算术就足够了,因为我们可以将一个带符号的区间分成负的和非负的子集,并且使用de Morgan定律和交换性只需要非负区间的按位AND,-OR和-AND-NOT情况需要待解决。

3 个答案:

答案 0 :(得分:4)

哎呀。你确定这些必须是整数吗?这只会引发一堆烦人的情况,你必须翻转它们。

如果我们将自己局限于无符号整数,我们可以沿着这些位找到最大值。高于max(a_max , b_max)中设置的最高位的位置显然不能打开。假设不失一般性,a_max> b_max。将所有位保持在a_max中,直到我们达到b_max中的最高位。然后保留两者的所有位,直到我们在至少一侧具有灵活性(即,我们可以选择允许翻转该位的数量)。如果另一方不能将该位设置为1,则将其设置为1并继续运行(设置一个更高的位总是胜过设置所有低位)。否则,将你的答案设置为(那个位 - 1),这将在所有其余位中放置1,你就完成了。

现在我们做同样的事情是最小的,除了我们避免在每个机会设置位,但是如果一方必须设置一个,则抓住每个机会配对。

如果我们可以在O(1)时间内对整数进行数学运算,那么这是位数的O(n)。否则,它是O(n ^ 2)。

以下是[2,3] |的例子[8,9]

101 -> 1xx works
10 to 11 -> x1x always set ; 11x doesn't fit in a so we're not done
11 can set last bit -> 111
100 -> 1xx must be set
10 to 11 -> x1x must be set ; 11x doesn't fit so we're not done
10 has xx0 as does 100 -> xx0 works -> 110

编辑:添加符号位不会改变算法的顺序,但它确实需要更烦人的簿记。如果你不能摆脱一个符号位,那么你翻转最小和最大策略(即设置与不设置位)。如果它是可选的,min是你设置它然后尝试保持其他一切未设置;最大的是你取消设置然后尝试保留其他所有设置。

第二次编辑:这是另一个例子;两个范围都是[1001,1100]:

Must keep first bit -> 1xxx
Can set 2nd bit -> x1xx
Other _could have but did not need to_ set 2nd bit -> set 2nd bit -1 -> xx11
-> 1111 is max
Must keep first bit -> 1xxx
Neither needs 2nd bit -> x0xx
Neither needs 3rd bit -> xx0x
Both need 4th bit -> xxx1
-> 1001 is min

答案 1 :(得分:4)

对于非负整数的区间[a min max ],我们可以计算按位最小值a 0 ,其中位只要在间隔内可能,就会独立设置为0。类似地,我们可以计算按位最大值a 1 ,其中在区间中尽可能将位设置为1。对于[b min ,b max ],我们做同样的事情并得到b 0 和b 1 。然后结果区间是[a 0 | b 0 1 | B'的子> 1 ]。

从[a min max ]中检查a可以采用的值是很容易的。对于位n,如果m> m> = n的所有位m在 min max 中一致,则强制该值,否则它可以是0或1。 / p>

这可以在O(n)中完成。

签名的案例留给读者练习。第一步是提供按位运算符对负整数的含义。

编辑:不幸的是这是错误的:考虑[a min max ] = [b min < / sub>,b max ] = [1,2]。 然后a | b可以是1,2或3,但按位最小值是0。

答案 2 :(得分:0)

我刚刚为无符号整数做了这个。边界并不完美,但非常紧张。对于100,000个随机输入,与采样计算的实际间隔相比,少于200个偏差超过0.1%。而且它总是保守的(包含真实界限)。

关键是使用FindLeadingOnes函数作为构建块。这允许表达有效位彼此匹配的情况。这很重要,因为整数的间隔具有在上边界和下边界中匹配的前导位的属性也匹配间隔中的所有值。因此,考虑到前导匹配位允许计算输出间隔端点的最高有效位。

此外,对于在一个输入间隔内保持不变但在另一个输入间隔内变化的中间位,必须将运算符应用于上限和下限以获得这些位的间隔。这可以在iXOr中看到。

最后,AND的上限是min(left.upper,right.upper),因为其中一个中的零不能在输出中为1。与OR的下限相似。

(不要注意ToInt和ToFloat的东西。我实际上是在固定点数上做这个。如果你只是让这些函数成为无操作,那么它就能正常工作。

interval iAnd(const interval lv, const interval rv)
{
    unsigned int ll = ToInt(lv.lower), lu = ToInt(lv.upper), rl = ToInt(rv.lower), ru = ToInt(rv.upper);

    unsigned int lvx = FindLeadingOnes(~(ll ^ lu));
    unsigned int rvx = FindLeadingOnes(~(rl ^ ru));
    unsigned int constmask = (lvx | rvx);

    return interval(ToFloat((ll & rl) & constmask), ToFloat(std::min(lu, ru)));
}

和OR:

interval iOr(const interval lv, const interval rv)
{
    unsigned int ll = ToInt(lv.lower), lu = ToInt(lv.upper), rl = ToInt(rv.lower), ru = ToInt(rv.upper);

    unsigned int lvx = FindLeadingOnes(ll & lu) | FindLeadingOnes(~ll & ~lu);
    unsigned int rvx = FindLeadingOnes(rl & ru) | FindLeadingOnes(~rl & ~ru);
    unsigned int constmask = (lvx | rvx);

    return interval(ToFloat(std::max(ll, rl)), ToFloat((lu | ru) | ~constmask));
}

和XOR:

interval iXOr(const interval lv, const interval rv)
{
    unsigned int ll = ToInt(lv.lower), lu = ToInt(lv.upper), rl = ToInt(rv.lower), ru = ToInt(rv.upper);

    unsigned int lvx = FindLeadingOnes(ll & lu) | FindLeadingOnes(~ll & ~lu);
    unsigned int rvx = FindLeadingOnes(rl & ru) | FindLeadingOnes(~rl & ~ru);
    unsigned int constmask = (lvx | rvx);
    interval iout(ToFloat((ll ^ rl) & constmask), ToFloat((lu ^ ru) & constmask)); // Not sure which is larger; interval constructor sorts them.

    iout.extend(ToFloat(ToInt(iout.upper) | ~constmask)); // Now that the upper is known, extend it upward for the lsbs.

    return iout;
}

这是我的FindLeadingOnes(对于我的16位定点。但是你可以使用更多位:

unsigned int FindLeadingOnes(unsigned int v)
{
    for(unsigned int mask = 0x8000; mask != 0xffff; mask |= mask >> 1u) {
        if ((mask & v) != mask)
            return (mask << 1u) & 0xffff;
    }

    return 0xffff;
}