添加int和uint

我对以下示例中的C#编译器行为感到惊讶:

int i = 1024; uint x = 2048; x = x+i; // A error CS0266: Cannot implicitly convert type 'long' to 'uint' ... 

似乎可以,因为int + uint可以溢出。 但是,如果将uint更改为int ,则错误消失,如int + int不能给出溢出:

 int i = 1024; int x = 2048; x = x+i; // OK, int 

而且, uint + uint = uint

 uint i = 1024; uint x = 2048; x = x+i; // OK, uint 

这似乎完全模糊不清。

为什么int + int = intuint + uint = uint ,但int + uint = long

这个决定的动机是什么?

简短的回答是“因为标准说它应该如此”,见ISO 23270的信息 §14.2.5.2。 规范性 §13.1.2。 (隐式数字转换)说:

隐式数字转换是:

  • intlongfloatdoubledecimal
  • uintlongulongfloatdoubledecimal

intuintlongulongfloat以及从longulongdouble会导致精度损失,但绝不会导致数量级的损失。 其他隐式数字转换永远不会丢失任何信息。我的

[略]更长的答案是你要添加两种不同的类型:32位有符号整数和32位无符号整数:

  • 带符号的32位整数的域是-2,147,483,648(0x80000000) – +2,147,483,647(0x7FFFFFFF)。
  • 无符号32位整数的域是0(0x00000000) – +4,294,967,295(0xFFFFFFFF)。

因此类型不兼容,因为int不能包含任何任意uint并且uint不能包含任何任意int 。 它们被隐式转换( 扩展转换,根据§13.1.2的要求,没有信息丢失)到下一个可以包含两者的最大类型:在这种情况下,一个long整数,一个有符号的64位整数,它有域-9,223,372,036,854,775,808(0x8000000000000000) – + 9,223,372,036,854,775,807(0x7FFFFFFFFFFFFFFF)。

编辑注意:除此之外,执行此代码:

 var x = 1024 + 2048u ; Console.WriteLine( "'x' is an instance of `{0}`" , x.GetType().FullName ) ; 

并不像原始海报的例子那样long 。 相反,产生的是:

 'x' is an instance of `System.UInt32` 

这是因为不断折叠 。 表达式中的第一个元素1024没有后缀,因此是一个int ,表达式2048u的第二个元素是uint ,根据规则:

  • 如果文字没有后缀,则它具有这些类型中的第一个,其值可以表示为: intuintlongulong
  • 如果文字后缀为Uu ,则它具有这些类型中的第一个,其值可以表示为: uintulong

由于优化器知道值是什么,因此总和被预先计算并作为uint进行评估。

一致性是小脑袋的大人物。

为什么int + int = int和uint + uint = uint,但int + uint = long? 这个决定的动机是什么?

这个问题的措辞意味着设计团队希望 int + uint很长的预设,并选择类型规则来实现这个目标。 这个预设是错误的。

相反,设计团队认为:

  • 人们最有可能进行哪些数学运算?
  • 可以安全有效地执行哪些数学运算?
  • 可以在不损失幅度和精度的情况下执行数值类型之间的转换?
  • 如何使操作员解析的规则既简单又符合方法重载决策的规则?

以及许多其他考虑因素,例如设计是否适用于可调试,可维护,可版本化的程序等等。 (我注意到我没有参加这次特别的设计会议,因为它早于设计团队的时间。但我已经阅读了他们的笔记,并了解了在此期间设计团队会遇到的各种事情。)

调查这些问题导致了当前的设计:算术运算定义为int + int – > int,uint + uint – > uint,long + long – > long,int可以转换为long,uint可以转换长,等等。

这些决定的结果是,当添加uint + int时,重载决策选择long + long作为最接近的匹配,而long + long是long,因此uint + int是long。

使uint + int有一些更不同的行为,你可能认为更合理的不是团队的设计目标,因为混合有符号和无符号值是第一个,在实践中很少见,其次,几乎总是一个bug。 设计团队可以为有符号和无符号的一,二,四和八字节整数的每个组合添加特殊情况,以及char,float,double和decimal,或者那些数百个案例的任何子集,但是有效违背简单的目标。

简而言之,一方面,我们有大量的设计工作来制作一个function,我们希望没有人以大规模复杂的规格为代价实际使用更容易。 另一方面,我们有一个简单的规范,在我们希望在实践中没有人遇到的罕见情况下会产生exception行为。 鉴于这些选择,您会选择哪个? C#设计团队选择了后者。

