【问题标题】:Determine the sign of a 32 bit int确定 32 位 int 的符号
【发布时间】:2011-11-12 13:46:59
【问题描述】:

仅使用:

! ~ & ^ | + >

没有循环

我需要确定一个 32 位整数的符号,如果是正数,我需要返回 1,如果是 0,我需要返回 0,如果是负数,我需要返回 -1。

有什么想法吗?我首先考虑移动超过 31 位,然后查看那个标志,但这显然行不通,现在我有点卡住了。

【问题讨论】:

  • 我只能判断它是否为负数。如果它为 0,则该数字可能全为零,也可能为正数,但我将这些位移开了。
  • 啊,我错过了也检测 0 的要求。
  • @Oli:它不可移植,因为右移负值的结果是实现定义的,并且没有要求它要么 0-填充或符号填充.也就是说,在实践中它会起作用,因为所有或几乎所有的实现都做一个或另一个。
  • 请注意,!+ 都不是位运算符。
  • 你可以这样做吗,例如if (!x) return 0;

标签: c logic bit-manipulation


【解决方案1】:

如果允许条件(不是if 语句)和减法,最简单和更清洁的解决方案 (IMO) 是:

int sign = (v > 0) - (v < 0);

不使用减法(并假设 int 是 32 位):

#include <stdio.h>
#include <assert.h>
#include <limits.h>

int process(int v) {
    int is_negative = (unsigned int)v >> 31; // or sizeof(int) * CHAR_BIT - 1
    int is_zero = !v;
    int is_positive = !is_negative & !is_zero;
    int sign = (is_positive + ~is_negative) + 1;
    return sign;
}

int main() {
    assert(process(0) == 0);
    printf("passed the zero test\n");
    for (int v = INT_MIN; v < 0; v++) {
        assert(process(v) == -1);
    }
    printf("passed all negative tests\n");
    for (int v = 1; v < INT_MAX; v++) {
        assert(process(v) == +1);
    }
    printf("passed all positive tests\n");
    return 0;
}

结果如下:

$ gcc -o test test.c -Wall -Wextra -O3 -std=c99 && ./test && echo $#
passed zero test
passed all negative tests
passed all positive tests
0

【讨论】:

  • 问题没有指定循环。全部大写。
  • @Carey:请在投票前理解代码。请注意,循环只是为了提供输入值 (v) 来测试算法。
  • 我理解代码。如果您的解决方案仅涉及按位运算符作为问题所需,我不会提及它。
  • @Carey:我将其拆分为一个单独的函数以使其清晰。希望这会有所帮助。
  • 请告诉我这个要求(仅限位运算符)。我没有在问题中看到它。到目前为止,您已经提出了 2 个不同的论点,而且都是无效的。
【解决方案2】:

试试这个:

(x >> 31) | (((0 - x) >> 31) & 1)

这个怎么样:

(x >> 31) | (((~x + 1) >> 31) & 1)

编辑 2:

针对 cme​​ts 中提出的问题(或者更确切地说是挑剔)...

这些解决方案有效的假设:

  1. x 是 32 位有符号整数类型。
  2. 在此系统上,带符号的 32 位整数是二进制补码。 (右移是算术)
  3. 算术溢出时回绕。
  4. 对于第一个解决方案,文字 0 与 x 的类型相同。

【讨论】:

  • 这是否取决于&gt;&gt; 左侧操作数为负时的特定行为?
  • - 不在允许的运算符列表中,不是吗?
  • 如果我们没有二进制补码,那么它实际上是无法解决的,不是吗?我的意思是我们必须对什么是“消极”是正确的做出一些假设?
  • 如果int 的宽度大于 32 位,您的技巧也将不起作用。那么你所有的表达都会在那个宽度上完成,你的转变不会是正确的。
  • 这是错误的。 x&gt;&gt;31 的结果是针对负 x 的实现定义的,不能保证它的值是多少。在符合 C 的实现中,对于所有否定的 x,它可能是 0。但我认为这是问题中的一个缺陷——我强烈怀疑(a)预期的答案使用了不可移植的&gt;&gt;,并且(b)实际提出的问题没有解决方案。所以没有downvote。
【解决方案3】:

为什么需要使用位运算符?

int get_sign(int value)
{
    return (value < 0) ? -1 : (int)(value != 0);
}

