byte + byte = int ... 为啥?
Posted
技术标签:
【中文标题】byte + byte = int ... 为啥?【英文标题】:byte + byte = int... why?byte + byte = int ... 为什么? 【发布时间】:2010-10-30 19:21:26 【问题描述】:看看这段 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
?
一点背景知识:我正在对“小数”(即 byte 数组(而不是 int 数组)更快(因为缓存命中)。但是通过代码传播的大量字节转换使其更加难以阅读。
【问题讨论】:
***.com/questions/927391/… 在这里有用的不是 Eric 对 标准 的了解——而是他对语言的设计 的了解;什么不是为什么。但是,是的,埃里克的回答会非常明确:) 下面的各种想法是对设计考虑的合理近似。更笼统地说:我不认为字节是“数字”;我认为它们是位模式,可以解释为数字、字符、颜色等。如果您要对它们进行数学运算并将它们视为数字,那么将结果转换为更普遍解释为数字的数据类型是有意义的。 @Eric:这对于字节来说很有意义,但对于 short/ushort 来说可能就没那么有意义了。 @Eric:byte1 | byte2
根本没有将它们视为数字。这将它们精确地视为位模式。我理解你的观点,但碰巧的是,每次我在 C# 中对字节进行任何算术运算时,我实际上都是将它们视为位,而不是数字,而且这种行为总是会阻碍。
【参考方案1】:
你的代码第三行sn -p:
byte z = x + y;
其实就是
byte z = (int) x + (int) y;
因此,对字节没有 + 操作,字节首先转换为整数,两个整数相加的结果是一个(32 位)整数。
【讨论】:
我已经尝试了下面的代码,但仍然无法正常工作。字节 z = (byte)x + (byte)y; 那是因为字节没有 + 操作(见上文)。试试 byte z = (byte)( (int) x + (int) y) 这一定是最正确、最简洁的答案。字节之间没有要添加的操作数,因此不是解释为什么“添加两个字节”有效或无效(它从未发生过),这清楚地说明了为什么结果是一个 int,因为唯一能发生是增加 2 个整数。 我在阅读所有其他答案时感到头晕(没有冒犯 Jon Skeet 先生)。我发现这是最简单的答案,可以正确描述引擎盖下发生的事情。谢谢! 这是我在别处写的一个答案,其中包含一个程序,用于识别何时发生这种编译器驱动的自动升级到int
:***.com/a/43578929/4561887【参考方案2】:
就“为什么会发生”而言,这是因为 C# 没有为算术定义任何运算符,如其他人所说的那样,字节、sbyte、short 或 ushort。这个答案是关于为什么这些运算符没有定义。
我相信这基本上是为了性能。处理器具有本机操作,可以非常快速地使用 32 位进行算术运算。自动将结果转换回字节可以完成,但在您实际上不想要这种行为的情况下会导致性能损失。
我认为这是在带注释的 C# 标准之一中提到的。看着...
编辑:烦人的是,我现在浏览了带注释的 ECMA C# 2 规范、带注释的 MS C# 3 规范和注释 CLI 规范,并且 none 尽我所能看。我确定我已经看到了上面给出的原因,但是如果我知道在哪里,我会感到震惊。道歉,参考粉丝:(
【讨论】:
很抱歉这么说,但我发现这不是最佳答案。 您是否对每个您认为不是最好的答案都投了反对票? ;) (澄清一下,我并没有真正对你有意见。似乎每个人都有自己的否决标准,这没关系。如果我认为答案是积极的,我只会投反对票有害而不仅仅是不理想。) 我将投票作为一种工具来获得“最佳”答案。实际上,我发现您在回答中根本没有说太多,这是我投反对票的主要原因。另一个原因可能是我的主观感觉,即您的代表在投票时给了您很大的奖励,并且您获得了“更好”的答案。 IMO 获得“最佳”答案的最佳方式是投票。老实说,我认为这里信息最丰富的答案是 Eric 在问题中的评论......但除此之外,对于设计角度(与“编译器在做什么”的角度相反)我不认为那里 是超越“性能”的答案。特别是,我真的不赞成“它防止溢出”的论点(17 票),因为这表明 int+int = long。【参考方案3】:我以为我以前在某个地方看到过这个。来自this article, The Old New Thing:
假设我们生活在一个幻想世界 对“字节”的操作导致 '字节'。
byte b = 32;
byte c = 240;
int i = b + c; // what is i?
在这个奇幻的世界里,我的价值 将是 16 岁!为什么?因为两人 + 运算符的操作数都是 字节,因此总和“b+c”计算为 一个字节,结果为 16 由于 整数溢出。 (而且,正如我所指出的 早些时候,整数溢出是新的 安全攻击向量。)
编辑:Raymond 本质上是在捍卫 C 和 C++ 最初采用的方法。在 cmets 中,他为 C# 采用相同方法的事实辩护,理由是语言向后兼容。
【讨论】:
如果我们添加整数并且它溢出它不会自动将其转换为不同的数据类型,那么为什么要使用字节呢? 使用整数它确实会溢出。尝试添加 int.MaxValue + 1 你得到 -2147483648 而不是 2147483648。 @Longhorn213:是的,这就是 Ryan 所说的:int math 可以溢出,但 int math 不会返回 long。 没错。如果这是一种安全措施,那么它的实施非常糟糕;) @Ryan:对于像原始数学这样基本的东西,“懒惰”对 C# 语言设计者来说是一个相当大的指控。如果你想指责他们任何事情,那就让它“过度向后兼容 C/C++”。【参考方案4】:C#
ECMA-334 声明加法仅在 int+int、uint+uint、long+long 和 ulong+ulong 上被定义为合法 (ECMA-334 14.7.4)。因此,这些是针对 14.4.2 考虑的候选操作。因为存在从 byte 到 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 仅在 6 种类型(int32、native int、int64、F、O 和 &)上运行。 (ECMA-335 分区 3 第 1.5 节)
Byte (int8) 不是这些类型之一,在添加之前会自动强制转换为 int32。 (ECMA-335 分区 3 第 1.6 节)
【讨论】:
ECMA 仅指定那些特定操作不会阻止语言实现其他规则。 VB.NET 将有助于允许byte3 = byte1 And byte2
不进行强制转换,但如果 int1 = byte1 + byte2
产生超过 255 的值,则将无济于事地抛出运行时异常。我不知道是否有任何语言允许 byte3 = byte1+byte2
并在超过时抛出异常255,但如果 int1 = byte1+byte2
产生的值在 256-510 范围内,则不会抛出异常。【参考方案5】:
表明添加字节并将结果截断回字节的某些低效率的答案是不正确的。 x86 处理器具有专门为 8 位整数运算而设计的指令。
事实上,对于 x86/64 处理器,执行 32 位或 16 位操作的效率低于 64 位或 8 位操作,因为必须解码操作数前缀字节。在 32 位机器上,执行 16 位操作会带来相同的损失,但仍然存在用于 8 位操作的专用操作码。
许多 RISC 架构都有类似的本地字/字节高效指令。那些通常没有存储并转换为一些位长度的有符号值的。
换句话说,这个决定一定是基于对字节类型的感知,而不是由于硬件的潜在低效率。
【讨论】:
+1;如果每次我在 C# 中对两个字节进行移位和 OR 运算时这种看法都没有错就好了…… 截断结果不应该有任何性能成本。在 x86 汇编中,这只是从寄存器中复制一个字节或从寄存器中复制四个字节的区别。 @JonathanAllen 没错。具有讽刺意味的是,唯一的区别是在执行 加宽 转换时。 当前设计会导致执行扩展指令(有符号扩展或无符号扩展)的性能损失。 "感知字节类型的用途" -- 这可以解释byte
(和char
)的这种行为,但不适用于short
语义上显然是一个数字。【参考方案6】:
我记得有一次从 Jon Skeet(现在找不到,我会继续寻找)那里读到一些关于 byte 实际上不会使 + 运算符超载的内容。事实上,当像您的示例一样添加两个字节时,每个字节实际上都被隐式转换为一个 int。结果显然是一个int。现在至于为什么这样设计,我会等 Jon Skeet 自己发帖:)
编辑:找到了!关于这个主题的大量信息here。
【讨论】:
【参考方案7】:这是因为溢出和进位。
如果将两个 8 位数字相加,它们可能会溢出到第 9 位。
例子:
1111 1111
+ 0000 0001
-----------
1 0000 0000
我不确定,但我认为ints
、longs
和doubles
被给予了更多空间,因为它们本来就相当大。此外,由于内部数据总线的宽度为 4 字节或 32 位(现在 64 位越来越普遍),因此它们是 4 的倍数,这对于计算机来说更有效。 byte 和 short 效率稍低一些,但它们可以节省空间。
【讨论】:
但较大的数据类型不遵循相同的行为。 溢出问题是一回事。如果您将逻辑应用到语言中,那么所有数据类型在加法运算后都会返回更大的数据类型,这绝对不是这种情况。整数 + 整数 = 整数,长 + 长 = 长。我认为问题在于不一致。 这是我的第一个想法,但为什么 int+int = long 不呢?所以我不买“可能溢出”的论点......但是根据 C# 语言规范 1.6.7.5 7.2.6.2 Binary numeric Promotions,如果它不能适合其他几个类别,它会将两个操作数都转换为 int。我的猜测是他们没有重载 + 运算符以将 byte 作为参数,而是希望它正常运行,所以他们只使用 int 数据类型。
C# language Spec
【讨论】:
【参考方案9】:我的怀疑是 C# 实际上调用了在 int
上定义的 operator+
(除非您在 checked
块中,否则它将返回 int
),并隐式转换您的 bytes
/@987654326 @到ints
。这就是行为看起来不一致的原因。
【讨论】:
它将两个字节都压入堆栈,然后调用“add”命令。在 IL 中,添加“吃掉”这两个值并将它们替换为 int。【参考方案10】:这可能是语言设计者的一个实际决定。毕竟,int 是一个 Int32,一个 32 位有符号整数。每当您对小于 int 的类型执行整数运算时,无论如何,大多数 32 位 CPU 都会将其转换为 32 位有符号 int。再加上溢出小整数的可能性,很可能达成了交易。它使您免于不断检查溢出/下溢的繁琐工作,并且当字节表达式的最终结果在范围内时,尽管在某些中间阶段它会超出范围,但您会得到正确的结果。
另一个想法:必须模拟这些类型的上溢/下溢,因为它不会在最有可能的目标 CPU 上自然发生。何必呢?
【讨论】:
【参考方案11】:这是我与该主题相关的大部分答案,首先提交给类似的问题here。
所有小于 Int32 的整数运算默认在计算前四舍五入到 32 位。结果是 Int32 的原因只是在计算后保持原样。如果检查 MSIL 算术操作码,它们操作的唯一整数类型是 Int32 和 Int64。这是“设计使然”。
如果您希望返回 Int16 格式的结果,则在代码中执行强制转换是无关紧要的,或者编译器(假设地)“在后台”发出转换。
例如做Int16算术:
short a = 2, b = 3;
short c = (short) (a + b);
这两个数字将扩展为 32 位,相加,然后截断回 16 位,这正是 MS 的预期。
使用短(或字节)的优势主要是在您拥有大量数据(图形数据、流媒体等)的情况下进行存储
【讨论】:
【参考方案12】:没有为字节定义加法。因此它们被强制转换为 int 以进行加法。大多数数学运算和字节都是如此。 (请注意,这是过去在旧语言中的用法,我假设它今天仍然适用)。
【讨论】:
【参考方案13】:我认为这是一个关于哪种操作更常见的设计决定......如果 byte+byte = byte 可能更多的人会因为在需要 int 作为结果时必须强制转换为 int 而烦恼。
【讨论】:
我有一次被另一种方式所困扰 :) 我似乎总是需要字节结果,所以我总是必须转换。 除非您不必强制转换为 int。演员表是隐含的。只有另一种方式是明确的。 @nikie 我想你不明白我的回答。如果添加两个字节会产生一个字节,为了防止溢出,有人必须在添加之前将 操作数(不是结果)强制转换为 int。【参考方案14】:来自 .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);
【讨论】:
【参考方案15】:我已经测试了字节和整数之间的性能。 使用 int 值:
class Program
private int a,b,c,d,e,f;
public Program()
a = 1;
b = 2;
c = (a + b);
d = (a - b);
e = (b / a);
f = (c * b);
static void Main(string[] args)
int max = 10000000;
DateTime start = DateTime.Now;
Program[] tab = new Program[max];
for (int i = 0; i < max; i++)
tab[i] = new Program();
DateTime stop = DateTime.Now;
Debug.WriteLine(stop.Subtract(start).TotalSeconds);
字节值:
class Program
private byte a,b,c,d,e,f;
public Program()
a = 1;
b = 2;
c = (byte)(a + b);
d = (byte)(a - b);
e = (byte)(b / a);
f = (byte)(c * b);
static void Main(string[] args)
int max = 10000000;
DateTime start = DateTime.Now;
Program[] tab = new Program[max];
for (int i = 0; i < max; i++)
tab[i] = new Program();
DateTime stop = DateTime.Now;
Debug.WriteLine(stop.Subtract(start).TotalSeconds);
结果如下: 字节:3.57s 157mo、3.71s 171mo、3.74s 168mo with CPU ~= 30% int : 4.05s 298mo, 3.92s 278mo, 4.28 294mo with CPU ~= 27%结论: byte 使用更多的 CPU,但它消耗更少的内存并且速度更快(可能是因为要分配的字节更少)
【讨论】:
【参考方案16】:除了所有其他伟大的 cmets,我想我会添加一个小花絮。许多 cmets 想知道为什么 int、long 和几乎任何其他数字类型也不遵循此规则...返回“更大”类型以响应算术。
很多答案都与性能有关(嗯,32 位比 8 位快)。实际上,对于 32 位 CPU,8 位数字仍然是 32 位数字......即使添加两个字节,cpu 操作的数据块也将是 32 位......所以添加整数不会比添加两个字节“更快”……对cpu来说都是一样的。现在,添加两个整数将比在 32 位处理器上添加两个长整数更快,因为添加两个长整数需要更多的微操作,因为您使用的数字比处理器字宽。
我认为导致字节算术产生整数的根本原因非常清楚和直接:8 位并没有走得太远! :D 使用 8 位,您的无符号范围为 0-255。这不是有很大的工作空间......在算术中使用它们时,您遇到字节限制的可能性非常高。但是,在使用整数、长整数或双精度数等时,您将用完位的可能性要低得多……足够低,以至于我们很少遇到需要更多位的情况。
从字节到整数的自动转换是逻辑,因为字节的规模是如此之小。从 int 到 long、float 到 double 等的自动转换是不合逻辑,因为这些数字具有很大的规模。
【讨论】:
这仍然无法解释为什么byte - byte
返回int
,或者为什么他们不转换为short
...
为什么要加法返回与减法不同的类型?如果byte + byte
返回int
,因为 255+anything 大于一个字节可以容纳的,那么从返回类型一致性的角度来看,让任何字节减去任何其他字节返回除 int 之外的任何内容都是没有意义的。跨度>
我不会,只是说明上面的原因可能不对。如果它必须与结果“拟合”有关,那么byte
减法将返回byte
,而字节加法将返回short
(byte
+ byte
将始终适合short
)。如果像你说的那样与一致性有关,那么short
仍然足以满足这两种操作,而不是int
。显然有多种原因,并非所有原因都必须经过深思熟虑。或者,下面给出的性能原因可能更准确。以上是关于byte + byte = int ... 为啥?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Java API 使用 int 而不是 short 或 byte?
为啥允许从 int 到 byte 但不允许从 long 到 int 的隐式缩小转换?
为啥java虚拟机jvm要把byte和short的运算都转为int
java问题byte a=1,b=1;byte c=a+b;为啥错