【问题标题】:Bitwise operations equivalent of greater than operator相当于大于运算符的按位运算
【发布时间】:2012-04-23 04:57:43
【问题描述】:

我正在研究一个函数,该函数基本上可以查看两个整数中的哪一个更大。传递的参数是2 32 位整数。诀窍是唯一允许的运算符是! ~ | & << >> ^(没有强制转换,除了有符号的int、*、/、-等其他数据类型)。

到目前为止,我的想法是将^ 两个二进制文件放在一起,以查看它们不共享的1 值的所有位置。然后我想要做的是取那个值并隔离最左边的1。然后看看其中哪一个有这个价值。那么该值将更大。 (假设我们使用 8 位整数而不是 32 位)。 如果传递的两个值是0101101101101001 我在他们身上使用了^ 来获得00100010。 然后我想使它成为00100000 换句话说01xxxxxx -> 01000000 然后&它与第一个数字 !! 结果并返回。 如果是1,那么第一个#更大。

对如何01xxxxxx -> 01000000 或其他任何帮助有什么想法吗?

忘了注意:没有 ifs、whiles、fors 等...

【问题讨论】:

  • “然后我想将其设为 00100000,即 01xxxxxx-> 01000000”是什么意思?如何从 00100000 得到 01xxxxxx?
  • 哦,来吧...你能用什么?可以用三元条件吗?
  • 不,不是三元,它教我们如何从本质上创建构建块
  • 我的意思是你从 01xxxxxx 得到 01000000 其中 x 可能是 0 或 1
  • 在有符号整数中按位处理是可疑的。 C 标准没有为负数指定任何特定的二进制表示。现实世界的表示几乎总是二进制补码,但一些编译器(例如 GCC)利用不确定性进行优化。由于这个标志显然很重要,这至少是一个潜在的问题。

标签: c binary bit-manipulation


【解决方案1】:

这是一个无循环版本,它在 O(lg b) 操作中比较无符号整数,其中 b 是机器的字长。请注意,OP 声明除signed int 之外没有其他数据类型,因此该答案的上半部分似乎不符合 OP 的规范。 (剧透版本在底部。)

请注意,我们要捕获的行为是当最高有效位不匹配时,1 对应 a0 对应 b。另一种思考方式是a 中的任何位大于b 中的相应位意味着a 大于b,只要a 中没有更早的位小于b中的对应位。

为此,我们计算a 中的所有位大于b 中的相应位,同样计算a 中的所有位小于b 中的相应位。我们现在要屏蔽掉所有低于任何“小于”位的“大于”位,因此我们取出所有“小于”位并将它们全部涂抹在右侧,形成一个掩码:最高有效位全部设置现在到最低有效位的方法是1

现在我们要做的就是使用简单的位掩码逻辑删除“大于”位设置。

如果a <= b,则结果值为0,如果a > b,则为非零。如果在后一种情况下我们希望它是1,我们可以做一个类似的涂抹技巧,只看一下最低有效位。

#include <stdio.h>

// Works for unsigned ints.
// Scroll down to the "actual algorithm" to see the interesting code.

// Utility function for displaying binary representation of an unsigned integer
void printBin(unsigned int x) {
    for (int i = 31; i >= 0; i--) printf("%i", (x >> i) & 1);
    printf("\n");
}
// Utility function to print out a separator
void printSep() {
    for (int i = 31; i>= 0; i--) printf("-");
    printf("\n");
}

