位操作,如果x!= 0则返回0,否则返回非零值

时间:2016-04-26 05:36:03

标签: c bitwise-operators

我正在编写一个函数is_zero,如果0则返回x != 0,否则返回非{0}。我不允许使用任何常量。例如,不允许x == 0。 (也不允许使用==运算符)

我唯一可以使用的运营商是=~^*(取消引用),&|<<>>+

我现在编写函数的方式是,如果0它将返回x != 0,但即使0它仍然返回x == 0,它不应该返回0做。我尝试过各种各样的组合,但考虑到这些限制,这个功课问题似乎不可能。我在这里张贴作为最后的努力。

我可以告诉我如何在x == 0时让我的代码返回0以外的其他内容,同时在x != 0时仍然返回int is_zero(int x) { return (x ^ x); } 吗?

$truck_id=$_GET['truck_id'];

4 个答案:

答案 0 :(得分:3)

如果您希望代码在不假定int和表示的某个大小的情况下工作,我不相信它可以解决。但这适用于32位整数和两个补码表示:

int is_zero(int x) {
    int zero = (x^x);
    int one = ~(~zero + ~zero);
    int five = one + one + one + one + one;
    int thirtyone = (one << five) + ~zero;
    return ~((x >> thirtyone) | ((~x + one) >> thirtyone));
}

它使用多个赋值来构造常量,但如果需要,代码可以折叠成单个表达式。

如何运作

如果(x >> thirtyone)为负数,则

x为-1,否则为0。 同样,(~x + one) >> thirtyone如果x为正,则为-1,否则为0.

如果or为零,则这两个表达式的按位0x,否则为-1。如果~为零,则按位x然后给出-1,否则为0。

(差不多)单词大小独立解决方案

它不是完全独立于字大小的,但是可以扩展上面的解决方案以适用于16,32和64位整数(尽管仍取决于两个补码表示)。代码注意不要一次移位超过15位(否则如果int是16位,则结果是未定义的行为):

int is_zero(int x) {
    int zero = (x^x);
    int one = ~(~zero + ~zero);
    int four = one + one + one + one;
    int k15 = (one << four) + ~zero;
    return ~((x >> k15 >> k15 >> k15 >> k15 >> k15) |
             ((~x + one) >> k15 >> k15 >> k15 >> k15 >> k15));
}

答案 1 :(得分:1)

请注意,这建立在@Paul Hankin关于如何将0和1作为常量的答案的基础上。 (如果你被允许使用“/”运算符,那么(x/x)将更容易获得1。所以,一种理论上的方法可能是

  • 获取@PaulHankin的答案中的常量0和1(很好,BTW)
  • 使用这些“常量”,将x移动所有可能的位置左移,对结果进行OR运算(将原始中的任何“1”移位到任何可能的位位置,最后得到-1(0xffff)。我们不知道似乎知道一个整数的大小,做64或128(或1000)次,甚至更经常安全,并涵盖所有合理的int大小。(这条线可能会变得有点长。 ..)增加太多的班次不会受到伤害。

x = x | x << one | x << one+one | x << one + one + one ....

如果移位超过实际的字宽,这可能实际上最终在UB中,但是通过写作来修复(感谢@ PaulHankin的评论)

x = x | x << one | x << one << one | x << one << one << one ....

  • 如果x最初为0,那么你现在的x值为0。如果它有任何位设置(即与0不同),则比最初设置的一组高一点。
  • 做同样的事情向右移动,你将有0xffff(或任何你的单词大小)或0,然后
  • 将上面的结果与-1进行异或(从0 - 1获得一个常数) - 这将导致原始0为-1,其他原始为0,最后返回。

我很确定我不想写那个程序。它可能很容易超出编译器的任何合理行长。

答案 2 :(得分:1)

事实证明,有一个通用的解决方案,它不依赖于字大小,甚至不依赖于二进制补码算法。这是:

int is_zero(int x)
{
    unsigned    y   = x;
    unsigned    c0  = y^y;
    unsigned    c1  = ~(~c0 + ~c0);
    unsigned    c1h = ~c0 ^ (~c0 >> c1);
    unsigned    y2  = y + ~c0;
    unsigned    t1  = y ^ y2;
    unsigned    t2  = t1 & y2;
    unsigned    s   = t2 & c1h;
    int         r   = s >> c1;

    return r;
}

请注意,所有内容都是在unsigned中完成的,这是避免溢出问题所必需的,也是强制右移到零填充。我将参数和返回值保留为有符号整数,第一个和最后一个赋值只是改变了有符号/无符号行为(最后一个转换只是为了避免溢出 - 请参见下面的注释)。

解决方案实际上非常简单。正如已经指出的那样,不断发电是微不足道的。零是通过与自身相关来获得的。所有1都是其中的按位补码。一个是所有1的xor&d;并且所有1都向左移动了一个。

到目前为止,这一切都非常微不足道。棘手的部分是让它无论字大小如何都能发挥作用。为此,如果x为非零,则构造一个高阶位为0的值,如果x为零,则构造一个值。然后它用一个常数来屏蔽它,该常数在高位比特位置是单个1,它由所有1的xor&d; dd构成,所有1'向右移位1.保证移位由于值是无符号的,所以归零(而不是符号扩展)。

请注意,s在高位位置为零或一。最终返回值rs右移一,以避免在分配回有符号整数时出现溢出。 Paul Hankin建议这个修复。这使得最终返回值为零或零,后跟一个后跟全零。

如果要避免函数开头和结尾处的signed和unsigned之间的隐式转换,可以使用union来为值设置别名:

int is_zero(int x)
{
    union {int s; unsigned u;} su;
    su.s = x;
    unsigned    y   = su.u;
    unsigned    c0  = y^y;
    unsigned    c1  = ~(~c0 + ~c0);
    unsigned    c1h = ~c0 ^ (~c0 >> c1);
    unsigned    y2  = y + ~c0;
    unsigned    t1  = y ^ y2;
    unsigned    t2  = t1 & y2;
    unsigned    s   = t2 & c1h;
    su.u = s;
    int         r = su.s;

    return r;
}

在这种情况下,s的最后一次移位是不必要的,返回值为零或者一个后跟全部为零。请注意,C90标准不允许混合代码和声明,因此,如果这是一个问题,则必须将声明与赋值分开,但最终结果将是相同的。

答案 3 :(得分:0)

@TomKarzes的小优化答案:

int is_zero2(int x)
{
    unsigned y   = x;             // use (unsigned) x
    unsigned c0  = y ^ y;         // = 0
    unsigned c1  = ~(~c0 + ~c0);  // = 1
    unsigned c1h = ~(~c0 >> c1);  // = mask for sign bit
    unsigned t2  = ~(y | -y);     // mask for bits below (not including) lowest bit set
    unsigned s   = t2 & c1h;      // sign bit: 0 = (x != 0), 1 = (x == 0)
    int      r   = s >> c1;       // prevent overflow
    return r;
}