在溢出事件中携带位

时间:2017-01-31 19:03:11

标签: c++ logic bit-manipulation

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

  int msbX = x>>31;
  int msbY = y>>31;
  int sum_xy = (y+(~x+1));

  int twoPosAndNegative = (!msbX & !msbY) & sum_xy; //isLessOrEqual is FALSE. 
  // if = true, twoPosAndNegative = 1; Overflow true
  // twoPos = Negative means y < x which means that this 
  int twoNegAndPositive = (msbX & msbY) & !sum_xy;//isLessOrEqual is FALSE
  //We started with two negative numbers, and subtracted X, resulting in positive. Therefore, x is bigger.
  int isEqual = (!x^!y); //isLessOrEqual is TRUE
  return (twoPosAndNegative | twoNegAndPositive | isEqual);
    }

目前,我正在努力研究如何在此运算符中携带位。 此功能的目的是确定是否int y >= int x

这是类赋值的一部分,因此对于转换有限制,我可以使用哪些运算符。

我试图通过应用MSB的补码掩码来计算携带位,以尝试从等式中移除最高有效位,以便它们可以溢出而不会引起问题。

我的印象是,忽略溢出的情况,返回的运算符会起作用。

编辑:这是我的调整后的代码,仍然无法正常工作。但是,我认为这是进步吗?我觉得我在追逐自己的尾巴。

2 个答案:

答案 0 :(得分:1)

int isLessOrEqual(int x, int y)
{
    int msbX = x >> 31;
    int msbY = y >> 31;
    int sign_xy_sum = (y + (~x + 1)) >> 31;
    return ((!msbY & msbX) | (!sign_xy_sum & (!msbY | msbX)));
}

我在我的一位同事的帮助下,以及StackOverflow上的评论员一起解决了这个问题。

解决方案如上所示。

答案 1 :(得分:0)

提问者有self-answered他们的问题(课堂作业),所以此时提供替代解决方案似乎是合适的。问题清楚地假设整数表示为两个补数。

一种方法是考虑CPU如何通过比较指令计算条件分支的谓词。在处理器条件代码中表示的“小于”的符号是SF≠OF。 SF是符号标志,是符号位的副本,或结果的最高有效位(MSB)。 OF是overflow flag,表示有符号整数运算中的溢出。这被计算为进位的XOR和符号位或MSB的进位。使用二进制补码算术a - b = a + ~b + 1,因此a < b = a + ~b < 0。仍然将符号位(MSB)上的计算与低位比特分开。这导致以下代码:

int isLessOrEqual (int a, int b)
{
    int nb = ~b;
    int ma = a  & ((1U << (sizeof(a) * CHAR_BIT - 1)) - 1);
    int mb = nb & ((1U << (sizeof(b) * CHAR_BIT - 1)) - 1);
    // for the following, only the MSB is of interest, other bits are don't care
    int cyin = ma + mb;
    int ovfl = (a ^ cyin) & (a ^ b);
    int sign = (a ^ nb ^ cyin);
    int lteq = sign ^ ovfl;
    // desired predicate is now in the MSB (sign bit) of lteq, extract it
    return (int)((unsigned int)lteq >> (sizeof(lteq) * CHAR_BIT - 1));
}

在最终右移之前转换到unsigned int是必要的,因为根据ISO-C ++标准5.8节,对具有负值的有符号整数进行右移是实现定义的。 Asker已经指出演员阵容允许。当右移有符号整数时,C ++编译器将生成逻辑右移指令或算术右移指令。由于我们只对提取MSB感兴趣,我们可以通过移除然后屏蔽除LSB之外的所有其他位来隔离自己的选择,但需要额外的操作:

    return (lteq >> (sizeof(lteq) * CHAR_BIT - 1)) & 1;

上述解决方案需要总共十一或十二个基本操作。一个明显更有效的解决方案是基于1972年的MIT HAKMEM memo,其中包含以下观察结果:

  

项目23(Schroeppel):( A和B)+(A OR B)= A + B =(A XOR B)+ 2(A和B)。

这很简单,因为A AND B代表进位,A XOR B代表和位。在2000年2月11日的newsgroup postingcomp.arch.arithmetic中,Peter L. Montgomery提供了以下扩展:

  

如果XOR可用,则可以使用此平均值   当总和可能溢出时,两个无符号变量A和B:

 (A+B)/2 = (A AND B) + (A XOR B)/2

在这个问题的上下文中,这允许我们在没有溢出的情况下计算(a + ~b) / 2,然后检查符号位以查看结果是否小于零。虽然蒙哥马利只提到无符号整数,但通过使用算术右移,对有符号整数的扩展是直截了当的,请记住,右移是一个整数除法,它向负无穷大舍入,而不是朝向零作为常规整数除法。

int isLessOrEqual (int a, int b)
{
    int nb = ~b;
    // compute avg(a,~b) without overflow, rounding towards -INF; lteq(a,b) = SF
    int lteq = (a & nb) + arithmetic_right_shift (a ^ nb, 1);
    return (int)((unsigned int)lteq >> (sizeof(lteq) * CHAR_BIT - 1));
}

不幸的是,C ++本身没有提供可编程算术右移的可移植方法,但我们可以使用this answer相当有效地模拟它:

int arithmetic_right_shift (int a, int s)
{
    unsigned int mask_msb = 1U << (sizeof(mask_msb) * CHAR_BIT - 1);
    unsigned int ua = a;
    ua = ua >> s;
    mask_msb = mask_msb >> s;
    return (int)((ua ^ mask_msb) - mask_msb);
}

内联时,当移位计数为编译时常量时,这只会为代码添加几条指令。如果编译器文档指出实现定义的负值有符号整数的处理是通过算术右移指令完成的,那么简化这六个操作解决方案是安全的:

int isLessOrEqual (int a, int b)
{
    int nb = ~b;
    // compute avg(a,~b) without overflow, rounding towards -INF; lteq(a,b) = SF
    int lteq = (a & nb) + ((a ^ nb) >> 1);
    return (int)((unsigned int)lteq >> (sizeof(lteq) * CHAR_BIT - 1));
}

以前在将符号位转换为谓词时使用强制转换的注释也适用于此。