【问题标题】:Why is ushort + ushort equal to int?为什么 ushort + ushort 等于 int?
【发布时间】:2012-04-21 08:36:12
【问题描述】:

之前今天我试图添加两个 ushort,我注意到我必须将结果转换回 ushort。我认为它可能已经变成了一个 uint(以防止可能的意外溢出?),但令我惊讶的是它是一个 int (System.Int32)。

这是有什么巧妙的理由,还是因为 int 被视为“基本”整数类型?

例子:

ushort a = 1;
ushort b = 2;

ushort c = a + b; // <- "Cannot implicitly convert type 'int' to 'ushort'. An explicit conversion exists (are you missing a cast?)"
uint d = a + b; // <- "Cannot implicitly convert type 'int' to 'uint'. An explicit conversion exists (are you missing a cast?)"

int e = a + b; // <- Works!

编辑:就像 GregS 的回答所说,C# 规范说两个操作数(在本例中为“a”和“b”)都应转换为 int。我对为什么这是规范的一部分的根本原因很感兴趣:为什么 C# 规范不允许直接对 ushort 值进行操作?

【问题讨论】:

标签: c# types integer-arithmetic


【解决方案1】:
ushort x = 5, y = 12;

下面的赋值语句会产生编译错误,因为赋值运算符右边的算术表达式默认为int

ushort z = x + y;   // Error: conversion from int to ushort

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

编辑:
在对 ushort 进行算术运算的情况下,操作数被转换为可以保存所有值的类型。这样就可以避免溢出。操作数可以按 int、uint、long 和 ulong 的顺序变化。 请参阅C# Language Specification 在本文档中转到第 4.1.5 节整数类型(word 文档中的第 80 页左右)。在这里你会发现:

对于二进制 +、–、*、/、%、&、^、|、==、!=、>、= 和 可以完全代表所有可能的int、uint、long和ulong 两个操作数的值。然后使用 T 类型的精度,结果的类型是 T(或 bool 关系运算符)。一个操作数是不允许的 使用二元运算符键入 long,另一个为 ulong 类型。

Eric Lipper 在question 中表示

算术永远不会在 C# 中完成。算术可以在 ints、uints、longs 和 ulongs,但算术永远不会在 shorts 中完成。 短裤提升为 int 并且算术在 int 中完成,因为像 我之前说过,绝大多数算术计算都适合 一个整数。绝大多数不适合做空。短算术是 在为整数优化的现代硬件上可能更慢,并且 短算术不会占用更少的空间;这将是 在芯片上以整数或长整数形式完成。

【讨论】:

  • 这仍然无法解释为什么它会变成 int 而不是 uint。
  • @lesderid,这是由于 c# 规范(7.8.4,第 192 页)中定义的运算符 + 重载优先级。在此第一个重载签名是 int operator+(int x, int y),然后是它的 uint operator+(uint x, uint y)。所以这就是为什么它变成 int 而不是 uint
【解决方案2】:

来自 C# 语言规范:

7.3.6.2 二进制数字提升 二进制数字提升发生在预定义的 +、–、*、/、%、&、|、^、==、!=、>、= 和

· 如果任一操作数为十进制类型,则将另一个操作数转换为十进制类型,或者如果另一个操作数为浮点或双精度类型,则会发生绑定时错误。

· 否则,如果其中一个操作数是 double 类型,则另一个操作数将转换为 double 类型。

· 否则,如果任一操作数为浮点类型,则将另一个操作数转换为浮点类型。

· 否则,如果其中一个操作数是 ulong 类型,则另一个操作数将转换为 ulong 类型,或者如果另一个操作数是 sbyte、short、int 或 long 类型,则会发生绑定时错误。

· 否则,如果其中一个操作数是 long 类型,则将另一个操作数转换为 long 类型。

· 否则,如果任一操作数为 uint 类型,而另一个操作数为 sbyte、short 或 int 类型,则这两个操作数都将转换为 long 类型。

· 否则,如果任一操作数为 uint 类型,则将另一个操作数转换为 uint 类型。

· 否则,两个操作数都转换为 int 类型。

【讨论】:

  • 这确实证实了我的发现,但并不能真正解释为什么会发生。
  • @lesderid:我不明白。发生这种情况是因为您使用的是符合 C# 规范的编译器。也许你在问第 7.3.6.2 节背后的原因,以及为什么规则停止在 int 并且不继续使用更小的类型?
  • 嗯,是的,我对为什么规范说两个操作数都应该转换为 int 很感兴趣。我只是添加了编译器错误来澄清,因为我不确定这是一个规范还是一个实现决定。
  • 我可以猜到,但我真的不知道答案,所以你应该编辑你的问题,问为什么语言规则是这样的。