这是数字类型的重载解析的表现

数字提升包括自动执行预定义一元和二元数字运算符的操作数的某些隐式转换。 数字提升不是一种独特的机制,而是将重载决策应用于预定义运算符的效果。 尽管可以实现用户定义的运算符以显示类似的效果,但数字促销特别不会影响用户定义的运算符的评估。

http://msdn.microsoft.com/en-us/library/aa691328(v=vs.71).aspx

如果你看看

 long operator *(long x, long y); uint operator *(uint x, uint y); 

从该链接,您可以看到这两个可能的重载(示例引用operator * ,但对于operator +也是如此)。

uint被隐式转换为long以进行重载解析,就像int

从uint到long,ulong,float,double或decimal。

从int到long,float,double或decimal。

http://msdn.microsoft.com/en-us/library/aa691282(v=vs.71).aspx

这个决定的动机是什么?

设计团队的成员可能会回答这个问题。 Eric Lippert,你在哪里? :-)请注意,@ Nicolas的推理下面的推理非常合理,两个操作数都转换为“最小”类型,可以包含每个操作数的全部值。

我认为编译器的行为非常符合逻辑和预期。

在以下代码中:

 int i; int j; var k = i + j; 

这个操作有一个确切的重载,所以kint 。 添加两个uint ,两个byte或者你有什么相同的逻辑适用。 编译器的工作很简单,很高兴,因为重载决策找到了完全匹配。 编写此代码的人很有可能希望k成为一个int ,并且知道在某些情况下操作可能会溢出。

现在考虑一下你要问的案例:

 uint i; int j; var k = i + j; 

编译器看到了什么? 那么它会看到一个没有匹配过载的操作; 没有运算符+重载将intuint作为其两个操作数。 因此,重载决策算法继续进行并尝试查找可能有效的运算符重载。 这意味着它必须找到一个重载,其中涉及的类型可以“保持”原始操作数; 也就是说, ij都必须可以隐式转换为所述类型。

编译器无法将uint隐式转换为int因为这种转换不会存在。 它无法隐式地将int转换为uint ,因为该转换也不存在(两者都可能导致幅度发生变化)。 所以它唯一的选择就是选择第一个能够“保持”两种操作数类型的更广泛的类型,在这种情况下是long 。 一旦两个操作数被隐式转换为long k就很long很明显。

这种行为的动机是,IMO,选择最安全的可用选项而不是第二次猜测可疑编码器的意图。 编译器无法对编写此代码的人期望k是什么做出有根据的猜测。 一个int ? 那么,为什么不是一个uint ? 两种选择看起来同样糟糕。 编译器选择唯一的逻辑路径; 安全的: long 。 如果编码器希望kintunit他只需显式地转换其中一个操作数。

最后,但并非最不重要的是,C#编译器的重载决策算法在决定最佳过载时不考虑返回类型。 因此,将操作结果存储在uint这一事实与编译器完全无关,并且在重载解析过程中无任何影响。

这是我的所有猜测,我可能完全错了。 但这似乎是合乎逻辑的推理。

 i int i = 1024; uint x = 2048; // Technique #1 x = x + Convert.ToUInt32(i); // Technique #2 x = x + checked((uint)i); // Technique #3 x = x + unchecked((uint) i); // Technique #4 x = x + (uint)i; 

C#的数字提升规则基于Java和C的数字提升规则,它们通过识别两个操作数可以转换的类型然后使结果成为相同类型来工作。 我认为这种方法在20世纪80年代是合理的,但是新的语言应该把它放在一边,看看如何使用值(例如,如果我正在设计一种语言,那么给出Int32 i1,i2,i3; Int64 l;编译器将处理i4=i1+i2+i3;使用32位数学[在溢出的情况下抛出exception]将处理l=i1+i2+i3;使用64位数学。)但C#规则是什么他们似乎并且似乎不太可能改变。

应该注意的是,根据定义,C#促销规则总是选择被语言规范认为“最合适”的重载,但这并不意味着它们确实最适合任何有用的目的。 例如, double f=1111111100/11111111.0f; 看起来它应该产生100.0,并且如果两个操作数都被提升为double ,它将被正确计算,但编译器将转换整数1111111100转换为float产生1111111040.0f,然后执行除法产生99.999992370605469。