我正在编写一个函数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'];
答案 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
为零,则这两个表达式的按位0
为x
,否则为-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。所以,一种理论上的方法可能是
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 ....
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
在高位位置为零或一。最终返回值r
将s
右移一,以避免在分配回有符号整数时出现溢出。 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;
}