为啥减去无符号和有符号后符号不同?
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 int
而d
是一个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,不会大很多:-)以上是关于为啥减去无符号和有符号后符号不同?的主要内容,如果未能解决你的问题,请参考以下文章