int main()
{
    while (1)
    {
        unsigned int a, b;

        printf("Enter two unsigned integers separated by spaces: ");
        scanf("%u %u", &a, &b);
        getchar();

        printBin(a);
        printBin(b);
        printSep();

            /************ The actual algorithm starts here ************/

        // These are all the bits in a that are less than their corresponding bits in b.
        unsigned int ltb = ~a & b;

        // These are all the bits in a that are greater than their corresponding bits in b.
        unsigned int gtb = a & ~b;

        ltb |= ltb >> 1;
        ltb |= ltb >> 2;
        ltb |= ltb >> 4;
        ltb |= ltb >> 8;
        ltb |= ltb >> 16;

        // Nonzero if a > b
        // Zero if a <= b
        unsigned int isGt = gtb & ~ltb;

        // If you want to make this exactly '1' when nonzero do this part:
        isGt |= isGt >> 1;
        isGt |= isGt >> 2;
        isGt |= isGt >> 4;
        isGt |= isGt >> 8;
        isGt |= isGt >> 16;
        isGt &= 1;

            /************ The actual algorithm ends here ************/

        // Print out the results.
        printBin(ltb); // Debug info
        printBin(gtb); // Debug info
        printSep();
        printBin(isGt); // The actual result
    }
}

注意:如果您翻转 both 输入的最高位,这也适用于有符号整数,例如a ^= 0x80000000.

剧透

如果您想要一个满足所有要求的答案(包括 25 名或更少):

int isGt(int a, int b)
{
    int diff = a ^ b;
    diff |= diff >> 1;
    diff |= diff >> 2;
    diff |= diff >> 4;
    diff |= diff >> 8;
    diff |= diff >> 16;

    diff &= ~(diff >> 1) | 0x80000000;
    diff &= (a ^ 0x80000000) & (b ^ 0x7fffffff);

    return !!diff;
}

我会解释为什么它会起作用。