【解决方案3】:

没有理由这样做。这只是一个效果或应用重载决议的规则,该规则声明其参数的第一个重载存在适合参数的隐式转换,将使用该重载。

这在 C# 规范第 7.3.6 节中说明如下:

数值提升不是一种独特的机制,而是将重载决议应用于预定义运算符的效果。

继续举例说明:

作为数值提升的示例,考虑二进制 * 运算符的预定义实现:

int 运算符 *(int x, int y);

uint 运算符 *(uint x, uint y);

长运算符 *(long x, long y);

ulong 运算符 *(ulong x, ulong y);

浮点运算符 *(float x, float y);

双运算符 *(double x, double y);

十进制运算符 *(十进制 x, 十进制 y);

当重载决策规则(第 7.5.3 节)应用于这组运算符时,效果是从操作数类型中选择存在隐式转换的第一个运算符。例如,对于 b * s 操作,其中 b 是一个字节,s 是一个短字节,重载决策选择 operator *(int, int) 作为最佳运算符。

【讨论】:

    【解决方案4】:

    你的问题其实有点棘手。这个规范成为语言一部分的原因是......因为他们在创建语言时做出了这个决定。我知道这听起来令人失望,但事实就是如此。


    但是,真正的答案可能涉及到 1999-2000 年的许多上下文决策。我相信制作 C# 的团队对所有这些语言细节进行了激烈的辩论。

    • ...
    • C# 旨在成为一种简单、现代、通用、面向对象的编程语言。
    • 源代码可移植性非常重要,程序员的可移植性也很重要,尤其是对于那些已经熟悉 C 和 C++ 的程序员。
    • 对国际化的支持非常重要。
    • ...

    The quote above is from Wikipedia C#

    所有这些设计目标都可能影响了他们的决定。例如,在 2000 年,大多数系统已经是原生的 32 位,因此他们可能决定限制比这更小的变量的数量,因为在执行算术运算时无论如何都会在 32 位上进行转换。这通常较慢。

    那时,你可能会问我;如果这些类型存在隐式转换,为什么它们仍然包含它们?如上所述,他们的设计目标之一就是便携性。

    因此,如果您需要围绕旧的 C 或 C++ 程序编写 C# 包装器,您可能需要这些类型来存储一些值。在这种情况下,这些类型非常方便。

    这是 Java 没有做出的决定。例如,如果您编写一个与 C++ 程序交互的 Java 程序,以这种方式接收 ushort 值,那么 Java 只有 short(已签名),因此您不能轻易地将一个分配给另一个并期望正确的值。

    我敢打赌,在 Java 中可以接收此类值的下一个可用类型是 int(当然是 32 位)。你刚刚在这里把你的记忆加倍了。这可能没什么大不了的,但您必须实例化一个包含 100 000 个元素的数组。

    事实上,我们必须记住,这些决定是通过回顾过去和未来做出的,以便提供从一个到另一个的平稳转移。

    但现在我觉得我与最初的问题有分歧。


    所以你的问题很好,希望我能给你带来一些答案,即使我知道这可能不是你想听到的。

    如果您愿意,您甚至可以阅读有关 C# 规范的更多信息,链接如下。有一些有趣的文档可能会让您感兴趣。

    Integral types

    The checked and unchecked operators

    Implicit Numeric Conversions Table

    顺便说一句,我相信你应该奖励habib-osu,因为他通过正确的链接为最初的问题提供了一个相当好的答案。 :)

    问候

    【讨论】:

      【解决方案5】:

      简单而正确的答案是“因为 C# 语言规范是这么说的”。

      显然你对这个答案不满意,想知道“为什么这么说”。您正在寻找“可靠和/或官方来源”,这会有点困难。这些设计决策是很久以前做出的,13 年是软件工程中很多狗的生活。它们是由 Eric Lippert 所说的“老前辈”制作的,他们已经转向更大更好的东西,并且不会在此处发布答案以提供官方来源。

      然而,它可以被推断出来,只是冒着可信的风险。任何托管编译器,如 C# 的,都有为 .NET 虚拟机生成代码所需的约束。 CLI 规范中详细描述了这些规则(并且非常易读)。它是 Ecma-335 规范,您可以免费下载它from here

      转到第 III 部分,第 3.1 和 3.2 章。它们描述了可用于执行加法的两条 IL 指令,addadd.ovf。单击表 2“二进制数值运算”的链接,它描述了这些 IL 指令允许的操作数。请注意,此处仅列出了几种类型。 byte 和 short 以及所有无符号类型都丢失了。只允许使用 int、long、IntPtr 和浮点(float 和 double)。例如,对于用 x 标记的附加约束,您不能将 int 添加到 long 中。这些限制并不是完全人为的,它们是基于你可以在可用硬件上合理高效地做的事情。

      任何托管编译器都必须处理此问题才能生成有效的 IL。这并不难,只需将 ushort 转换为表中较大的值类型,这种转换始终有效。 C# 编译器选择 int,这是表中出现的下一个较大的类型。或者一般来说,将任何操作数转换为下一个最大值类型,使它们具有相同的类型并满足表中的约束。

      然而,现在有一个新问题,一个让 C# 程序员非常疯狂的问题。添加的结果是提升类型。在你的情况下,这将是 int。因此,添加两个 ushort 值,例如 0x9000 和 0x9000 具有完全有效的 int 结果:0x12000。问题是:这是一个不适合 ushort 的值。值溢出。但它没有在 IL 计算中溢出,它仅在编译器试图将其塞回 ushort 时才会溢出。 0x12000 被截断为 0x2000。一个令人眼花缭乱的不同值,仅当您用 2 根或 16 根手指而不是 10 根手指数数时才有意义。

      值得注意的是 add.ovf 指令不处理这个问题。它是用于自动生成溢出异常的指令。但事实并非如此,转换后的整数的实际计算没有溢出。

      这是真正的设计决策发挥作用的地方。老前辈显然认为简单地将 int 结果截断为 ushort 是一个错误工厂。必然是。他们决定您必须承认您知道添加可能会溢出,并且如果它发生也没关系。他们把它变成你的问题,主要是因为他们不知道如何把它变成他们的问题并且仍然生成高效的代码。你必须投。是的,这太让人抓狂了,我敢肯定你也不想要这个问题。

      值得注意的是,VB.NET 设计者对该问题采取了不同的解决方案。他们实际上把它他们的问题,并没有推卸责任。您可以添加两个 UShort 并将其分配给一个 UShort 而无需强制转换。不同之处在于 VB.NET 编译器实际上会生成 extra IL 来检查溢出情况。这不是便宜的代码,每一个简短的添加都会慢大约 3 倍。但除此之外,这解释了为什么 Microsoft 维护 两种 语言具有非常相似的功能。

      长话短说:您付出了代价,因为您使用的类型与现代 CPU 架构不太匹配。这本身就是使用 uint 而不是 ushort 的一个非常好的理由。从 ushort 中获得牵引力是很困难的,在操纵它们的成本超过内存节省之前,你需要很多它们。不仅仅是因为有限的 CLI 规范,x86 内核需要额外的 cpu 周期来加载 16 位值,因为机器代码中的操作数前缀字节。实际上不确定今天是否仍然如此,当我仍然注意计算周期时,它曾经回来过。一年前的狗。


      请注意,通过让 C# 编译器生成与 VB.NET 编译器生成的代码相同的代码,您可以对这些丑陋和危险的转换感觉更好。所以当演员被证明是不明智的时,你会得到一个 OverflowException。使用项目>属性>构建选项卡>高级按钮>勾选“检查算术上溢/下溢”复选框。仅用于调试版本。为什么项目模板没有自动打开此复选框是另一个非常神秘的问题,顺便说一句,这是很久以前做出的决定。

      【讨论】:

      • "这并不难,只需将 ushort 转换为 uint,这种转换始终有效。"确实,这也是我的想法,但它不会转换为 uint,而是转换为 int,目前还没有人能够真正解释。不过,这是一个非常好的答案!
      • 是的,我弄错了,修复了。看看我指的那个表,它没有无符号类型。
      • 啊,我明白了。我想这回答了我的问题!不过,我要奖励 Habib.OSU,因为他在我编辑之前完全回答了原始问题。
      • 优秀的阅读;你当然值得那个复选标记!我想这是为什么过早优化(使用 ushort 而不仅仅是 uint)是邪恶的另一种情况。
      • 出色的答案,我喜欢 StackOverflow 专家提供的那种细节。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-29
      • 1970-01-01
      相关资源
      最近更新 更多