【问题标题】:Trailing Zeros - C尾随零 - C
【发布时间】:2017-12-29 10:30:53
【问题描述】:

我需要一个程序来返回数字的二进制表示中尾随零的数量。我在网上找到了一个用 C 编写的函数,但我不明白它是如何工作的

这是函数:

unsigned tzr(unsigned x) 
{
    unsigned n; /* number of bits */

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

    n += (x & 1) ^ 1; // anyway what does this do ? 

    return n;
}

现在我真的很想了解它是如何工作的,但我不明白。 我真的需要有人能给我解释一下,我觉得这段代码很复杂。

关于那些十六进制常量,这些是它们的值:

0x0000FFFF = 65535
0x000000FF = 255
0x0000000F = 15
0x00000003 = 3

现在,为什么程序使用这些值并与数字进行按位与?

那么我知道,如果你想处理大数字,你必须
使用while 而不是第一个if 语句,如下所示:

while (!(x & 0x0000FFFF)) { bits += 16; x >>= 16; } // why should I need this ?

但我不知道为什么!在这种情况下使用while 而不是if 有什么区别?

【问题讨论】:

  • 看看那些十六进制数的二进制表示,你就应该明白了。十六进制 F 是二进制 1111
  • 尝试各种随机输入。甚至可能在每一步之后将输出语句添加到您的程序中
  • “我在街上发现了一些东西,请解释一下它是什么”。这不是堆栈溢出的工作原理。
  • @Olaf 我真的很抱歉。我不知道在哪里可以找到任何帮助。我发布这个是因为我看到很多关于人们发布代码要求某人帮助他们的帖子,所以我想我也可以这样做
  • n += (x & 1) ^ 1 检查 LSB,这是移位或不移位的结果。在此位之后,不需要移位,这就是不使用 if 语句的原因。

标签: c bitwise-operators trailing


【解决方案1】:

十六进制常量与值进行“与”运算,以检查最后 [number] 个数字是否为零。0x0000FFFF 是一个包含 16 个二进制的数字。如果与0x0000FFFF 进行“与”运算的值等于 0,则您知道最后 16 位数字为零(ifs 检查该语句的反面)。更进一步0x000000FF 是一个有 8 个二进制数的数字。下一个检查是最后 8 位,接下来是 4 位,最后一个是 2 位,因为0x00000003 是二进制的11。在检查之后,数字被移动以检查其他数字是否也为零。通过这种方式,我们可以检查任意数量的尾随零,因为这些值是 2 的幂,并且添加它们的工作方式与使用二进制完全一样。

最后一个语句在所有先前的移位完成后检查最后一个数字 - 与 1 并用 XOR(^) 检查它是 0 还是 1。

这个程序检查 32 位的数字。您可以将第一个 if 更改为 while 以检查更大,例如64 位,数字。另一种方法是检查0xFFFFFFFF,然后一次移动 32 位。

