字节+字节=整数...为什么?


365

查看此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数组)会更快(由于缓存命中)。但是,通过代码传播的大量字节广播使它变得更加难以理解。



10
在这里有用的不是Eric的标准知识,而是他对语言设计的知识。什么不是为什么。但是,是的,埃里克(Eric)的答案将是非常确定的:)
乔恩·斯凯特

143
下面的各种思路是设计考虑因素的合理近似值。更笼统地说:我不认为字节是“数字”。我认为它们是位的模式,可以解释为数字,字符,颜色或其他任何形式。如果要对它们进行数学运算并将其视为数字,则将结果移入更通常解释为数字的数据类型是有意义的。
Eric Lippert

28
@Eric:这对于字节来说很有意义,但是对于short / ushort来说可能没有那么意义。
乔恩·斯基特

23
@Eric:byte1 | byte2根本不将其视为数字。这正好将它们视为位模式。我理解您的观点,但是碰巧每次我在C#中对字节进行任何算术运算时,我实际上都将它们视为位,而不是数字,并且这种行为总是存在。
罗曼·斯塔科夫

Answers:


228

您的代码段的第三行:

byte z = x + y;

实际上意味着

byte z = (int) x + (int) y;

因此,对字节没有+操作,字节首先转换为整数,并且两个整数相加的结果是一个(32位)整数。


我在下面尝试过代码,但仍然无法正常工作。字节z =(字节)x +(字节)y;
匿名

10
这是因为字节没有+操作(请参见上文)。尝试字节z =(字节)((int)x +(int)y)
azheglov

35
这必须是最正确,最简洁的答案。字节之间没有可加的操作数,因此,除了说明“加两个字节”为何不起作用(它从未发生过)之外,这还清楚地表明了结果为什么是整数,因为发生的唯一事情就是加了2个整数
RichardTheKiwi

2
我对所有其他答案都感到头晕目眩(对乔恩·斯凯特先生没有冒犯)。我发现这是最简单的答案,可以正确描述引擎盖下发生的事情。谢谢!
rayryeng

这是我在其他地方写的答案,其中包含一个程序,该程序可以识别何时发生此编译器驱动的自动升级intstackoverflow.com/a/43578929/4561887
Gabriel Staples

172

就“为什么会发生”而言,这是因为C#定义的任何运算符都没有像其他人所说的那样使用byte,sbyte,short或ushort进行算术运算。这个答案是关于为什么未定义这些运算符的原因

我相信这基本上是出于性能考虑。处理器具有本机运算,可以非常快速地使用32位进行算术运算。可以自动完成从结果到字节的转换,但是如果您实际上不希望这种行为,则会导致性能下降。

认为这是在带注释的C#标准之一中提到的。看着...

编辑:令人讨厌的是,我现在已经浏览了带注释的ECMA C#2规范,带注释的MS C#3规范和注释CLI规范,据我所知,它们都没有提及。我敢肯定我已经知道了上面给出的原因,但是如果我知道在哪里,我会很震惊。抱歉,参考粉丝:(


14
抱歉,我不能这么说。
VVS

42
您是否已拒绝所有发现不是最佳答案的答案?;)
Jon Skeet

55
(为澄清起见,我并没有真正接受您的意见。似乎每个人都有自己的不赞成票标准,这没关系。我仅对我认为对自己有害的答案而不是不理想的答案不赞成。 )
乔恩·斯基特

21
我使用投票作为获得最佳答案的工具。实际上,我发现您的回答根本没有说太多,这是我投反对票的主要原因。另一个原因也许是我的主观感觉,即您的代表在投票方面给了您很大的好处,而您的答案就更好了。
VVS

23
国际海事组织获得最高“最佳”答案的最佳方法是对此表示赞成。说实话,我觉得这里最翔实的回答是Eric的在问题的评论......但除此之外,对于设计的角度(相对于“是什么编译器的做”的观点),我不认为有多少超越“表现”的答案。特别是,我真的不购买“它防止溢出”参数(17票),因为这表明int + int = long。
乔恩·斯基特

68

以为我以前在某个地方见过这个。从这篇文章,旧的新事物

假设我们生活在一个幻想世界中,对“字节”的操作导致了“字节”。

byte b = 32;
byte c = 240;
int i = b + c; // what is i?

在这个幻想世界中,我的价值将是16!为什么?由于+运算符的两个操作数均为字节,因此将“ b + c”之和计算为字节,由于整数溢出,结果为16。(而且,正如我之前提到的,整数溢出是新的安全攻击媒介。)

