为什么在减去无符号和签名后符号会有所不同?

Eug*_*bet 41 c++ unsigned negative-number language-lawyer

unsigned int t = 10;
int d = 16;
float c = t - d;
int e = t - d;
Run Code Online (Sandbox Code Playgroud)

为什么c积极但e负面的价值?

Rei*_*ica 64

让我们从分析结果开始t - d.

t是一个unsigned int同时d是一个int,这样做对他们的算术值d转换为unsigned int(C++规则说无符号送过来偏好).所以我们得到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:

如果目标类型已签名,则该值如果可以在目标类型中表示,则不会更改; 否则,该值是实现定义的.

  • @ user1810087这意味着编译器必须记录将要发生的事情,这意味着编译器作者必须考虑将要发生的事情,并一致地实现它(或至少将其与实现一致地记录).他们的决定很可能会受到目标硬件的影响,因此这个文档将特定于平台. (4认同)
  • 它不是_behavior_,它是实现定义的 - 只有值(参见你自己的脚注).特别是,由于这种转换,实现不能抛出异常,引发信号等. (3认同)

Seb*_*edl 15

首先,你必须理解"通常的算术转换"(该链接用于C,但规则在C++中是相同的).在C++中,如果你使用混合类型进行算术运算(顺便说一下,你应该尽可能避免使用),那么就有一套规则可以决定计算的类型.

在您的情况下,您从unsigned int中减去signed int.促销规则说实际计算是使用unsigned int.

所以你的计算是10 - 16无符号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.

但所有这一切都是重复第一点的很长的方式,即"不做混合型算术".首先明确地将类型转换为您知道会做正确事情的类型.