【问题标题】:Bitwise Interval Arithmetic位区间算术
【发布时间】:2010-04-12 07:03:34
【问题描述】:

我最近在 D 新闻组上看到了一个interesting thread,它基本上是在问,

给定两个(有符号)整数a ∈ [amin, amax sub>], b ∈ [bmin, bmax], 什么是a的最紧区间| b?

我认为区间算术是否可以应用于一般的位运算符(假设无限位)。按位非和移位是微不足道的,因为它们只对应于 -1 - x 和 2nx。但由于位和算术属性的混合,位与/或要复杂得多。

是否有多项式时间算法来计算按位与/或的间隔?


注意:

  • 假设所有按位运算都以线性时间(位数)运行,并且测试/设置位是恒定时间。
  • 蛮力算法在指数时间内运行。
  • 因为~(a | b) = ~a & ~b,解决按位与和-非问题意味着按位或已完成。
  • 虽然该线程的内容建议 min{a | b} = max(amin, bmin),不是最紧密的界限。只需考虑[2, 3] | [8, 9] = [10, 11]。)
  • 实际上处理无符号算术就足够了,因为我们可以将有符号区间拆分为负子集和非负子集,并且使用德摩根定律和交换性,仅非负区间上的按位与、-OR 和 -AND-NOT 情况需要得到解决。

【问题讨论】:

  • 我认为sign 位肯定会很痛苦......
  • @Matt 因为我假设无限位,所以任何负数都应该以无限多个 1 为前缀。但我也接受未签名的案例。
  • 我不相信您计算按位或和 -XOR 的算法在所有情况下都会产生最紧密的间隔。
  • @algorithmist:好的,你是对的,-XOR 算法是错误的,因为ab 出现了两次。但是对于-OR,由于按位-NOT 是严格单调的,我看不出它不会给出最紧密间隔的原因。
  • 我只是想问,如果他们没有签名,他们就不能取 {a-min|b-min, a-max|b-max} 的最小值吗?跨度>

标签: algorithm language-agnostic bit-manipulation


【解决方案1】:

啊。你确定这些必须是有符号整数吗?这只会带来一堆令人讨厌的情况,您必须翻转一些东西。

如果我们将自己限制为无符号整数,我们可以向下查找最大值。任何高于max(a_max , b_max) 中设置的最高位的位显然都不能打开。不失一般性地假设 a_max > b_max。保留 a_max 中的所有位,直到我们达到 b_max 中的最高位。然后保留两者的所有位,直到我们至少在一侧具有灵活性(即,我们可以在允许的范围内选择一个可以翻转该位的数字)。如果另一方无法将该位设置为 1,请将其设置为 1 并继续(设置一个高位总是优于设置所有低位)。否则,将您的答案设置为 (that bit - 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

编辑:添加符号位不会改变算法的顺序,但它确实需要更烦人的簿记。如果您无法摆脱符号位,那么您可以翻转最小和最大策略(即设置与不设置位)。如果它是可选的,则最小值是您设置它然后尝试保持其他所有内容未设置的时间;最大值是当您取消设置然后尝试保持其他所有设置时。

第二次编辑:这是另一个例子;两个范围都是 [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

【讨论】:

  • 签名算术不是必需的。实际上,我们可以将区间拆分为负数和非负数部分,并使用按位-NOT 转换为非负数区间。但接下来我们需要分别处理 OR、AND 和 AND-NOT。
  • 有趣的是,由于OR 逻辑,我们可以有一个更大的最大值。 AND 逻辑显然会产生反转效果。 NOT 是一个双射,所以如果我没记错的话,它应该会产生一个相似长度的范围。
【解决方案2】:

对于非负整数的区间 [amin, amax],我们可以计算按位最小值 a0,其中位只要在间隔内可能,就独立设置为 0。类似地,我们可以计算按位最大值 a1,其中位在区间内尽可能设置为 1。对于 [bmin, bmax],我们做同样的事情并得到 b0 和 b1。那么结果区间为 [a0 | b0, a1 | b1].

很容易从 [amin, amax] 中检查 a 可以取哪些值。对于第 n 位,如果 m >= n 的所有位 m 在 amin 和 amax 一致,则强制取值,否则为 0 或 1。

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

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

编辑:不幸的是,这是错误的:考虑 [amin, amax] = [bmin, bmax] = [1,2]。 那么a | b 可以是 1、2 或 3,但按位最小值为 0。

【讨论】:

  • 抱歉,“按位最小值”是什么意思? a_min & a_max?
  • 我明白了。但这似乎与雷克斯的回答没有什么不同。
  • Rex 写为 O(n²),他没有单独考虑区间。结果当然应该是一样的。
【解决方案3】:

我只是为无符号整数做了这个。界限并不完美,但非常严格。对于 100,000 个随机输入,少于 200 个与通过抽样计算的实际间隔相差 0.1% 以上。而且它总是保守的(包含真实的界限)。

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

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

最后,AND 的上限是 min(left.upper,right.upper),因为其中一个为 0 的位不能在输出中为 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)));
}

和或:

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));
}

和异或:

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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-05-21
    • 1970-01-01
    • 2017-01-14
    • 2011-07-25
    • 1970-01-01
    • 2015-05-16
    • 2012-03-21
    • 2011-04-11
    相关资源
    最近更新 更多