使用位操作找到最小值

时间:2016-08-28 20:15:44

标签: c bit-manipulation

任何人都可以向我解释以下代码行。它用于查找最少两个数字。

int min(int x, int y)

{

  return  y + ((x - y) & ((x - y) >>
            (sizeof(int) * CHAR_BIT - 1)));

}

提前致谢。

3 个答案:

答案 0 :(得分:5)

  

用于查找最少两个数字。

对于int的许多组合来说,这个代码很不幸失败,因此它的价值非常低。 @harold一个好的编译器会识别x < y ? x: y;并生成正确和快速的代码。 @David C. Rankin

确定它是如何工作的并不像失败那样有趣。

  1. 未定义的行为:如果x - y溢出,兼容的编译器可能会生成任何输出 - 甚至崩溃。优化编译器利用这一点使新程序员感到懊恼。

  2. 移动符号位是与some_negative_int >> (sizeof(int) * CHAR_BIT - 1)))一样的实现定义行为。 int的算术右移是常见的,但未由C指定。

  3. 如果some_int >> (sizeof(int) * CHAR_BIT - 1)))包含填充,则
  4. int可以超过允许的最大班次(这种情况很少见)。

  5. OP的代码在x,y的许多组合中失败 - 121个测试案例中的31个失败 - 见下文。 "It accomplishes this by an arithmetic shift"是实现定义的行为。 x-y的潜在溢出是未定义的行为。如果不解决这些问题,任何答案都是不完整的。

    极端情况"it works for any other sizes"通常属实,但稀有平台可能会在int中使用填充,导致sizeof(int) * CHAR_BIT - 1出现问题。

    #include <stdio.h>
    
    int minz(int x, int y) {
      return  y + ((x - y) & ((x - y) >> (sizeof(int) * CHAR_BIT - 1)));
    }
    
    void testmin(int x, int y) {
      static unsigned count = 0;
      static unsigned fail = 0;
      int min0 = x < y ? x: y;
      int min1 = minz(x,y);
      count++;
      if (min0 != min1) {
        fail++;
        printf("%u/%u min(%d, %d)--> %d, should be %d\n", fail,count, x,y, min1, min0);
      }
    }
    int main(void) {
      const int i[]={INT_MIN, INT_MIN+1, INT_MIN+2, -2,-1,0, 1, 2, INT_MAX-2,INT_MAX-1, INT_MAX};
      int x,y;
      for (x=0; x<sizeof i/sizeof i[0]; x++) {
        for (y=0; y<sizeof i/sizeof i[0]; y++) {
          testmin(i[x],i[y]);
        }
      }
    }
    

    输出(失败)

    1/7 min(-2147483648, 1)--> 1, should be -2147483648
    2/8 min(-2147483648, 2)--> 2, should be -2147483648
    3/9 min(-2147483648, 2147483645)--> 2147483645, should be -2147483648
    4/10 min(-2147483648, 2147483646)--> 2147483646, should be -2147483648
    5/11 min(-2147483648, 2147483647)--> 2147483647, should be -2147483648
    6/19 min(-2147483647, 2)--> 2, should be -2147483647
    7/20 min(-2147483647, 2147483645)--> 2147483645, should be -2147483647
    8/21 min(-2147483647, 2147483646)--> 2147483646, should be -2147483647
    9/22 min(-2147483647, 2147483647)--> 2147483647, should be -2147483647
    10/31 min(-2147483646, 2147483645)--> 2147483645, should be -2147483646
    11/32 min(-2147483646, 2147483646)--> 2147483646, should be -2147483646
    12/33 min(-2147483646, 2147483647)--> 2147483647, should be -2147483646
    13/44 min(-2, 2147483647)--> 2147483647, should be -2
    14/56 min(0, -2147483648)--> 0, should be -2147483648
    15/67 min(1, -2147483648)--> 1, should be -2147483648
    16/68 min(1, -2147483647)--> 1, should be -2147483647
    17/78 min(2, -2147483648)--> 2, should be -2147483648
    18/79 min(2, -2147483647)--> 2, should be -2147483647
    19/80 min(2, -2147483646)--> 2, should be -2147483646
    20/89 min(2147483645, -2147483648)--> 2147483645, should be -2147483648
    21/90 min(2147483645, -2147483647)--> 2147483645, should be -2147483647
    22/91 min(2147483645, -2147483646)--> 2147483645, should be -2147483646
    23/100 min(2147483646, -2147483648)--> 2147483646, should be -2147483648
    24/101 min(2147483646, -2147483647)--> 2147483646, should be -2147483647
    25/102 min(2147483646, -2147483646)--> 2147483646, should be -2147483646
    26/103 min(2147483646, -2)--> 2147483646, should be -2
    27/111 min(2147483647, -2147483648)--> 2147483647, should be -2147483648
    28/112 min(2147483647, -2147483647)--> 2147483647, should be -2147483647
    29/113 min(2147483647, -2147483646)--> 2147483647, should be -2147483646
    30/114 min(2147483647, -2)--> 2147483647, should be -2
    31/115 min(2147483647, -1)--> 2147483647, should be -1
    

答案 1 :(得分:4)

如果-1,则此部分的值为x<y,否则为0

(x - y) >> (sizeof(int) * CHAR_BIT - 1)

它通过31位的算术移位来实现这一点(如果使用64位整数则为63,等等)。算术移位保留符号位,因此对于负值,您将获得所有位均为1的结果,对于正值,您将得到所有位均为0的结果。例如,如果{{1} }和x=2

y=4

然后使用此值来屏蔽(2 - 4) >> (sizeof(int) * CHAR_BIT - 1) == (-2) >> (4 * 8 -1) == (-2) >> 31 == 0xFFFFFFFE >> 31 == 0xFFFFFFFF == -1 。也就是说,如果(x - y),您将获得(x - y) & -1 == (x - y),否则会获得x<y

最后,该值会添加到(x - y) & 0 == 0,从而产生yy + (x - y) == x

答案 2 :(得分:4)

(为清楚起见,我们假设sizeof(int) == 4CHAR_BIT == 8,但它适用于任何其他尺寸)

我们从内心的表达中解析它。

   (x - y) >> (sizeof(int) * CHAR_BIT - 1)
== (x - y) >> 31

>>的{​​{1}}通常是符号扩展的右移(a.k.a。arithmetic right shift)。移位31位只留下最高位,然后该位扩展到剩余的31位。这实际上相当于⌊(x - y)/ 2 31 ⌋。

int(x - y) >> 31为负数时应该不会-1给出x - y,否则会0。所以这实际上是一种写​​作的奇特方式

   x - y < 0 ? -1 : 0
== x < y ? -1 : 0

请注意,我们在此处说x - y < 0等同于x < y,但这只适用于没有溢出的情况。例如,当x == 0和{ {1}},y == INT_MIN会溢出到x - y这是否定的,但INT_MIN肯定是假的。

将其重新插入完整的表达式:

x < y

y + ((x - y) & ((x - y) >> (sizeof(int) * CHAR_BIT - 1))) == y + ((x - y) & (x < y ? -1 : 0)) // <- assume no overflow 时,我们得到x < y。由于(x - y) & -1已设置所有位,因此这相当于-1。当x - y时,我们得到x >= y,即0。

因此,(x - y) & 0只是写((x - y) & (x < y ? -1 : 0))的一种奇特方式。

x < y ? x - y : 0

由于溢出错误,并且表达式非常棘手,所以在实践中不应该使用此表达式。只需使用== y + (x < y ? x - y : 0) == x < y ? x : y