字节+字节=整数…为什么?
看这个C#代码:
byte x = 1; byte y = 2; byte z = x + y; // ERROR: Cannot implicitly convert type 'int' to 'byte'
在byte
(或short
)类型上执行的任何数学运算的结果都被隐式地转换回整数。 解决方案是显式地将结果转换回一个字节:
byte z = (byte)(x + y); // this works
我想知道为什么? 它是建筑吗? 哲学?
我们有:
-
int
+int
=int
-
long
+long
=long
-
float
+float
=float
-
double
+double
=double
那为什么不呢:
-
byte
+byte
=byte
-
short
+short
=short
?
有一点背景:我正在执行一个关于“小数字”(即<8)的长长的计算列表,并将中间结果存储在一个大数组中。 使用一个字节数组 (而不是一个int数组) 更快 (因为缓存命中)。 但是通过代码传播的大量字节数据使得它更难读。
代码片段的第三行:
byte z = x + y;
实际上是指
byte z = (int) x + (int) y;
所以,对字节没有+操作,字节先被转换为整数,两个整数相加的结果是一个(32位)整数。
就“为什么会发生这一切”而言,这是因为没有任何由C#定义的运算符,用于像字节,字节,短字或ushort这样的算术,正如其他人所说的那样。 这个答案是关于为什么这些运营商没有定义。
我相信这基本上是为了表现。 处理器具有本地操作,可以非常快速地进行32位运算。 自动完成从结果到字节的转换可以完成,但在实际上不需要这种行为的情况下会导致性能损失。
我认为这是在注释的C#标准之一中提到的。 看着…
编辑:恼人的是,我现在已经通过注释的ECMA C#2规范,注释的MS C#3规范和注释CLI规范, 没有一个提到这一点,据我所知。 我确信我已经看到了上面给出的原因,但是如果我知道在哪里,我会被打击。 道歉,参考粉丝:(
我以前在什么地方见过这个 从这篇文章,旧的新事物 :
假设我们生活在一个幻想的世界里,对“字节”的操作导致了“字节”。
byte b = 32; byte c = 240; int i = b + c; // what is i?
在这个幻想的世界里,我的价值将是16! 为什么? 因为+运算符的两个操作数都是字节,所以总和“b + c”被计算为一个字节,由于整数溢出,结果为16。 (如前所述,整数溢出是新的安全攻击向量。)
编辑 :雷蒙德是捍卫,本质上,方法C和C + +本来。 在评论中,他捍卫C#采取同样的方式,基于语言向后兼容的事实。
C#
ECMA-334指出,只有在int + int,uint + uint,long + long和ulong + ulong(ECMA-334 14.7.4)中,才会将添加定义为合法。 因此,这些是14.4.2中要考虑的候选操作。 由于存在从字节到int,uint,long和ulong的隐式转换,因此所有的附加函数成员都是14.4.2.1下的可用函数成员。 我们必须找到14.4.2.3中规则所隐含的最好的内隐:
铸造(C1)到int(T1)优于铸造(C2)到uint(T2)或ulong(T2),因为:
- 如果T1是int,T2是uint或ulong,则C1是更好的转换。
将(C1)投射到int(T1)要比将(C2)投射到long(T2)要好,因为存在从int到long的隐式投射:
- 如果存在从T1到T2的隐式转换,并且不存在从T2到T1的隐式转换,则C1是更好的转换。
因此使用int + int函数,它返回一个int。
在C#规范中,它埋藏得非常深,这是一个很长的路要走。
CLI
CLI仅在6种类型(int32,native int,int64,F,O和&)上运行。 (ECMA-335分区3第1.5节)
字节(int8)不是这些类型之一,并且在添加之前被自动强制为一个int32。 (ECMA-335分区3第1.6节)
指示某些低效率添加字节并将结果截断为一个字节的答案是不正确的。 x86处理器具有专门针对8位量的整数运算而设计的指令。
实际上,对于x86 / 64处理器,由于操作数前缀字节必须被解码,因此执行32位或16位操作的效率低于64位或8位操作。 在32位机器上,执行16位操作需要相同的惩罚,但是仍然存在专用于8位操作的操作码。
许多RISC体系结构具有相似的本地字/字节有效指令。 那些通常不具有存储和转换为有符号值的位长的数据。
换句话说,这个决定一定是基于对字节类型的理解,而不是由于硬件的低效率。
我记得曾经从Jon Skeet读了一些东西(现在找不到,我会继续看),关于字节如何实际上不会超载+运算符。 实际上,当像样本中那样添加两个字节时,每个字节实际上都被隐式转换为一个int。 这个结果显然是一个整数。 至于为什么这是这样设计的,我会等着Jon Skeet自己发帖:)
编辑:找到它! 关于这个话题的伟大的信息在这里 。
这是因为溢出和携带。
如果添加两个8位数字,则可能会溢出到第9位。
例:
1111 1111 + 0000 0001 ----------- 1 0000 0000
我不太确定,但是我认为整体, longs
和doubles
都有更多的空间,因为它们相当大。 此外,由于内部数据总线的宽度为4字节或32位(现在64位现在变得更普遍),所以它们是4的倍数,这对计算机处理更有效。 字节和短小有点低效,但是可以节省空间。
从C#语言规范1.6.7.5 7.2.6.2二进制数字促销它将两个操作数转换为int,如果它不适合其他几个类别。 我的猜测是他们没有重载+运算符将字节作为参数,但希望它有点正常,所以他们只是使用int数据类型。
C#语言规范
我怀疑C#实际上是调用int
定义的operator+
(除非你在一个checked
块中,否则返回一个int
),并隐式地将你的两个bytes
/ shorts
ints
为ints
。 这就是为什么行为看起来不一致的原因。
这可能是语言设计者的实际决定。 毕竟,int是一个Int32,一个32位的有符号整数。 每当你对小于int的类型进行整型操作时,无论如何,它将被大多数32位CPU转换为32位有符号整数。 那么,再加上小整数溢出的可能性,可能会把交易密封起来。 它可以帮助你避免连续检查溢出/溢出的情况,并且当字节表达式的最终结果在范围内时,尽管事实上在某个中间阶段它会超出范围,你会得到一个正确的结果。
另一个想法是:这些类型的溢出/溢出将不得不被模拟,因为它不会在最可能的目标CPU上自然发生。 何必?
这在很大程度上是关于这个话题的答案, 在这里首先提交一个类似的问题。
整数小于Int32的所有操作默认在计算之前四舍五入为32位。 Int32的结果之所以简单,是因为它是在计算之后。 如果您检查MSIL算术操作码,它们操作的唯一整数数字类型是Int32和Int64。 这是“设计”。
如果您希望以Int16格式返回结果,那么如果您在代码中执行强制转换或者编译器(低调地)在“引擎盖下”发出转换,则无关紧要。
例如,要做Int16算术:
short a = 2, b = 3; short c = (short) (a + b);
这两个数字将扩展到32位,得到添加,然后截断回到16位,这就是MS的意图。
使用短(或字节)的好处主要是存储海量数据(图形数据,流媒体等)
我认为这是一个关于哪个操作更常见的设计决定…如果字节+字节=字节可能会有更多的人不得不投入int,当一个int是必需的结果。
加法没有为字节定义。 所以他们被投到int为补充。 对于大多数数学运算和字节来说这是真的。 (请注意,这是以前的语言,我假设它今天是真实的)。
从.NET Framework代码:
// bytes private static object AddByte(byte Left, byte Right) { short num = (short) (Left + Right); if (num > 0xff) { return num; } return (byte) num; } // shorts (int16) private static object AddInt16(short Left, short Right) { int num = Left + Right; if ((num <= 0x7fff) && (num >= -32768)) { return (short) num; } return num; }
简化与.NET 3.5及以上:
public static class Extensions { public static byte Add(this byte a, byte b) { return (byte)(a + b); } }
现在你可以这样做:
byte a = 1, b = 2, c; c = a.Add(b);
除了所有其他的好评之外,我还想添加一点点珍闻。 很多评论都想知道为什么int,long和几乎任何其他的数字类型都不遵循这个规则…返回一个“更大”的类型来回应算术。
很多答案都与性能有关(32位比8位更快)。 在现实中,一个8位数字仍然是一个32位的32位CPU …即使你添加两个字节,CPU的数据块将是32位,无论… …所以添加整数不会比添加两个字节更“快”,这一切都与CPU相同。 现在,添加两个整数将比在32位处理器上增加两个长度更快,因为增加两个长整数需要更多的微操作,因为您处理的数字比处理器单词更宽。
我认为引起字节算术得出整数的根本原因是非常清晰直截了当的:8bits只是不会走得太远! :D有8位,你有一个0-255的无符号范围。 这不是一个很大的工作空间…在算术中使用它们的可能性将会遇到字节限制。 但是,在使用整数,长整数或者双精度等等时,你将会耗尽精力的可能性要低得多,因为我们很少遇到需要更多的东西。
从字节到整数的自动转换是合乎逻辑的,因为一个字节的比例很小。 从int到long,float到double等的自动转换是不合逻辑的,因为这些数字具有显着的规模。