【讨论】:

    【解决方案2】:

    n += (x & 1) ^ 1 行检查 x 的当前状态的最低有效位 (LSB)。如果 LSB 为 1,则 (x & 1) 得到 1,然后将其与 1 进行异或(插入符号“^”表示对两个值进行异或)得到 0(1 ^ 1 == 0)。当 x 在 LSB 中有 0 并与 1 进行异或运算时,得到 1 (0 ^ 1 == 1)。

    【讨论】:

      【解决方案3】:

      !(x&0x0000FFFF) 仅在x 的最后 16 位全为 0 时为真。 & 是按位与,0x0000FFFFF 是以 16 个 1 结尾的数字。 因此,如果所有 16 个尾随位均为 0,则 and 的结果为 0(因此 FALSE 和 1 反转真值),因为如果最后 16 个中至少有一个 1,则 and 与常量中的相应 1将是 1。那么 and 不是 0(所以 TRUE 和 ! 反转真值)。

      所以代码说:如果最后 16 位是 1,则将 16 加到 n 并丢弃最后 16 位(这就是 x >>= 16 所做的)。
      下一行以类似的方式说: 如果 (可能缩短 x) 的最后 8 位为 0 ,则将 8 加到 n 并丢弃最右边的 8 位,以此类推 4 位和 2 位

      如果最右边的位 (x&1) 为 0,则最后一行加 1,否则为 0 (1^1 = 0)。

      假设如果最右边的 15 位为 0,则第一个 if 将为 false ,n 保持为 0。
      第二个是真的,因为我们有超过 8 个。Tne new x 将有 7 个 0 位, n=8。
      第三个也是正确的(我们还有 4 个或更多),所以新的 x 在移位后有 3 个 0 位并且 n=12。
      第四个也为真(2 个或多个 0),因此新 x 有 1 个 0 位且 n=14。
      最后一条语句加 1,所以得到 n=15。

      因为我们使用 2 的递减幂,所以我们不需要循环。我们通过这种方式获得所有可能的 n 值(除了 32,对于输入 x=0,一个完全正确的函数可能应该检查该值并提前中止。

      【讨论】:

        【解决方案4】:

        n += (x & 1) ^ 1; // anyway what does this do ?

        这会检查最右边的位。要么已设置,要么未设置。

        如果已设置,则不会再将 0 添加到尾随零的运行总数中,因此 n+=0。

        如果未设置,则还有一个 0 要添加到尾随零的运行总数中,因此 n+=1。

        另外,您的示例无法编译,它缺少两个 ;如下:

        unsigned tzr(unsigned x)
        {
            unsigned n; /* number of bits */
        
            n = 0;
            if (!(x & 0x0000FFFF)) { n += 16; x >>= 16; }
            if (!(x & 0x000000FF)) { n += 8; x >>= 8; }
            if (!(x & 0x0000000F)) { n += 4; x >>= 4 } // won't compile due to missing ;
            if (!(x & 0x00000003)) { n += 2; x >>= 2 } // won't compile due to missing ;
        
            n += (x & 1) ^ 1; // anyway what does this do ?
        
            return n;
        }
        

        此外,您可以随时尝试打印数据,例如,每个 2 的幂都有多个尾随零,但只有奇数数量的尾随零会从n += (x & 1) ^ 1;...中增加一个额外的 1...

        cout << tzr(9) << endl << endl; // 1001 (not a power of two )
        cout << tzr(8) << endl << endl; // 1000 (8>>2 & 1)^1==1
        cout << tzr(4) << endl << endl; // 0100 (4>>2 & 1)^1==0
        cout << tzr(2) << endl << endl; // 0010 (   2 & 1)^1==1
        cout << tzr(1) << endl << endl; // 0001 (   1 & 1)^1==0
        

        tzr(9) == 0 ==> 0 + (9 & 1) ^ 1 == 0 + 0

        tzr(8) == 3 ==> 2 + (8>>2 & 1) ^ 1 == 2 + 1

        tzr(4) == 2 ==> 2 + (4>>2 & 1) ^ 1 == 2 + 0

        tzr(2) == 1 ==> 0 + (2 & 1) ^ 1 == 0 + 1

        tzr(1) == 0 ==> 0 + (1 & 1) ^ 1 == 0 + 0

        程序以退出代码结束:0

        【讨论】:

          【解决方案5】:

          您说,“我需要一个程序,它可以返回数字的二进制表示中尾随零的数量。”但它必须是你找到的程序吗?这是另一种解决方案,它仅在一行代码中实现 tzr(),

          #include <stdio.h>
          #include <stdlib.h>
          
          int tzr(int n) { /* --- every time n is even, add 1 and check n/2 --- */
            return ( (n/2)*2 == n? 1+tzr(n/2) : 0 ); }
          
          int main ( int argc, char *argv[] ) { /* --- test driver --- */
            int n = (argc>1? atoi(argv[1]) : 1000);
            printf("tzr(%d) = %d\n", n,tzr(n)); }
          

          这样更容易理解吗?

          (PS 你可以使用位掩码和移位来代替我的除法和乘法。这可能更有效,但我认为我的方式可能更易于阅读。)

          【讨论】:

          • 我认为用位运算符或反之来描述固有的算术运算是增加复杂性的原因,而不是使用位运算符本身。使用有符号算术和零时潜在的堆栈溢出有点让人不舒服,关于为什么一个计数版本等效会失败的解释有点微妙,但这可能是吹毛求疵。
          猜你喜欢
          • 1970-01-01
          • 2020-09-11
          • 1970-01-01
          • 2011-05-30
          • 2020-08-16
          • 1970-01-01
          • 2012-11-21
          • 2017-09-19
          • 1970-01-01
          相关资源
          最近更新 更多