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

时间:2018-10-11 07:23:27

标签: c++ language-lawyer unsigned negative-number

unsigned int t = 10;
int d = 16;
float c = t - d;
int e = t - d;

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

2 个答案:

答案 0 :(得分:64)

让我们开始分析t - d的结果。

tunsigned int,而dint,因此要对它们进行算术运算,将d的值转换为{{ 1}}(C ++规则说unsigned在这里得到优先选择)。这样我们得到unsigned int,它(假设32位10u - 16u)环绕到int

然后,该值在第一个声明中转换为4294967290u,在第二个声明中转换为float

假设int(32位单精度IEEE)的典型实现,其最高可表示值大约为float,因此1e38处于该范围内。会有舍入错误,但是到float的转换不会溢出。

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


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

  

如果目标类型是带符号的,则该值可以用目标类型表示,则保持不变;   否则,该值是实现定义的。

答案 1 :(得分:15)

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

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

因此,按照无符号int算术,您的计算结果为10 - 16。无符号算术是模算术,这意味着它可以环绕。因此,假设您是典型的32位整数,则此计算的结果为2 ^ 32-6。

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

那么,区别就是分配过程中发生的事情。减法的结果在一种情况下隐式转换为float,在另一种情况下隐式转换为int

向float的转换尝试找到与该类型可以表示的实际值最接近的值。这将是一个非常大的价值;虽然不完全是原始减法得出的结果。

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

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

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