解释这个片段,它在不使用if-else或任何其他比较运算符的情况下找到最多两个整数?

时间:2011-01-23 07:39:45

标签: c algorithm math bit-manipulation max

找到最多两个数字。您不应该使用if-else或任何其他比较运算符。我在网上公告板上发现了这个问题,所以我想我应该在StackOverflow中提问

实施例 输入:5,10 输出:10

我找到了这个解决方案,有人可以帮我理解这些代码行

int getMax(int a, int b) {  
    int c = a - b;  
    int k = (c >> 31) & 0x1;  
    int max = a - k * c;  
    return max;  
}

20 个答案:

答案 0 :(得分:116)

int getMax(int a, int b) {
    int c = a - b;
    int k = (c >> 31) & 0x1;
    int max = a - k * c;
    return max;
}

让我们剖析一下。第一行看起来很简单 - 它存储ab的差异。如果a < b,此值为负,否则为非负。这里实际上存在一个错误 - 如果数字ab之间的差异太大而无法放入整数,这将导致未定义的行为 - 哎呀!所以我们假设这不会发生。

在下一行,即

int k = (c >> 31) & 0x1;

我们的想法是检查c的值是否为负值。在几乎所有现代计算机中,数字以称为二进制补码的格式存储,其中如果数字为正,则数字的最高位为0,如果数字为负,则为1。而且,大多数是32位。 (c >> 31)将数字向下移动31位,在最低位的位置留下数字的最高位。获取此数字并将其与1进行AND运算(除了最后一位之外,其二进制表示为0)的下一步将擦除所有较高位,并为您提供最低位。由于c >> 31的最低位是c的最高位,因此将c的最高位读取为0或1.由于最高位为1 iff c是1,这是检查c是否为负(1)还是为正(0)的方法。将此推理与上述相结合,如果k,则a < b为1,否则为0。

最后一步是这样做:

int max = a - k * c;

如果a < b,则k == 1k * c = c = a - b,等等

a - k * c = a - (a - b) = a - a + b = b

a < b开始,这是正确的最大值。否则,如果是a >= b,则k == 0

a - k * c = a - 0 = a

这也是正确的最大值

答案 1 :(得分:28)

我们走了:(a + b) / 2 + |a - b| / 2

答案 2 :(得分:19)

使用按位黑客

r = x ^ ((x ^ y) & -(x < y)); // max(x, y)

如果您知道INT_MIN <= x - y <= INT_MAX,,那么您可以使用以下内容,因为(x - y)仅需要评估一次,因此速度更快。

r = x - ((x - y) & ((x - y) >> (sizeof(int) * CHAR_BIT - 1))); // max(x, y)

来源:Bit Twiddling Hacks by Sean Eron Anderson

答案 3 :(得分:11)

(sqrt( a*a + b*b - 2*a*b ) + a + b) / 2

这是基于与mike.dld's solution相同的技术,但在这里我不太“明显”。 “abs”操作看起来像是在比较某些东西的符号,但我在这里利用了sqrt()总是会返回正平方根的事实,所以我正在平方(ab)将其全部写出来然后正方形 - 再次生根,添加+ b并除以2.

你会看到它总是有效:例如用户的10和5的例子你得到sqrt(100 + 25 - 100)= 5然后加10和5得到20除以2得到10。

如果我们使用9和11作为我们的数字(sqrt(121 + 81 - 198)+ 11 + 9)/ 2 =(sqrt(4)+ 20)/ 2 = 22/2 = 11

答案 4 :(得分:8)

最简单的答案如下。

#include <math.h>

int Max(int x, int y)
{
    return (float)(x + y) / 2.0 + abs((float)(x - y) / 2);
}

int Min(int x, int y)
{
    return (float)(x + y) / 2.0 - abs((float)(x - y) / 2);
}

答案 5 :(得分:4)

int max(int i, int j) {
    int m = ((i-j) >> 31);
    return (m & j) + ((~m) & i);
}

此解决方案避免了乘法。 m将为0x00000000或0xffffffff

答案 6 :(得分:3)

使用移位的想法来提取他人发布的标志,这是另一种方式:

max (a, b) = new[] { a, b } [((a - b) >> 31) & 1]

这将两个数字推入一个数组,其中最大数字由array-element给出,其索引是两个数字之差的符号位。

请注意:

  1. 差异(a - b)可能会溢出。
  2. 如果数字未签名且>>运算符引用逻辑右移,则& 1是不必要的。

