如何检查有符号整数是否为正数?

6
使用位运算符和加减法,我该如何检查有符号整数是否为正数(即既非负数也非零)?我相信答案很简单,但就是想不起来。

2
仅供参考 - 以下答案不错,但我认为您不应在实践中使用这些,因为在现代CPU上,“>0”这样的比较只需要1条指令,而下面的任何答案都将成为多条指令,可能无法利用单独的管道或核心。如果您正在制作自己的电路以进行特殊比较并需要减少逻辑路径延迟,则它们很有用。 - Phil
这可能是一个作业问题,特别限制了使用这样的比较 :) - Alex
这与一个更大的作业问题有关,这只是我无法掌握的一部分。 - Rowhawn
请不要忘记添加“作业”标签,这样那些避免作业问题的人就可以避免这些问题。 - Matti Virkkunen
5个回答

5
如果你想在不使用条件语句的情况下(假设为二进制补码),为int n提供一个“严格为正”谓词,有以下几种方法:
  • -n如果n严格为正,则符号(最高)位设置,否则在所有其他情况下均不清除,但是会出现n == INT_MIN的情况;
  • ~n如果n严格为正或为0,则符号位设置,在所有其他情况下均不清除,包括n == INT_MIN
  • …因此,-n & ~n如果n严格为正,则符号位设置,在所有其他情况下均不清除。

应用一个无符号移位,将其转换为0 / 1的答案:

int strictly_positive = (unsigned)(-n & ~n) >> ((sizeof(int) * CHAR_BIT) - 1);

编辑:正如评论中caf所指出的那样,-nn == INT_MIN时会导致溢出(仍然假设为2的补码)。C标准允许程序在这种情况下失败(例如,您可以使用带有-ftrapv选项的GCC启用有符号溢出陷阱)。将n强制转换为无符号数可解决问题(无符号算术不会导致溢出)。因此,改进如下:

unsigned u = (unsigned)n;
int strictly_positive = (-u & ~u) >> ((sizeof(int) * CHAR_BIT) - 1);

n == INT_MIN 时,-n 可能会溢出。 - caf
严格来说,这仅适用于2的补码算术(它们是实际上有意义的系统)。如果存在负零(1的补码或符号-幅度),则您的声明无效。 - Jonathan Leffler
@Jonathan:如果在应用~运算符之前将其转换为unsigned,则它适用于所有系统。 - R.. GitHub STOP HELPING ICE
@caf:已更新,谢谢(忘记了有符号溢出可能会陷入陷阱)。 - Matthew Slattery
现在它只存在一个问题,即(诚然,非常理论化的)“int具有与unsigned int一样多的值位”的情况;) - caf
@caf:而sizeof不是计算类型宽度的正确方法的问题……可能存在填充位。请参阅我对Jonathan答案的评论。 - Jens Gustedt

3

如果您无法使用明显的比较运算符,那么您需要更加努力:

int i = anyValue;
if (i && !(i & (1U << (sizeof(int) * CHAR_BIT - 1))))
    /* I'm almost positive it is positive */

第一个条件检查值是否为零;第二个条件检查值是否没有设置首位。这对于二进制补码、一的补码或符号数都适用。


很不幸,它有未定义的行为 ;) 2的(sizeof(int) * CHAR_BIT - 1)次方在int中肯定是无法表示的。 - caf
@caf:通过将移位后的1转换为带有U后缀的“unsigned int”来解决这个问题……这意味着“i”也将被转换为“unsigned int”。位移操作通常应该在无符号数量上执行。 - Jonathan Leffler
没错,这修复了UB - 现在你只需要担心(更理论的!)情况,即“int”和“unsigned int”具有相同数量的值位... - caf
很不幸,它确实存在 - 在6.2.6.2中:“如果有带符号类型中的M个值位和无符号类型中的N个值位,则M <= N” - 意味着您可以拥有(例如)范围为-131072至131071的“int”和范围为0至131071的“unsigned int”。 - caf
通常,使用 sizeof 进行移位计算不一定总是有效的,因为类型的宽度和大小并不强制相关。可能存在填充位。我认为像 (((unsigned)-1u)/2u)+1u) 这样的表达式会更具可移植性。尽管这也假设 signedunsigned 拥有相同的宽度。 - Jens Gustedt
显示剩余3条评论

3

检查最高位。0表示正数,1表示负数。


1
这对于零来说是失败的,因为它既不是正数也不是负数。 - caf
但是如果最高有效位为0且所有其他位都为零,则返回“正数”,但应该排除零。 - Jonathan Leffler

1
考虑符号如何表示。通常使用 二进制补码 或简单的 符号位 - 我认为这两种方法都可以用简单的逻辑与进行检查。

0

检查它不是0且最高位为0,类似这样:

int positive(int x) {
   return x && (x & 0x80000000);
}

返回 x && (x & MIN_INT) 怎么样? - Ssancho

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接