- 如果
x == y
,那么x - y == 0
。
- 如果
x < y
,那么x - y < 0
- 如果
x > y
,那么x - y > 0
因此,我们希望将上述3个条件转换为cmp
函数所需的3个单一输出值:
int cmp( int x, int y ) {
return -1 if x < y
return 0 if x == y
return 1 if x > y
}
这可以被重新定义为:
int cmp( int x, int y ) return singleValue( x - y );
int singleValue( int diff ) {
return -1 if diff < 0
return 0 if diff == 0
return 1 if diff > 0
}
现在考虑(并假设)计算机使用32位有符号整数的
二进制补码,也称为
int
),那么所有负值都将具有最高有效位(MSB,第
0
位)设置为
1
。
对于32位整数,这意味着以下表达式对于所有负数都成立:
( anyNegativeNumber & 0x8000000 ) == 0x8000000
相反的情况也是成立的:所有正的非零整数的最高位都是
0
。最后,所有值为零的数(
int zero == 0
)其所有位都被设置为
0
。
( anyPositiveNumber & 0x8000000 ) == 0
如果我们查看MSB(最高位)并检查是否有其他位为
1
,结合上述
singleValue
函数的期望输出:
value | first bit | any other bits | desired output
0 | 0 | 0 | 0b ( 0)
122 | 0 | 1 | 1b ( 1)
-128 | 1 | 0 | 11111...111b (-1)
-123 | 1 | 1 | 11111...111b (-1)
我们可以通过掩码位直接从输入值中创建
0
和
1
,但是
-1
是一个特殊情况,但我们可以像这样处理它:
int diff = x - y; // so only the 1st and last bits are set
如果差异的第一位被设置,则返回
-1
。如果差异值为
0
,则返回
0
。否则返回
1
。
return ( diff & 0x80000000 == 0x80000000 ) ? 0xFFFFFFFF : ( diff != 0 );
这可以压缩:
int diff;
return ( ( ( diff = x - y ) & 0x80000000 ) == 0x80000000 ) ? 0xFFFFFFFF : ( diff != 0 );
但是仍然使用==
和!=
运算符,这可以通过利用单个位(第n
位)值可以向右移动n
位以将其转换为布尔值的事实来消除:
( diff = x - y ) >> 31 // evaluates to 1 if x < y, 0 if x == y or x > y
"
可以通过利用!!a
对于所有非零值为1
,对于零值为0
的事实来消除diff != 0
位:
"
!diff // evaluates to 1 if diff == 0, else 0
!!diff // evaluates to 0 if diff == 0, else 1
我们可以将两者结合起来:
int diff;
return ( ( diff = x - y ) >> 31 ) ? -1 : !!diff;
这个操作中包含了一个分支语句(
?:
)和一个临时变量(
diff
),但是同样的函数也可以不使用分支语句。
可以看到,这个操作有三种可能的输出结果:
0xFFFFFFFF == 1111_1111 1111_1111 1111_1111 1111_1111 b
0x00000000 == 0000_0000 0000_0000 0000_0000 0000_0000 b
0x00000001 == 0000_0000 0000_0000 0000_0000 0000_0001 b
< p > < code >>> 运算符对于有符号值具有“符号扩展”的特性,这意味着:
1000 b >> 2 == 1110 b
0100 b >> 2 == 0001 b
如果第0位是1,则diff >> 31
为1111..1111
,否则为0000..0000
。
每个单独的位的值可以表示为diff
的函数:
a = ( diff >> 31 ) // due to sign-extension, `a` will only ever be either 1111..1111 or 0000..0000
b = !!diff // `b` will only ever 1 or 0
c = a | b // bitwise OR means that `1111..1111 | 0` is still `1111..1111` but `0000..0000 | 1` will be `0000..0001`.
或者只需:
c = ( diff >> 31 ) | ( !!diff );
将此代入上述表达式:
int diff = x - y;
return ( diff >> 31 ) | !!diff;
或者
int diff;
return diff = x - y, ( diff >> 31 ) | !!diff;
逗号运算符 必须使用,因为C语言不指定也不保证二元运算符操作数表达式的求值顺序,但逗号运算符的求值顺序是确定的。
由于这是一个内联函数,并且假设我们可以接受可变参数,那么我们可以消除 diff
,因为我们只使用了 x
或者 y
一次:
return x = x - y, ( x >> 31 ) | !!x;
这是我的测试程序和使用GCC得到的输出:
#include <stdio.h>
int cmp(int x, int y) {
return x = x - y, ( x >> 31 ) | !!x;
}
int main() {
printf( "cmp( 1, 2 ) == %d\n", cmp( 1,2 ) );
printf( "cmp( 2, 2 ) == %d\n", cmp( 2,2 ) );
printf( "cmp( 2, 1 ) == %d\n", cmp( 2,1 ) );
}
输出:
cmp( 1, 2 ) == -1
cmp( 2, 2 ) == 0
cmp( 2, 1 ) == 1
现在这个算法并不完美,因为如果x
和y
都是大数且x
为负数,例如(-4000000000) - (4000000000)
,就会出现整数溢出的问题。虽然可以检查此条件,但这样做违背了让代码尽可能简洁的初衷,而且还需要添加处理错误情况的代码。在这种情况下,更好的方法是仅检查用户提供的输入,而不是检查函数参数值。
TL;DR:
int cmp(int x, int y) {
return x = x - y, ( x >> 31 ) | !!x;
}
<
、>
、==
、!=
、<=
和>=
。 - Dai