编辑:雷蒙德实质上是在捍卫C和C ++最初采用的方法。在评论中,他以语言的向后兼容性为由捍卫了C#采用相同方法的事实。


42
对于整数,如果我们将它们相加并且溢出,它不会自动将其强制转换为其他数据类型,那么为什么要使用字节呢?
Ryan

2
使用int会溢出。尝试添加int.MaxValue + 1你-2147483648代替2147483648
大卫巴萨拉布

8
@ Longhorn213:是的,这就是Ryan的意思:int数学可以溢出,但是int数学不会返回long。
Michael Petrotta 09年

28
究竟。如果这是一种安全措施,那么这是实施不佳的措施;)
Jon Skeet

5
@Ryan:“懒惰”是针对C#语言设计者的一项相当大的指控,涉及基本数学之类的基本知识。如果您想指责他们,请使其“与C / C ++的向后兼容性过高”。
Michael Petrotta 09年

58

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仅对6种类型(int32,本机int,int64,F,O和&)运行。(ECMA-335分区3第1.5节)

字节(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产生任何异常(如果产生255)256-510范围内的值。
2014年

26

表示添加字节效率低下并将结果截断为字节的答案不正确。x86处理器具有专门针对8位量的整数运算而设计的指令。

实际上,对于x86 / 64处理器,由于必须解码操作数前缀字节,因此执行32位或16位操作的效率要低于64位或8位操作。在32位计算机上,执行16位操作会带来相同的损失,但是仍然存在用于8位操作的专用操作码。

许多RISC体系结构具有相似的本机字/字节有效指令。那些通常没有存储并转换为某位长度的带符号值的那些。

换句话说,该决定必须基于对字节类型的含义的了解,而不是由于底层硬件效率低下。


+1; 如果每一次我都移位并在C#中对两个字节进行OR运算时,这种理解就不会出错...
Roman Starkov 09年

截断结果不应有任何性能成本。在x86汇编中,只是从寄存器中复制一个字节或从寄存器中复制四个字节之间的区别。
乔纳森·艾伦,2010年

1
@乔纳森·艾伦没错。具有讽刺意味的是,唯一的区别是执行扩展转换时。在当前设计招致的性能损失来执行扩大指示(有符号扩展或无符号扩展)
reirab

字节类型是什么的理解 ”-可以解释byte(和char)的这种行为,但对于short语义上显然不是数字的情况却不能。
smls '18

13

我记得曾经从Jon Skeet读过一些东西(现在找不到,我会继续寻找),关于字节实际上是如何不会使+运算符重载的。实际上,当像示例中那样添加两个字节时,每个字节实际上都被隐式转换为int。其结果显然是一个int。现在为什么要这样设计,我将等待Jon Skeet自己发表:)

编辑:找到了!关于这个题目伟大的信息在这里


9

这是因为溢出和携带。

如果将两个8位数字相加,它们可能会溢出到第9位。

例:

  1111 1111
+ 0000 0001
-----------
1 0000 0000

我不知道肯定,但我认为intslongsdoubles被赋予了更多的空间,因为他们是相当大的,因为它是。而且,它们是4的倍数,由于内部数据总线的宽度为4字节或32位(64位现在越来越流行),因此对于计算机而言,它们的处理效率更高。字节和短时效率较低,但是可以节省空间。


23
但是较大的数据类型不会遵循相同的行为。
Inisheer

12
溢出问题放在一边。如果要采用自己的逻辑并将其应用于语言,那么所有数据类型在加算术之后都会返回更大的数据类型,这绝对不是这种情况。int + int = int,长+长=长。我认为问题在于不一致之处。
约瑟夫

那是我的第一个想法,但为什么int + int = long?因此,我不是在购买“可能的溢出”争论……而是<grin>。
罗伯特·卡塔诺

11
哦,关于“可能的溢出”建议,为什么字节+字节=短?
罗伯特·卡塔诺

A)在给定C#规则的情况下,为什么它按其工作方式工作?请参阅下面的答案。B)为什么按原样设计?基于大多数人倾向于使用整数和字节的方式的主观判断,可能只是可用性方面的考虑。
mqp

5

从C#语言规范1.6.7.5 7.2.6.2二进制数值促销中,如果不能将其分为其他几个类别,则它将两个操作数都转换为int。我的猜测是,他们没有让+运算符重载以byte作为参数,而是希望它以某种正常的方式运行,因此他们只使用int数据类型。

C#语言规范


4

我怀疑是C#实际上是调用operator+定义int(返回的int,除非你是在一个checked块),并含蓄铸造双方你的bytes/ shortsints。这就是行为似乎不一致的原因。