答案 7 :(得分:3)

以下是我认为我会做的工作。它并不像你想的那样可读,但是当你开始“我怎么做X而不使用明显的做X的方式时,你必须要有这样的期望。 从理论上讲,这也放弃了一些可移植性,但你必须找到一个非常不寻常的系统才能看到问题。

#define BITS (CHAR_BIT * sizeof(int) - 1)

int findmax(int a, int b) { 
    int rets[] = {a, b};
    return rets[unsigned(a-b)>>BITS];
}

这确实比问题中显示的有一些优点。首先,它计算正确的移位大小,而不是对32位整数进行硬编码。其次,对于大多数编译器,我们可以期望所有乘法都在编译时发生,因此在运行时剩下的所有内容都是微不足道的位操作(减法和移位),然后是加载和返回。简而言之,这几乎肯定会非常快,即使在最小的微控制器上,原始使用的乘法必须在运行时发生,所以虽然它可能在台式机上相当快,但它通常是相当快的在小型微控制器上慢一点。

答案 8 :(得分:2)

以下是这些行正在做的事情:

c是a-b。如果c为负,则a

k是c的第32位,它是c的符号位(假设32位整数。如果在具有64位整数的平台上完成,则此代码将不起作用)。它向右移31位以移除最右边的31位,在最右边的位置留下符号位,然后用1除去它以移除左边的所有位(如果c为负,则将填充1s)。因此,如果c为负,k将为1,如果c为正,则k为0。

然后max = a - k * c。如果c为0,则这意味着a> = b,因此max是-0 * c = a。如果c为1,这意味着a&lt; b然后a - 1 * c = a - (a - b)= a - a + b = b。

总的来说,它只是使用差异的符号位来避免使用大于或小于操作。老实说,这段代码不使用比较,实在是有点傻。 c是比较a和b的结果。代码只是不使用比较运算符。您可以在许多汇编代码中执行类似的操作,只需减去数字然后根据状态寄存器中设置的值跳转。

我还应该补充一点,所有这些解决方案都假设这两个数字是整数。如果它们是浮点数,双精度数或更复杂的东西(BigInts,Rational数字等),那么你真的必须使用比较运算符。比特技巧通常不适用于那些人。

答案 9 :(得分:1)

getMax()函数没有任何逻辑操作 -

int getMax(int a, int b){
    return (a+b+((a-b)>>sizeof(int)*8-1|1)*(a-b))/2;
}

说明:

让我们粉碎&#39; max&#39;成碎片,

max
= ( max + max ) / 2
= ( max + (min+differenceOfMaxMin) ) / 2
= ( max + min + differenceOfMaxMin ) / 2
= ( max + min + | max - min | ) ) / 2

所以函数看起来应该是这样的 -

getMax(a, b)
= ( a + b + absolute(a - b) ) / 2

现在,

absolute(x)
= x [if 'x' is positive] or -x [if 'x' is negative]
= x * ( 1 [if 'x' is positive] or -1 [if 'x' is negative] )
  

在整数正数中,第一位(符号位)是 - 0 ;否定它是 - 1 。通过向右移位(&gt;&gt;),可以捕获第一位。

     

在右移期间,空格由符号位填充。所以 01110001&gt;&gt; 2 = 00011100 ,而 10110001&gt;&gt; 2 = 11101100

     

因此,对于8位数字移位,7位将产生 - 1 1 1 1 1 1 [0或1] 为负,或 0 0 0 0 0 0 0 [0或1] 表示正面。

     

现在,如果使用 00000001(= 1)执行 OR 操作,则负数产生 - 11111111(= -1),并且positive- 00000001(= 1)

所以,

absolute(x)
= x * ( 1 [if 'x' is positive] or -1 [if 'x' is negative] )
= x * ( ( x >> (numberOfBitsInInteger-1) ) | 1 )
= x * ( ( x >> ((numberOfBytesInInteger*bitsInOneByte) - 1) ) | 1 )
= x * ( ( x >> ((sizeOf(int)*8) - 1) ) | 1 )

最后,

getMax(a, b)
= ( a + b + absolute(a - b) ) / 2
= ( a + b + ((a-b) * ( ( (a-b) >> ((sizeOf(int)*8) - 1) ) | 1 )) ) / 2

另一种方式 -

int getMax(int a, int b){
    int i[] = {a, b};
    return i[( (i[0]-i[1]) >> (sizeof(int)*8 - 1) ) & 1 ];
}