如果您绝对必须使用按位运算符,那么您可以使用&amp; 运算符来检查负值,无需移位:

int get_sign(int value)
{
    return (value & 0x80000000) ? -1 : (int)(value != 0);
}

如果你想换班:

int get_sign(int value)
{
    return ((value >> 31) & 1) ? -1 : (int)(value != 0);
}

【讨论】:

    【解决方案4】:

    有点复杂,但有这样的:

    (~((x >> 31) & 1) + 1) | (((~x + 1) >> 31) & 1)
    

    这应该注意移位是否填充 1 或 0 的歧义

    对于细分,我们有这个构造的任何地方:

    (z >> 31) & 1
    

    当为负数时为 1,否则为 0。

    我们拥有的任何地方:

    (~z + 1)
    

    我们得到否定数(-z)

    因此,如果 x 为负,前半部分将产生 0xFFFFFFFF (-1) 的结果,如果 x 为正,则后半部分将产生 0x00000001 (1)。如果两者都不为真,则按位或将它们组合在一起将产生 0x00000000 (0)。

    【讨论】:

      【解决方案5】:

      怎么样:

      int getsign(int n)
      {
        return (!!n) + (~((n >> 30) & 2) + 1);
      }
      

      ..对于 32 位有符号整数,仅 2 的补码。

      如果n 非零,!!n 给出 1。 ((n &gt;&gt; 30) &amp; 2) 如果设置了高位(符号),则给出 2。按位 NOT 和 +1 取其 2 的补码,给出 -2 或 0。 添加 -1 (1 + -2) 表示负值,0 (0 + 0) 表示零,+1 (1 + 0) 表示正值。

      【讨论】:

        【解决方案6】:

        假设实现定义了算术右移:

        (x>>31) | !!x
        

        与 Mystical 的回答不同,没有 UB。

        而且,如果您还想支持将右移定义为算术移位的系统:

        ~!(x>>31)+1 | !!x
        

        编辑:对不起,我在第二个版本中省略了!。应该是:

        ~!!(x>>31)+1 | !!x
        

        这个版本仍然依赖于二进制补码的实现并且具有either算术逻辑右移,即如果实现定义的行为完全是另外一回事可能会破裂。但是,如果将类型更改为 unsigned 类型,则所有实现定义的行为都会消失,结果为 -1U0U1U,具体取决于“符号”(高x 的位和零/非零状态)。

        【讨论】:

        • +1 是迄今为止最优雅的解决方案,尽管它不是真正的解决方案,因为它仍然取决于架构提供的右移类型。难道不能将两者结合成一个独特的解决方案吗?
        • 正确,带负值的有符号整数的右移在 C99 中具有未指定的行为。顺便说一句,您的第二个示例缺少括号?
        • @jweyrich:它不是未指定的,它是实现定义的。在实践中,我们知道我们关心的所有实现要么是算术右移,要么是逻辑右移(如果未指定,它们甚至不必保持一致,但它们必须定义结果,并且可以理解的是,它们似乎总是选择一个或其他)。原则上,一个实现可以定义-1 &gt;&gt; 1 == 0,这就是为什么我认为如果没有一些额外的假设就不可能解决这个问题。当然,也许陈述这些假设是作业的一部分。
        • 第二个表达式似乎没有给出0 的正确答案。
        • 错字,或者至少我会以此为逻辑错误的借口。 :-) 现在已经修复了。
        【解决方案7】:

        我不确定这是否是绝对理想的做事方式,但我认为它具有相当的便携性,并且至少比你所拥有的更简单:

        #define INT_BITS 32
        
        int sign(int v) { 
            return (!!v) | -(int)((unsigned int)v >> (INT_BITS-1));
        }
        

        【讨论】:

        • @Mysticial:有一个小问题:- 在这个问题上实际上是不允许的,所以从技术上讲,它实际上并不能作为这个问题的答案。
        【解决方案8】:

        Dimitri 的想法可以简化为 (!!x) - ((x >> 30) & 2)

        再提供一个神秘的解决方案:

        ~!x & ((-((unsigned) x >> 31)) | !!x)
        

        【讨论】:

          猜你喜欢
          • 2013-05-03
          • 2023-03-11
          • 2013-10-19
          • 1970-01-01
          • 2015-08-03
          • 2011-01-17
          • 2011-08-14
          • 2019-10-31
          • 1970-01-01
          相关资源
          最近更新 更多