3
它将两个字节都压入堆栈,然后调用“ add”命令。在IL中,将两个值“吃掉”并用int替换它们。
乔纳森·艾伦,2010年

3

对于语言设计师来说,这可能是一个实际的决定。毕竟,int是一个Int32(32位有符号整数)。每当您对小于int的类型进行整数运算时,无论如何,大多数32位CPU都会将其转换为32位带符号的int。这样,再加上小整数溢出的可能性,很可能达成了交易。它使您免于连续检查上溢/下溢的繁琐工作,并且当以字节为单位的表达式的最终结果在范围内时,尽管在某些中间阶段它超出范围,您还是可以得到正确的结果。结果。

另一个想法:必须模拟这些类型的上溢/下溢,因为在最可能的目标CPU上自然不会发生这种情况。何必呢?


2

这是在大多数情况下我的答案,涉及到这个话题,首先提交给一个类似的问题在这里

默认情况下,所有整数小于Int32的运算都将四舍五入到32位。结果为Int32的原因只是将其保留为计算后的样子。如果检查MSIL算术操作码,则它们使用的唯一整数类型是Int32和Int64。这是“设计使然”。

如果希望将结果返回为Int16格式,则在代码中执行强制转换或编译器(假设地)发出“内幕”转换均无关紧要。

例如,执行Int16算法:

short a = 2, b = 3;

short c = (short) (a + b);

这两个数字将扩展为32位,相加,然后截断为16位,这就是MS的预期方式。

使用短(或字节)的优势主要是在您拥有大量数据(图形数据,流式传输等)的情况下进行存储


1

没有为字节定义加法。因此它们被强制转换为int进行加法运算。对于大多数数学运算和字节来说都是如此。(请注意,这是以前使用较旧语言的方式,我假设它在今天仍然适用)。


0

我认为这是关于哪个操作更常见的设计决定...如果byte + byte = byte的话,可能会在需要int时强制转换为int,从而使更多的人受到困扰。


2
我一次被另一种方法困扰:)我似乎总是需要字节结果,所以我总是必须强制转换。
罗曼·斯塔科夫

除非您不必强制转换为int。强制转换是隐式的。只有另一种方式是明确的。
Niki 2010年

1
@nikie我认为您不理解我的答案。如果将两个字节相加会产生一个字节,则为了防止溢出,必须在添加之前将操作数(不是结果)强制转换为int。
fortran

0

从.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);


0

我已经测试了字节和整数之间的性能。
具有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);
    }
}

结果为:
byte: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%
结论:
字节使用更多的CPU但它花费较少的内存,并且速度更快(可能是因为分配的字节更少)


-1

除了所有其他很棒的评论外,我还想补充一点建议。许多评论都想知道为什么int,long和几乎任何其他数字类型也都没有遵循此规则...对算术运算返回“更大”的类型。

许多答案与性能有关(好吧,32位比8位快)。实际上,对于32位CPU,8位数字仍然是32位数字...。即使您添加两个字节,CPU所操作的数据块也将是32位,无论如何...因此添加int不会比添加两个字节要快得多……对CPU来说都是一样的。现在,在32位处理器上加两个整数将比加两个long更快,因为加两个整数需要更多的微操作,因为您要处理的数字大于处理器的字。

我认为导致字节算术运算结果为int的根本原因非常清楚而直接:8bits并没有走太远!:D 8位,无符号范围为0-255。这并不是一个很大的工作空间...在算术中使用它们时,遇到字节限制的可能性非常高。但是,在使用整数,长整数或双精度数等时,用尽位的机会会大大降低...低到足以很少遇到更多需求。

从字节自动转换为int是合乎逻辑的,因为字节的规模很小。从int到long的自动转换,从float到double的自动转换等都是不合逻辑的,因为这些数字的比例很大。


这仍然不能解释为什么byte - byte返回int,或者为什么不将其投给short...
KthProg

为什么要让加法返回与减法不同的类型?如果byte + byte返回intreturn,因为255+大于一个字节可以容纳的任何东西,所以从返回类型一致性的角度来看,让任何字节减去任何其他字节都返回除int之外的任何东西是没有意义的。
jrista

我不会,这只是表明上述原因可能不正确。如果必须与结果“拟合”有关,则byte减法将返回byte,字节加法将返回a shortbyte+ byte始终适合于short)。如果您要说的是一致性,那么short这两个操作都足够了,而不是int。显然有多种原因混合在一起,并非所有的原因都必须经过深思熟虑。或者,下面给出的性能原因可能更准确。
KthProg
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.