尾随零的数量

时间:2017-07-20 17:52:13

标签: c++ c while-loop bitwise-operators

我编写了一个函数 trailing_zeroes(int n),它返回数字二进制表示中的尾随零的数量。

示例:二进制文件中的4100,因此本例中的函数返回2

unsigned trailing_zeroes(int n) {
    unsigned bits;

    bits = 0;
    while (n >= 0 && !(n & 01)) {
        ++bits;
        if (n != 0)
            n >>= 1;
        else
            break;
    }
    return bits;
}

if语句的原因是因为如果n等于0,则会有一个循环。

我觉得这个代码写的很丑陋;有更好的方法吗?

我想避免break内的while语句,因为很多人告诉我在while/for内使用该语句有时候可能是"非正式的" 。我想要改写这样的功能,但我不认为这是最好的方法:

unsigned bits;
if (n == 0)
    return bits = 1;

bits = 0;
while (!(n & 01)) {
    ++bits;
    n >>= 1;
}

2 个答案:

答案 0 :(得分:4)

您的功能不正确:0仍有无限循环。测试应该是:

while (n > 0 && !(n & 1))

请注意,您无法使用此方法处理负数,因此您的函数可能应采用unsigned数字参数,或者您可以将参数转换为unsigned

您的函数应该是特殊情况0并使用更简单的循环:

unsigned trailing_zeroes(int n) {
    unsigned bits = 0, x = n;

    if (x) {
        while ((x & 1) == 0) {
            ++bits;
            x >>= 1;
        }
    }
    return bits;
}

以上功能非常简单易懂。如果结果很小,它会很快。 0返回的值与函数中的0一样,这是值得怀疑的,因为0实际上有unsigned类型中的值位的尾随零。

有一个更有效的方法,步骤数量恒定:

unsigned trailing_zeroes(int n) {
    unsigned bits = 0, x = n;

    if (x) {
        /* assuming `x` has 32 bits: lets count the low order 0 bits in batches */
        /* mask the 16 low order bits, add 16 and shift them out if they are all 0 */
        if (!(x & 0x0000FFFF)) { bits += 16; x >>= 16; }
        /* mask the 8 low order bits, add 8 and shift them out if they are all 0 */
        if (!(x & 0x000000FF)) { bits +=  8; x >>=  8; }
        /* mask the 4 low order bits, add 4 and shift them out if they are all 0 */
        if (!(x & 0x0000000F)) { bits +=  4; x >>=  4; }
        /* mask the 2 low order bits, add 2 and shift them out if they are all 0 */
        if (!(x & 0x00000003)) { bits +=  2; x >>=  2; }
        /* mask the low order bit and add 1 if it is 0 */
        bits += (x & 1) ^ 1;
    }
    return bits;
}

请注意,我们可以通过将第一步更改为

来处理任何更大的int尺寸
while (!(x & 0x0000FFFF)) { bits += 16; x >>= 16; }

某些编译器具有内置函数__builtin_ctz(),可使用非常高效的汇编代码计算尾随零的数量。它不是C标准功能,但是以降低可移植性为代价,如果可用,您可能希望使用它。查看编译器的文档。

以下是GCC docuemntation的摘要:

  

内置函数:int __builtin_ctz (unsigned int x)

     

从最低有效位开始,返回x中的尾随0位数。如果x0,则结果未定义。

答案 1 :(得分:3)

正如已经提到的,有一个内置可以做到这一点,因为它可能使用硬件,它可能非常快。但是,doc for GCC确实表示如果输入为0,结果是未定义的。因为这是一个扩展,它可能不适用于您的编译器。

否则,每当有人说'但操纵'时,或者'比特计算',您需要找到"Hacker's Delight"的副本。一本书很好,我买了两个版本。大约有4页(第1版)致力于此,' ntz' (尾随零的数量)。如果你已经有了一个&nbspz  (前导零的数量)或者“前导零”的数量。功能,然后你可以直接获得ntz。否则本书给出several implementations,有些使用popcnt,有一个有循环,其他使用二进制搜索。

例如,

int ntz3(unsigned x) {
   int n;

   if (x == 0) return(32);
   n = 1;
   if ((x & 0x0000FFFF) == 0) {n = n +16; x = x >>16;}
   if ((x & 0x000000FF) == 0) {n = n + 8; x = x >> 8;}
   if ((x & 0x0000000F) == 0) {n = n + 4; x = x >> 4;}
   if ((x & 0x00000003) == 0) {n = n + 2; x = x >> 2;}
   return n - (x & 1);
}