【讨论】:

    【解决方案2】:

    要将001xxxxx 转换为00100000,首先执行:

    x |= x >> 4;
    x |= x >> 2;
    x |= x >> 1;
    

    (这是 8 位;要将其扩展到 32,请在序列的开头添加 8 和 16 的移位)。

    这给我们留下了00111111(这种技术有时被称为“位涂抹”)。然后我们可以砍掉除前 1 位以外的所有内容:

    x ^= x >> 1;
    

    留下00100000

    【讨论】:

    • 嗯,有时我也称之为“位涂抹”。通过一些快速的谷歌搜索,我找不到对该术语的各种版本的任何可靠参考。
    • @Kaganar:我也知道这个名字。不过,我不记得第一次听到它是什么时候了。
    【解决方案3】:

    一种无符号变体,可以使用逻辑(&&,||)和比较(!=,==)。

    int u_isgt(unsigned int a, unsigned int b)
    {
        return a != b && (    /* If a == b then a !> b and a !< b.             */
                   b == 0 ||  /* Else if b == 0 a has to be > b (as a != 0).   */
                   (a / b)    /* Else divide; integer division always truncate */
               );             /*              towards zero. Giving 0 if a < b. */
    }
    

    !===很容易被淘汰,即:

    int u_isgt(unsigned int a, unsigned int b)
    {
        return a ^ b && (
                   !(b ^ 0) ||
                   (a / b)
               );
    }
    

    对于 已签名,然后可以扩展为:

    int isgt(int a, int b)
    {
        return
        (a != b) &&
        (
            (!(0x80000000 & a) && 0x80000000 & b) ||  /* if a >= 0 && b < 0  */
            (!(0x80000000 & a) && b == 0) ||
            /* Two more lines, can add them if you like, but as it is homework
             * I'll leave it up to you to decide. 
             * Hint: check on "both negative" and "both not negative". */
        )
        ;
    }
    

    可以更紧凑/消除操作。 (至少一个)但为了清楚起见这样写。

    除了0x80000000,你可以说 ie:

    #include <limits.h>
    static const int INT_NEG = (1 << ((sizeof(int) * CHAR_BIT) - 1));
    

    用这个来测试:

    void test_isgt(int a, int b)
    {
        fprintf(stdout,
            "%11d > %11d = %d : %d %s\n",
            a, b,
            isgt(a, b), (a > b),
            isgt(a, b) != (a>b) ? "BAD!" : "OK!");
    }
    

    结果:

             33 >           0 = 1 : 1 OK!
            -33 >           0 = 0 : 0 OK!
              0 >          33 = 0 : 0 OK!
              0 >         -33 = 1 : 1 OK!
              0 >           0 = 0 : 0 OK!
             33 >          33 = 0 : 0 OK!
            -33 >         -33 = 0 : 0 OK!
             -5 >         -33 = 1 : 1 OK!
            -33 >          -5 = 0 : 0 OK!
    -2147483647 >  2147483647 = 0 : 0 OK!
     2147483647 > -2147483647 = 1 : 1 OK!
     2147483647 >  2147483647 = 0 : 0 OK!
     2147483647 >           0 = 1 : 1 OK!
              0 >  2147483647 = 0 : 0 OK!
    

    【讨论】:

    • &amp;&amp;|| 等逻辑运算符等价于 if/else - 特别是,它们被编译为完全相同的机器代码。所以这不符合约束条件。 (约束的实际语句也列出了运算符,而这些不在列表中。)
    【解决方案4】:

    Kaganar's smaller isGt function 的完全无分支版本可能如下所示:

    int isGt(int a, int b)
    {
        int diff = a ^ b;
        diff |= diff >> 1;
        diff |= diff >> 2;
        diff |= diff >> 4;
        diff |= diff >> 8;
        diff |= diff >> 16;
    
        //1+ on GT, 0 otherwise.
        diff &= ~(diff >> 1) | 0x80000000;
        diff &= (a ^ 0x80000000) & (b ^ 0x7fffffff);
    
        //flatten back to range of 0 or 1.
        diff |= diff >> 1;
        diff |= diff >> 2;
        diff |= diff >> 4;
        diff |= diff >> 8;
        diff |= diff >> 16;
        diff &= 1;
    
        return diff;
    }
    

    这大约需要 60 条指令用于实际计算(MSVC 2010 编译器,在 x86 架构上),另外还有大约 10 个堆栈操作用于函数的序言/结语。

    【讨论】:

      【解决方案5】:

      编辑:

      好的,代码有一些问题,但我修改了它并且以下工作。

      此辅助功能比较数字的第 n 位有效数字:

      int compare ( int a, int b, int n )
      {
          int digit = (0x1 << n-1);
          if ( (a & digit) && (b & digit) )
             return 0; //the digit is the same
      
          if ( (a & digit) && !(b & digit) )
             return 1; //a is greater than b
      
          if ( !(a & digit) && (b & digit) )
             return -1; //b is greater than a
      }
      

      以下应该递归返回较大的数字:

      int larger ( int a, int b )
      {
          for ( int i = 8*sizeof(a) - 1 ; i >= 0 ; i-- )
          {
             if ( int k = compare ( a, b, i ) )
             {
                 return (k == 1) ? a : b;
             }
          }
          return 0; //equal
      }
      

      【讨论】:

      • @Daniel 我打错了,应该是数字而不是 n。
      • 啊,我明白了,你不只是想要int digit = 1 &lt;&lt; n 吗?看起来它会移动一个二次量。
      • @Daniel 有几个问题,现在好了 :)
      • 看起来不错 :-) 除了你确定 n-1 吗?我想如果您将最后一个有效位视为第 1 位,那将是正确的,但您的示例代码似乎下降到 0。
      【解决方案6】:

      尽管我不想做别人的作业,但我无法抗拒这个.. :) 我相信其他人可以想到一个更紧凑的..但这是我的.. 效果很好,包括负数..

      编辑:虽然有几个错误。我会把它留给 OP 来查找并修复它。

          #include<unistd.h>
          #include<stdio.h>
          int a, b, i, ma, mb, a_neg, b_neg, stop;
      
          int flipnum(int *num, int *is_neg) {
              *num = ~(*num) + 1;
              *is_neg = 1;
      
              return 0;
          }
      
          int print_num1() {
              return ((a_neg && printf("bigger number %d\n", mb)) ||
                   printf("bigger number %d\n", ma));
          }
      
          int print_num2() {
              return ((b_neg && printf("bigger number %d\n", ma)) ||
                   printf("bigger number %d\n", mb));
          }
      
          int check_num1(int j) {
              return ((a & j) && print_num1());
          }
      
          int check_num2(int j) {
              return ((b & j) && print_num2());
          }
      
          int recursive_check (int j) {
              ((a & j) ^ (b & j)) && (check_num1(j) || check_num2(j))  && (stop = 1, j = 0);
      
              return(!stop && (j = j >> 1) && recursive_check(j));
          }
      
          int main() {
              int j;
              scanf("%d%d", &a, &b);
              ma = a; mb = b;
      
              i = (sizeof (int) * 8) - 1;
              j = 1 << i;
      
              ((a & j) && flipnum(&a, &a_neg));
      
              ((b & j) && flipnum(&b, &b_neg));
      
              j = 1 << (i - 1);
      
              recursive_check(j);
      
              (!stop && printf("numbers are same..\n"));
          }
      

      【讨论】:

        【解决方案7】:

        我想我有 3 个操作的解决方案:

        将第一个数字加一个,然后从您可以表示的最大可能数字中减去它(全为 1)。将该数字添加到第二个数字。如果它溢出,那么第一个数字小于第二个。

        我不能 100% 确定这是否正确。那就是你可能不需要加1,我不知道是否可以检查溢出(如果没有,那么只需保留最后一位并测试它最后是否为1。)

        【讨论】:

          【解决方案8】:

          编辑:约束使底部的简单方法无效。我正在添加二进制搜索功能和最终比较以检测更大的值:

          unsigned long greater(unsigned long a, unsigned long b) {
              unsigned long x = a;
              unsigned long y = b;
              unsigned long t = a ^ b;
              if (t & 0xFFFF0000) {
                  x >>= 16;
                  y >>= 16;
                  t >>= 16;
              }
              if (t & 0xFF00) {
                  x >>= 8;
                  y >>= 8;
                  t >>= 8;
              }
              if (t & 0xf0) {
                  x >>= 4;
                  y >>= 4;
                  t >>= 4;
              }
              if ( t & 0xc) {
                  x >>= 2;
                  y >>= 2;
                  t >>= 2;
              }
              if ( t & 0x2) {
                  x >>= 1;
                  y >>= 1;
                  t >>= 1;
              }
              return (x & 1) ? a : b;
          }
          

          我们的想法是从我们感兴趣的单词的最重要的一半开始,看看那里是否有任何设置位。如果有,那么我们不需要最低有效的一半,因此我们将不需要的位移开。如果没有,我们什么也不做(反正一半是零,所以不会妨碍)。由于我们无法跟踪移动的数量(需要添加),我们还移动了原始值,以便我们可以通过最终的and 来确定更大的数字。我们用前一个掩码的一半大小重复这个过程,直到我们将感兴趣的位折叠到位位置 0。

          我没有故意在这里添加相等的大小写。


          旧答案:

          最简单的方法可能是最好的家庭作业。一旦你得到了不匹配的位值,你从 0x80000000 (或任何适合你的字大小的最大位位置)开始另一个掩码,并继续右移直到你碰到在你的不匹配值中设置的位。如果您的右移以 0 结束,则不匹配值为 0。

          我假设您已经知道确定较大数字所需的最后一步。

          【讨论】:

          • 限制为 25 个运算符并且没有循环
          • 在这种情况下,您需要手动二分搜索(稍加改动)。用来源更新答案 - 描述时间太长。 :)
          • if 会让事情变得更加困难。如果我能得到答案,我会更新答案。
          猜你喜欢
          • 2023-04-04
          • 1970-01-01
          • 1970-01-01
          • 2011-06-27
          • 1970-01-01
          • 2022-09-24
          • 2011-03-11
          • 2019-02-11
          • 2011-06-05
          相关资源
          最近更新 更多