【问题标题】:Convert unsigned int to signed int C将无符号整数转换为有符号整数 C
【发布时间】:2012-01-09 04:16:52
【问题描述】:

我正在尝试将 65529unsigned int 转换为已签名的 int。我试着做这样的演员:

unsigned int x = 65529;
int y = (int) x;

但是y 在应该返回 -7 时仍然返回 65529。这是为什么呢?

【问题讨论】:

  • 你可以试试这个:y=(int)x<<(sizeof(int)*CHAR_BIT-16)>>(sizeof(int)*CHAR_BIT-16);。应该适用于大多数平台。
  • 32 位时代已经到来了几十年。如果您仍在为 DOS 或 int 具有 16 位的嵌入式系统使用古老的编译器,那么结果可能与您预期的一样
  • 为什么会是-7? 65529 - 65535 = -6...
  • @MarcusJ 因为 2^16 是 65536,而不是 65535(提示:2 的幂总是偶数)。所以 16 位整数的正确计算是 65529 - 65536,正如预期的那样,是 -7。您可以从负数倒数:-1 是 65535,-2 是 65534,...,-7 是 65529。但 OP 显然没有使用 16 位整数。
  • @MarcusJ 哈,是的,我看到了,但我碰巧在这个页面上,因为没有人回复你的评论(而且你没有删除它),我想迟到总比没有好。

标签: c int unsigned-integer


【解决方案1】:

您似乎希望 intunsigned int 是一个 16 位整数。显然情况并非如此。最有可能的是,它是一个 32 位整数 - 它足够大,可以避免您所期望的环绕。

请注意,没有完全符合 C 标准的方法来执行此操作,因为超出范围的值在有符号/无符号之间进行转换是实现定义的。但这在大多数情况下仍然有效:

unsigned int x = 65529;
int y = (short) x;      //  If short is a 16-bit integer.

或者:

unsigned int x = 65529;
int y = (int16_t) x;    //  This is defined in <stdint.h>

【讨论】:

  • 如果你能保证short是一个16位整数,那么是的,int y = (short)x;可以工作。或者,如果您的编译器具有 &lt;stdint.h&gt; 标头,则可以使用 int16_t
  • @Nayefc 正如我所说,您可以通过打印 sizeof(int) sizeof(short) 的值或任何您需要的值来检查类型的大小
  • 有一种主要符合 C 标准的方法:y = x &lt; 32767 ? (int)x : (x &gt; 32768 ? -(int)-x : -32768);(唯一的问题是 -32768 不一定可以表示为带符号的 int,因此您必须决定如何处理它)。
  • 是的,这也行得通——尽管是一种更脏的方法。没想到一路分叉去做……
  • @Mysticial:它不会在一个像样的编译器上分支。
【解决方案2】:

我知道这是一个老问题,但这是一个很好的问题,那么这个怎么样?

unsigned short int x = 65529U;
short int y = *(short int*)&x;

printf("%d\n", y);

【讨论】:

  • 你能在这里解释一下吗?谢谢。
  • 因为我们将 x 的地址转换为其类型的签名版本,这是 C 标准所允许的。并非所有像这样(事实上大多数)的类型双关语都是合法的。标准是这样说的。
  • 一个对象的存储值只能由具有以下类型之一的左值访问 *对象的声明类型,*对象声明类型的限定版本,*类型即对应于对象的声明类型的有符号或无符号类型, * 一种类型,它是对应于对象声明类型的限定版本的有符号或无符号类型, * 包括以下之一的聚合或联合类型其成员中的上述类型(包括,递归地,子聚合或包含联合的成员),*字符类型。
  • 您好,非常感谢您的回复。一行代码说一千个单词。如果您可以具体引用一些示例并通过具有以下类型之一的左值来更新您对 "..." 部分的问题,其中星星在上面的示例中进行,那将是非常有帮助。无论哪种方式,+1 响应。
  • @pm89 接受的答案对实现定义的行为是明确的,因此它具有相同的理论问题,但它并没有声称对标准的任何实现产生相同的结果。
【解决方案3】:

@Mysticial 明白了。短片通常是 16 位的,会说明答案:

int main()  
{
    unsigned int x = 65529;
    int y = (int) x;
    printf("%d\n", y);

    unsigned short z = 65529;
    short zz = (short)z;
    printf("%d\n", zz);
}

65529
-7
Press any key to continue . . .


再详细一点。这完全是关于如何将带符号的数字存储在内存中。搜索二进制补码表示法以获取更多详细信息,但这里是基础知识。

让我们看一下十进制的 65529。它可以用十六进制表示为FFF9h。我们也可以用二进制表示:

