【问题标题】:Bitwise operation in C to compare two integersC中的按位运算比较两个整数
【发布时间】:2017-09-09 23:53:17
【问题描述】:

我最近在我的一堂课上接受了测验。问题如下:

用 C 语言编写一个函数(称为 cmp),它接受两个整数(xy) 并返回:-1 如果x y,0 如果x = y1 如果x > y。尽可能简洁地写cmp

我能想到的最简洁的函数是:

int cmp(int x, int y) {
    return ((x < y) ? (-1) : ((x == y) ? (0) : (1)));
}

但我有一种感觉,我可以使用一些操作来更简洁地执行此操作。也许&amp;^ 的组合?过去几天这一直困扰着我,我想知道实际上是否有 IS 更好的方法来做到这一点?

【问题讨论】:

  • 你不需要那么多括号。
  • 小心假设您可以使用按位运算 - IIRC C 语言没有指定有符号整数的实现细节:它们可以使用二进制补码、补码、符号位或其他一些奇异的格式。我相信唯一能保证正确比较有符号整数的方法是内置运算符&lt;&gt;==!=&lt;=&gt;=
  • @Dai:你是对的。我想我只是想小心我的代码。测验前的讲座多次提到位操作。因此,我有一种预感,可能会有一些位操作概念正在测试中。
  • 我投票结束这个问题作为离题,因为实际上 promote 不可读代码的问题对你不利:-) 你所说的更好的方法是写你的代码就好像你的祖母需要理解它一样,让编译器负责优化——这比大多数开发人员都好得多。
  • 在内部,编译器执行减法并设置 CPU 标志。您可以减去,如果为 0,则返回 0,或者将高位向下移动到最低位并返回。它更快吗?不,可能不是。但是您必须比较每个的汇编器输出才能确定。老实说,您进行 2 次比较的方式一点也不差。这仅取决于比较/跳转的成本,并且每个 CPU 都会有所不同。

标签: c bit-manipulation bitwise-operators


【解决方案1】:

“尽可能简洁”是测验的一个极其模糊的要求。你会打代码高尔夫吗?删除空格和括号会使其更简洁吗?无论如何,这里有一个对比较结果使用算术的解决方案:

int cmp(int x, int y) {
    return (x > y) - (x < y);
}

【讨论】:

  • 确实,C 的意图是简洁还是使用底层机器语言编写旨在简洁(或快速?)的代码?
  • C99 引入了 bool 类型 - 但是比较运算符仍然会导致 int 表达式,还是现在是 bool?减去bool 值的操作是否定义明确?
  • @Dai:比较运算符在 C99 中仍然会产生 int 表达式,但即使没有,从 boolint 的转换仍然是明确定义的。
  • @Dai bool 值只能有两个值,我们需要三个。对于&lt;==&gt;。所以这里 bool 是不够的
  • x - y 可以溢出。特别是如果您正在研究网络安全,您应该避免在某些输入上出现不可预测、未定义或不正确结果的捷径。
【解决方案2】:
  • 如果x == y 那么x - y == 0
  • 如果x &lt; y 那么x - y &lt; 0
  • 如果x &gt; y 那么x - y &gt; 0

所以我们想看看我们是否可以将上面 3 个要点中描述的 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
}

现在考虑(并假设)计算机将 two's complement 用于 32 位有符号整数,也就是 int)然后所有负值的最高有效位(MSB,0th 位)设置为 @ 987654336@.

对于 32 位整数,这意味着以下表达式对所有负数都为真:

( anyNegativeNumber & 0x8000000 ) == 0x8000000

反之亦然:所有正非零整数的 MSB 为 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)

我们可以通过屏蔽位直接从输入值创建01,但-1 是一种特殊情况,但我们可以处理它,如下所示:

int diff = x - y; // so only the 1st and last bits are set

如果设置了 diff 的第 1 位,则返回 -1。 如果差异值为0,则返回0 否则返回1

return ( diff & 0x80000000 == 0x80000000 ) ? 0xFFFFFFFF : ( diff != 0 );

这可以压缩:

int diff;
return ( ( ( diff = x - y ) & 0x80000000 ) == 0x80000000 ) ? 0xFFFFFFFF : ( diff != 0 );

这仍然使用==!= 运算符,但可以通过利用可以移动一位(nth 位)值的事实来消除n-bits将其转换为布尔值的权利:

( diff = x - y ) >> 31 // evaluates to 1 if x < y, 0 if x == y or x > y

diff != 0 位可以通过利用以下事实来消除:!!a 对于所有非零值都是 1,对于零值是 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

&gt;&gt; 运算符具有符号值的“符号扩展”,这意味着:

1000 b >> 2 == 1110 b
0100 b >> 2 == 0001 b

如果0th 位为1,则diff &gt;&gt; 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;

必须使用The comma operator,因为C不指定也不保证二元运算符操作数表达式的求值顺序,但逗号运算符的求值顺序是。

由于这是一个内联函数,并且假设我们可以使用可变参数,那么我们可以消除diff,因为我们只使用一次xy

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

如果xy 都是大数并且x 是负数,那么由于整数溢出的问题,现在这并不完美,例如(-4000000000) - (4000000000)Checking for this condition is possible 但是没有让代码尽可能简洁——您还需要添加处理错误情况的代码。在这种情况下,更好的方法是简单地检查用户提供的输入而不是检查函数参数值。

TL;DR:

int cmp(int x, int y) {

    return x = x - y, ( x >> 31 ) | !!x;
}

【讨论】:

  • 我喜欢答案的详细程度。感谢您解释每个步骤。
  • ideone.com/MKMWXk(从一个大的正数中减去一个大的负数会导致整数溢出)。假设类似 gcc 的行为,这将导致一个 negative 数字,因此x &gt; y ==&gt; x - y &gt; 0 的断言并不总是正确的。
【解决方案3】:

我可以建议一下吗:

int cmp(int x, int y) {
    return (x < y) ? -1 : (x > y);
}

x &gt; y 的比较在 x 较大时为 1,在不较大时为 0。 Ryan 的回答较短,但我认为这个版本仍然清楚地显示了代码的用途。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-05
    • 1970-01-01
    相关资源
    最近更新 更多