答案 10 :(得分:0)

没有逻辑运算符,没有库(JS)

function (x, y) {
    let z = (x - y) ** 2;
    z = z ** .5;
    return (x + y + z) / 2
}

答案 11 :(得分:0)

///在C#中,您可以使用数学库来执行最小或最大函数

使用系统;

ClassNumberComparator {

static void Main()
{

    Console.Write(" write the first number to compare: ");
    double first_Number = double.Parse(Console.ReadLine());

    Console.Write(" write the second number to compare: ");
    double second_Number = double.Parse(Console.ReadLine());

    double compare_Numbers = Math.Max(first_Number, second_Number);
    Console.Write("{0} is greater",compare_Numbers);

}

}

答案 12 :(得分:0)

以下是一些 bit-twiddling 方法,可以获得两个整数值的最大值:

方法1

int max1(int a, int b) {
  static const size_t SIGN_BIT_SHIFT = sizeof(a) * 8 - 1;
  int mask = (a - b) >> SIGN_BIT_SHIFT;
  return (a & ~mask) | (b & mask);
}

说明:

  • (a-b)&gt;&gt; SIGN_BIT_SHIFT - 如果a > ba - b为正,则符号位为0,掩码为0x00.00。否则,a < b因此a - b为负数,符号位为1,移位后,我们会获得0xFF..FF的掩码
  • (a&amp;〜mask) - 如果掩码为0xFF..FF,则~mask0x00..00,然后此值为0。否则,~mask0xFF..FF,值为a
  • (b&amp; mask) - 如果掩码为0xFF..FF,则此值为b。否则,mask0x00..00,值为0

最后:

  • 如果a >= b然后a - b为正,我们会max = a | 0 = a
  • 如果a < b然后a - b为否定,我们会max = 0 | b = b

方法2

int max2(int a, int b) {
  static const size_t SIGN_BIT_SHIFT = sizeof(a) * 8 - 1;
  int mask = (a - b) >> SIGN_BIT_SHIFT;
  return a ^ ((a ^ b) & mask);
}

说明:

  • 面具说明与方法1 相同。如果a > b掩码为0x00..00,则掩码为0xFF..FF
  • 如果掩码为0x00..00,则(a ^ b) & mask0x00..00
  • 如果掩码为0xFF..FF,则(a ^ b) & maska ^ b

最后:

  • 如果a >= b,我们会a ^ 0x00..00 = a
  • 如果a < b,我们会a ^ a ^ b = b

答案 13 :(得分:0)

#include<stdio.h>
main()
{
        int num1,num2,diff;
        printf("Enter number 1 : ");
        scanf("%d",&num1);
        printf("Enter number 2 : ");
        scanf("%d",&num2);
        diff=num1-num2;
        num1=abs(diff);
        num2=num1+diff;
        if(num1==num2)
                printf("Both number are equal\n");
        else if(num2==0)
                printf("Num2 > Num1\n");
        else
                printf("Num1 > Num2\n");
}

答案 14 :(得分:0)

我提供的代码是在两个数字之间找到最大值,数字可以是任何数据类型(整数,浮点数)。如果输入数字相等,则函数返回数字。

double findmax(double a, double b)
{
    //find the difference of the two numbers
    double diff=a-b;
    double temp_diff=diff;
    int int_diff=temp_diff;
    /*
      For the floating point numbers the difference contains decimal
      values (for example 0.0009, 2.63 etc.) if the left side of '.' contains 0 then we need
      to get a non-zero number on the left side of '.'
    */
    while ( (!(int_diff|0)) && ((temp_diff-int_diff)||(0.0)) )
    {
       temp_diff = temp_diff * 10;
       int_diff = temp_diff;
    }
    /*
      shift the sign bit of variable 'int_diff' to the LSB position and find if it is 
      1(difference is -ve) or 0(difference is +ve) , then multiply it with the difference of
      the two numbers (variable 'diff') then subtract it with the variable a.
    */
    return a- (diff * ( int_diff >> (sizeof(int) * 8 - 1 ) & 1 ));
}

