【问题标题】:Get the greater of two numbers with bitwise operations通过按位运算获得两个数字中的较大者
【发布时间】:2015-01-25 21:51:12
【问题描述】:

我必须使用位操作找到两个数字中的较大者。这些是相同的规则:

 /* 
  * isGreater - if x > y  then return 1, else return 0 
  * Example: isGreater(4,5) = 0, isGreater(5,4) = 1
  *   Legal ops: ! ~ & ^ | + << >>
  *   Max ops: 24
 */

这是我为此编写的代码:

int isGreater(int x, int y) {
/* to find greater, subtract y from x using 2's complement method.
 * then find 2's complement of the answer and shift right by 31 to give MSB
 * which is 1 if x>y and 0 if x<y */
 int ydash=(~y)+0x01;
 int diff=x+ydash;
 int a=(~diff)+0x01;
 int b=a>>31;
 int c=b&0x01;  
 return c;
}

为此我收到此错误:

错误:测试 isGreater(-2147483648[0x80000000],2147483647[0x7fffffff]) 失败... ...给出 1[0x1]。应该是 0[0x0]

我可以使用 unsigned int,但不能使用其他数据类型。我不确定这有什么帮助。我该如何解决这个问题?

【问题讨论】:

  • 是否允许否定?
  • 那么,输入值是有符号整数还是无符号整数?你也可以使用普通的int吗? (看起来答案是“是”,但不妨检查一下。)鉴于错误消息,输入似乎已签名。另外,我们处理的是 32 位值,而不是 64 位值?这是一种虐待狂的家庭作业问题,让那些不在大学里的人发疯。当您被允许使用比较运算符时,答案是微不足道的,而当您不允许使用比较运算符时,答案会非常困难——因为您不是。值得称赞的是,您已经清楚地说明了前面的限制——谢谢。
  • 由于您在代码中使用签名的int,因此每次溢出都是未定义的行为。这是否真的会影响您的答案尚不清楚,但理论上,您应该在类型签名时避免溢出。
  • 在 C 中不是右移负值 UB 吗?
  • 显然否定是允许的。有一个测试工具可以检查每个函数的所有可能输入值。这个函数弹出这个错误。这是作业,这是我在通过测试工具运行之前得到的代码。问题是,我已经尝试了所有我能想到的东西,理论上它在我尝试时适用于数学,但这个错误符合我的编程,因为作为无符号数,负数大于另一个。是的,32 位值;它也驱使那些在大学里疯了的人:)

标签: c comparison max bit-manipulation


【解决方案1】:

在这里您可能不允许使用 if-else 或任何控制语句。但是你可以“构造”一些类似 if-else 的语句。

int isGreater(int x, int y) {        
    int sgnext_x = x >> 31;  //sgnext_x = x >= 0 ? 00000000 : 11111111
    int sgnext_y = y >> 31;
    int sgn_y = sgnext_y & 1;  //sgn_y = y >= 0 ? 0 : 1
    int minus = x + (~y + 1);  // a - b = a + (~b+1)
    int sgn_minus =(minus >> 31) & 1;

    //A control-like statement. ((statement & a) | (!(statement | (b))))
    //Returns a if statment = 11111111
    //Returns (b&1) if statment = 00000000

    int which = sgnext_x ^sgnext_y;
    int result = (!!(x^y)) & ((which & sgn_y) | (!(which | (sgn_minus))));
  return result;
}

【讨论】:

    【解决方案2】:

    如果 x = -2147483648 和 y = 2147483647 那么 x - y = -4,294,967,295 超出了int 的范围,因此结果无法在变量中表示,并且您得到未定义的行为。

    要克服这个问题,您需要使用比 int 更宽的类型。由于您只允许使用 unsigned int,因此如果您想使用更大的类型,则必须自己实现 big int 操作。您也可以使用另一种方式,例如单独检查溢出条件

    if ((x ^ y) & 0x80000000) // x and y have different sign
    {
        return (y >> 31) & 1; // return 1 if y is negative
    }
    else     // x and y have the same sign, overflow never occurs
    {
        unsigned int xu = x, yu = y;
        unsigned int xmu = ~xu + 1U;
        unsigned int diffu = yu + xmu;
        return diffu >> 31;
    }
    

    如果您不允许使用条件,您可以使用复用器来复用值

    unsigned int r1 = (y >> 31) & 1U; // 1st case
    
    unsigned int xu = x, yu = y;
    unsigned int xmu = ~xu + 1U;
    unsigned int diffu = yu + xmu;
    unsigned int r2 = diffu >> 31;    // 2nd case
    
    unsigned int s = ((x ^ y) >> 31) & 1U; // s = 1 if x and y have different sign, 0 otherwise
    unsigned int mask = 0xFFFFFFFFU + s;
    return (r1 & ~mask) | (r2 & mask);
    

    【讨论】:

    • 我不能使用循环或条件,所以没有 if 语句 :( 只有上面代码块中提到的位运算符,不能使用其他任何东西。
    • @stalagmite7 当然,你可以混合它们,这不再防止溢出,但无论如何这不是问题
    【解决方案3】:

    您提出的算法根本不起作用,因为如果您减去,几乎所有可能的答案都可以是a &lt; b 的减法和a &gt;= b 的减法的结果。所以本质上,结果不会告诉你任何东西,除非它为零,那么你就知道a == b

    Hacker's Delight 在第 2.11 章比较谓词中有这个答案:

    x < y: (x & ~y) | ((x ^ ~y) & (x - y))
    

    (verification)

    您知道如何在加法方面实现减法,并且交换xy 也不应该成为问题。结果出现在符号位中。

    【讨论】:

    • 我喜欢你的机器人。但是为什么bit hacks中的很多表达式都返回false呢?例如 y ^ ((x ^ y) &amp; -(x &lt; y)) == min(x, y) 它返回 [false] in 0x7fffffff00000000 cases, for example: [y = -1 (0xffffffff), x = -3 (0xfffffffd)]. Also [true] in 0x8000000100000000 cases, for example: [y = -1 (0xffffffff), x = -1 (0xffffffff)]
    • @LưuVĩnhPhúc 布尔值为 0 或 -1,因此您必须删除该减号。顺便说一句,你可以像let m = x &gt;&gt; 31 in x + m ^ m
    • 你怎么能像那样运行 2^64 次测试?
    • @LưuVĩnhPhúc 我没有,我对此没有足够的耐心。我将表达式转换为 BDD,这就是为什么它对某些事情(和、或、异或、加法、比较)非常有效,而对于其他事情(通过一个变量移动,将两个变量相乘)效果很差或根本不适用跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多