【问题标题】:Efficient conditional for increasing size in bits增加位大小的有效条件
【发布时间】:2011-05-23 15:24:16
【问题描述】:

假设我有一个递增的无符号整数序列C[i]。随着它们的增加,它们可能会占用越来越多的位。我正在寻找一个有效的条件,纯粹基于序列C[i]C[i+1] 的两个连续元素(过去和未来的元素是不可观察的),每次数字都会准确或大约一次评估为真所需的位数增加。

一个明显(但缓慢)的条件选择是:

if (ceil(log(C[i+1])) > ceil(log(C[i]))) ...

以及任何使用特殊 cpu 操作码计算前导零位数的东西(好得多,但仍然不是很好)。

我怀疑可能有一个很好的解决方案,涉及仅使用按位或和按位以及值C[i+1]C[i] 的表达式。有什么想法吗?

【问题讨论】:

标签: c algorithm bit-manipulation


【解决方案1】:

当值大约溢出 2 的幂时,位数会增加。那么,一个简单的测试是,该值是否等于 2 的幂减去 1?这可以通过询问来完成:

if  ((C[i] & (C[i]+1))==0) ...

【讨论】:

  • 无意让他们上下班。如果 C[i] 即将溢出,而 C[i+1] 更大,则 C[i+1] 需要更多位。您无需查看 C[i+1] 即可做出决定。
【解决方案2】:

Keith Randall 的解决方案很好,但是您可以使用以下代码节省一条异或指令,该代码在 O(w + n) 指令中处理整个序列,其中 w 是一个字的位数,n 是序列中的元素数量。如果序列很长,大多数迭代将只涉及一次比较,避免使用一条异或指令。

这是通过跟踪已达到的两个的最高功率来实现的,如下所示:

t = 1; // original setting

if (c[i + 1] >= t) {
  do {
    t <<= 1;
  } while (c[i + 1] >= t); // watch for overflow
  ... // conditional code here
}

【讨论】:

  • 我注意到您的解决方案与我的略有不同。我还注意到,R. 叮了我的(恕我直言不合理),但没有叮你的。
【解决方案3】:

假设你的两个数字是 x 和 y。如果它们具有相同的高位,则 x^y 小于 x 和 y。否则高于两者之一。

所以

v = x^y
if (v > x || v > y) { ...one more bit... }

【讨论】:

  • 太棒了!既然他已经知道C[i+1] > C[i],那么if ((C[i+1] ^ C[i]) &gt; C[i]) { /* number of bits has changed */ }
  • 是的,您可以通过这种方式摆脱 1 个比较。
  • +1 并检查。这正是我一直在寻找的,但我还没有时间思考逻辑。它比我在问题中写的幼稚解决方案基本相同的所有其他建议更有效。
【解决方案4】:

BSR - 位扫描反向 (386+)

    Usage:  BSR     dest,src
    Modifies flags: ZF

    Scans source operand for first bit set.  Sets ZF if a bit is found
    set and loads the destination with an index to first set bit.  Clears
    ZF is no bits are found set.  BSF scans forward across bit pattern
    (0-n) while BSR scans in reverse (n-0).

                             Clocks                 Size
    Operands         808x  286   386   486          Bytes

    reg,reg           -     -   10+3n  6-103          3
    reg,mem           -     -   10+3n  7-104         3-7
    reg32,reg32       -     -   10+3n  6-103         3-7
    reg32,mem32       -     -   10+3n  7-104         3-7

您需要其中两个(在 C[i] 和 C[i]+1 上)和一个比较。

【讨论】:

    【解决方案5】:

    鉴于(我相信这来自Hacker's Delight):

    int hibit(unsigned int n) {
        n |= (n >>  1);
        n |= (n >>  2);
        n |= (n >>  4);
        n |= (n >>  8);
        n |= (n >> 16);
        return n - (n >> 1);
    }
    

    你的条件只是hibit(C[i]) != hibit(C[i+1])

    【讨论】:

    • 我听说这比在某些 cpu 上使用专用操作码要快,但我一直在寻找更快的解决方案,它可以跳过整个比特计数步骤,我接受了。
    【解决方案6】:

    我认为你只需要clz(C[i+1]) &lt; clz(C[i]) 其中clz 是一个返回前导零数量的函数(“计算前导零”)。一些 CPU 系列有这方面的指令(可能作为内在指令提供)。如果没有,那么您必须自己动手(通常只需要一些说明) - 请参阅 Hacker's Delight

    【讨论】:

    • 我同意这可以工作(我只是用ceillog 更抽象地写了它),但我相信(特别是如果您将要求从位大小增加到仅一次时放宽-per-increas) 可以更有效地完成。
    • 你能得到比一条指令高效多少? (好的,好的,三个指令。)
    • 如何在没有原生指令的情况下仅用几条指令实现clz?我发现最好的是在我的解决方案中,尽管 lsb 可以在使用乘法的几条指令中找到,如另一个 SO 问题中所述。
    • 好吧,我删除了我的计数前导零解决方案(只留下我的 2 次幂阈值解决方案),因为 Keith 的解决方案比计算短序列的前导零更好。不过,我很好奇你是否知道一种比标准 O(lg w) 解决方案更好的方法来计算 msb 的索引,标准 O(lg w) 解决方案仍然存在于 aix 的解决方案中。
    • 仅仅因为clz 是一条指令并不意味着它很快。我见过更复杂的 asm,在许多 x86 cpu 上速度更快......
    【解决方案7】:

    当值即将溢出 2 的幂时,位数会增加。 然后是一个简单的测试:

     while (C[i] >= (1<<number_of_bits)) then number_of_bits++;
    

    如果你想要更快:

    int number_of_bits = 1;
    int  two_to_number_of_bits = 1<<number_of_bits ;
    
    
    ... your code ....
    
    while ( C[i]>=two_to_number_of_bits )
       { number_of_bits++; 
         two_to_number_of_bits = 1<<number_of_bits ;
       }
    

    【讨论】:

    • 我说的纯粹是基于C[i]C[i+1],而不是一个新的魔法计数器变量。
    • 你也说“快”。有什么反对意见?
    • 没有地方可以存储number_of_bits。我根据需要准确地提出了这个问题。你的答案是一个非常不同的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多