<强>描述

  • 该函数首先将参数作为double,并将返回类型作为double。这样做的原因是创建一个可以为所有类型找到最大值的单个函数。当提供整数类型数字或者一个是整数而另一个是浮点时,那么由于隐式转换,该函数也可以用来找到整数的最大值。
  • 基本逻辑很简单,假设我们有两个数字a&amp; b如果a-b> 0(即差值为正),则a为最大值,如果a-b == 0则两者相等且a-b <0(即diff为-ve)b为最大值。
  • 符号位保存为内存中的最高有效位(MSB)。如果MSB为1,反之亦然。为了检查MSB是1还是0,我们将MSB移位到LSB位置并按位和&amp;如果结果为1则为1,则数字为-ve else no。是+ ve。该结果通过以下声明获得:

    int_diff&gt;&gt; (sizeof(int)* 8 - 1)&amp; 1

这里为了获得从MSB到LSB的符号位,我们将其右移到k-1位(其中k是在存储器中保存整数所需的位数,这取决于系统的类型)。这里k = sizeof(int)* 8,因为sizeof()给出了保存整数以获得no所需的字节数。比特,我们乘以8.在右移之后,我们应用按位&amp;用1得到结果。

  • 现在获得结果(让我们假设它为r)为1(对于-ve diff)和0(对于+ ve diff)我们将结果乘以两个数的差值,逻辑是给出如下:

    1. 如果a> b,则a-b> 0,即,+ ve,因此结果为0(即,r = 0)。所以a-(a-b)* r =&gt; a-(a-b)* 0,给出'a'作为最大值。
    2. 如果a&lt; b然后a-b <0,即-ve,结果是1(即r = 1)。所以a-(a-b)* r =&gt; a-(a-b)* 1 =&gt; a-a + b =&gt; b,它给出'b'作为最大值。
  • 现在剩下两点1.使用while循环和2.为什么我使用变量'int_diff'作为整数。要正确回答这些问题,我们必须了解一些观点:

    1. 浮点类型值不能用作按位运算符的操作数。
    2. 由于上述原因,我们需要通过使用按位运算符来获取整数值中的值以获得差异的符号。这两点描述了变量'int_diff'作为整数类型的需要。
    3. 现在让我们说我们发现变量'diff'的差异现在'diff'的值有3种可能性,而不管这些值的符号如何。 (一个)。 | diff |&gt; = 1,(b)。 0&lt; | diff |&lt; 1,(c)。 | DIFF |。== 0
    4. 当我们为整数变量赋值时,小数部分会丢失。
    5. 对于情况(a),'int_diff'的值> 0(即1,2,...)。对于其他两种情况,int_diff = 0。
    6. 条件(temp_diff-int_diff)|| 0.0检查diff == 0这两个数字是否相等。
    7. 如果diff!= 0,那么我们检查int_diff | 0是否为真,即情况(b)为真
    8. 在while循环中,我们尝试将int_diff的值设为非零,以便int_diff的值也得到diff的符号。

答案 15 :(得分:0)

static int mymax(int a,int b)

    {
        int[] arr;
        arr = new int[3];
        arr[0] = b;
        arr[1] = a;
        arr[2] = a;
        return arr[Math.Sign(a - b) + 1];

    }

如果b> a然后(ab)将为负数,符号将返回-1,通过加1,我们得到索引0,即b,如果b = a则ab将为0,+ 1将给出1个索引因此如果我们是返回a或b,当a> b然后a-b将为正,sign将返回1,加1,我们得到索引2,其中a存储。

答案 16 :(得分:-2)

问题中描述的逻辑可以解释为如果第一个数字小于0则将被减去,否则将从第一个数字中减去差值以获得第二个数字。 我发现了另外一个数学解决方案,我认为理解这个概念有点简单。

将a和b视为给定数字

c=|a/b|+1;
d=(c-1)/b;
smallest number= a - d*(a-b);

再次,想法是找到0或1的k并将其与两个数的差相乘。最后,应该从第一个数中减去该数,以得到两个数中较小的数。 附:如果第二个数字为零,此解决方案将失败

答案 17 :(得分:-2)

int a=151;
int b=121;
int k=Math.abs(a-b);
int j= a+b;
double k1=(double)(k);
double j1= (double) (j);
double c=Math.ceil(k1/2) + Math.floor(j1/2);
int c1= (int) (c);
System.out.println(" Max value = " + c1);

答案 18 :(得分:-3)

有一种方法

 public static int Min(int a, int b)
  {
   int dif = (int)(((uint)(a - b)) >> 31);
   return a * dif + b * (1 - dif);
  }

和一个

return (a>=b)?b:a;

答案 19 :(得分:-4)

猜猜我们可以将这些数字与其按位比较相乘,例如:

int max=(a>b)*a+(a<=b)*b;