为啥减去无符号和有符号后符号不同?

Posted

技术标签:

【中文标题】为啥减去无符号和有符号后符号不同?【英文标题】:Why is the sign different after subtracting unsigned and signed?为什么减去无符号和有符号后符号不同? 【发布时间】:2019-03-16 04:59:43 【问题描述】:
unsigned int t = 10;
int d = 16;
float c = t - d;
int e = t - d;

为什么c的值为正,而e的值为负?

【问题讨论】:

如果您将 signed 值隐式转换为 unsigned 或反之亦然,大多数编译器都有警告您的选项。在 GCC 和 Clang 上,这是 -Wconversion。你的问题是一个很好的例子,说明为什么用它来编译是个好主意。 如果您显示您看到的实际值会有所帮助。 【参考方案1】:

我们先来分析t - d的结果。

t 是一个unsigned intd 是一个int,所以要对它们进行算术运算,d 的值将转换为unsigned int(C++ 规则说 unsigned 在此处获得优先权) .所以我们得到10u - 16u,它(假设是32位int)环绕到4294967290u

然后,此值在第一个声明中转换为 float,在第二个声明中转换为 int

假设float(32位单精度IEEE)的典型实现,它的最高可表示值大约是1e38,所以4294967290u正好在这个范围内。会有舍入误差,但转成float不会溢出。

对于int,情况有所不同。 4294967290u 太大而无法放入 int,因此会发生回绕,我们返回值 -6。请注意,标准不保证这种环绕:这种情况下的结果值是实现定义的(1),这意味着结果值是什么取决于编译器,但它必须记录在案。


(1) C++17 (N4659), [conv.integral] 7.8/3:

如果目标类型是有符号的,如果可以在目标类型中表示,则值不变; 否则,该值是实现定义的。

【讨论】:

只是好奇,但在这种情况下 implementation-defined 是否意味着与硬件相关?因为,最常见的硬件是二进制补码,所以你得到了这种行为,但即使使用相同的编译器,它也会在一个补码 CPU 上发生变化? @user1810087 这意味着编译器必须记录会发生什么,这意味着编译器作者必须考虑会发生什么,并一致地实现它(或至少与实现一致地记录它)。他们的决定很可能会受到目标硬件的影响,因此这方面的文档将是特定于平台的。 这不是 behavior 是实现定义的——只有值(见你自己的脚注)。特别是,由于这种转换,实现不能抛出异常、引发信号等。【参考方案2】:

首先,您必须了解"usual arithmetic conversions"(该链接适用于 C,但在 C++ 中的规则相同)。在 C++ 中,如果您使用混合类型进行算术运算(顺便说一句,您应该尽可能避免这种情况),有一组规则可以决定使用哪种类型进行计算。

在你的情况下,你是从一个无符号整数中减去一个有符号整数。促销规则说实际计算是使用unsigned int完成的。

所以你的计算是 10 - 16 在 unsigned int 算术中。无符号算术是模算术,这意味着它环绕。所以,假设你的典型 32 位 int,这个计算的结果是 2^32 - 6。

这对两条线都是一样的。请注意,减法完全独立于赋值;左侧的类型对计算的发生方式绝对没有影响。认为左侧的类型会以某种方式影响计算是初学者的常见错误。但float f = 5 / 6 为零,因为除法仍然使用整数运算。

因此,不同之处在于分配期间发生的情况。减法的结果在一种情况下被隐式转换为float,在另一种情况下被隐式转换为int

到 float 的转换会尝试找到与该类型可以表示的实际值最接近的值。这将是一些非常大的值;但与原始减法产生的结果并不完全一致。

转换为int表示如果值适合int的范围,则该值将保持不变。但是 2^32 - 6 远大于 32 位 int 可以容纳的 2^31 - 1,所以你得到了转换规则的另一部分,它表示结果值是实现定义的。这是标准中的一个术语,意思是“不同的编译器可以做不同的事情,但他们必须记录他们所做的事情”。

出于所有实际目的,您可能会遇到的所有编译器都说位模式保持不变,只是被解释为有符号。由于 2 的补码算法的工作方式(几乎所有计算机都表示负数的方式),结果是您期望从计算中得到的 -6。

但这一切都是重复第一点的一个很长的路要走,即“不要做混合类型算术”。首先明确地将类型转换为您知道会做正确事情的类型。

【讨论】:

我认为您的意思是 2^31-1 是第 7 段中的第二个值。因为从表面上看,“2^32 - 6 远大于 2^32 - 1” , 似乎是一件显而易见的事情。大多数人会认为第一个正好比第二个小 5,不会大很多:-)

以上是关于为啥减去无符号和有符号后符号不同?的主要内容,如果未能解决你的问题,请参考以下文章

无符号整型和有符号整形转换

无符号整型和有符号整型的区别,以及无符号整型的使用

一元减号和有符号到无符号的转换

整数的无符号编码和有符号编码

VHDL深度讲解无符号和有符号加法处理溢出的问题

整数编码