unsigned int t = 10;
int d = 16;
float c = t - d;
int e = t - d;
为什么c
的值为正而e
的值为负?
答案 0 :(得分:64)
让我们开始分析t - d
的结果。
t
是unsigned int
,而d
是int
,因此要对它们进行算术运算,将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。
但是所有这一切都是重复第一点的很长的路,这就是“不要做混合类型算术”。首先,将类型明确转换为您知道将做正确的事情的类型。