11111111 11111001

当我们声明 short zz = 65529; 时,编译器会将 65529 解释为有符号值。在二进制补码表示法中,最高位表示有符号值是正数还是负数。在这种情况下,您可以看到最高位是1,因此它被视为负数。这就是为什么它会打印出-7

对于unsigned short,我们不关心符号,因为它是unsigned。所以当我们使用%d打印出来时,我们使用了所有的16位,所以它被解释为65529

【讨论】:

  • 您能否进一步解释这是如何发生的?谢谢。
  • 当然我可以在答案中添加一点。
  • 是的,谢谢我对短部分的铸造有点不确定。但一分钟后,我意识到 (short) 实际上是 (short int)。现在一切对我来说都是有意义的。
【解决方案4】:

要了解原因,您需要知道 CPU 使用二进制补码表示有符号数(可能不是全部,但很多)。

    byte n = 1; //0000 0001 =  1
    n = ~n + 1; //1111 1110 + 0000 0001 = 1111 1111 = -1

此外,int 和 unsigned int 类型的大小可以根据您的 CPU 不同。在做这样的特定事情时:

   #include <stdint.h>
   int8_t ibyte;
   uint8_t ubyte;
   int16_t iword;
   //......

【讨论】:

    【解决方案5】:

    值 65529u 和 -7 的表示对于 16 位整数是相同的。只是位的解释不同。

    对于较大的整数和这些值,您需要签署扩展;一种方法是逻辑运算

    int y = (int )(x | 0xffff0000u); // assumes 16 to 32 extension, x is > 32767
    

    如果速度不是问题,或者处理器上的除法速度很快,

    int y = ((int ) (x * 65536u)) / 65536;
    

    乘法左移 16 位(再次假设 16 到 32 扩展),除法右移保持符号。

    【讨论】:

    • 这只适用于小端二进制补码系统。
    • 提问者的机器是二进制补码,他报告的值证明了这一点。 Endianess 与它无关。我在这里只使用 int 阀,而不是内存中的单个字节。
    【解决方案6】:

    您期望您的 int 类型是 16 位宽,在这种情况下您确实会得到一个负值。但很可能它是 32 位宽,所以带符号的 int 可以代表 65529 就好了。你可以通过打印sizeof(int)来检查。

    【讨论】:

      【解决方案7】:

      要回答上面评论中发布的问题 - 请尝试以下操作:

      unsigned short int x = 65529U;
      short int y = (short int)x;
      
      printf("%d\n", y);
      

      unsigned short int x = 65529U;
      short int y = 0;
      
      memcpy(&y, &x, sizeof(short int);
      printf("%d\n", y);
      

      【讨论】:

        【解决方案8】:

        由于转换无符号值用于表示正数,因此可以通过将最高有效位设置为 0 来完成转换。因此,程序不会将其解释为二进制补码值。一个警告是,这将丢失接近无符号类型最大值的数字的信息。

        template <typename TUnsigned, typename TSinged>
        TSinged UnsignedToSigned(TUnsigned val)
        {
            return val & ~(1 << ((sizeof(TUnsigned) * 8) - 1));
        }
        

        【讨论】:

          【解决方案9】:

          我知道这是一个老问题,但我认为响应者可能误解了它。我认为其目的是将作为无符号整数(技术上为unsigned short)接收的 16 位位序列转换为有符号整数。当您需要将从网络接收到的内容从网络字节顺序转换为主机字节顺序时,可能会发生这种情况(它最近发生在我身上)。在这种情况下,使用联合:

          unsigned short value_from_network;
          unsigned short host_val = ntohs(value_from_network);
          // Now suppose host_val is 65529.
          union SignedUnsigned {
            short          s_int;
            unsigned short us_int;
          };
          SignedUnsigned su;
          su.us_int = host_val;
          short minus_seven = su.s_int;
          

          现在minus_seven 的值为-7。

          【讨论】:

          • 不清楚 OP 需要 16 位,只是从 unsignedsigned 的转换没有按预期进行。除了在有符号类型中存储超出范围的无符号值始终是实现定义的行为之外,shortunsigned short 不需要为 16 位宽;它们必须最小为 16 位宽。如果您需要精确宽度,请使用精确宽度类型,例如 int16_tuint16_t。请注意,确切的宽度类型是可选类型,但在常见平台上仍受支持。
          猜你喜欢
          • 2012-08-13
          • 2017-07-21
          • 1970-01-01
          • 1970-01-01
          • 2011-06-14
          • 2013-09-01
          • 2014-05-02
          相关资源
          最近更新 更多