byte + byte = int ...为什么?

时间:2009-06-02 19:59:00

标签: c# type-conversion

看看这个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数组)更快(因为缓存命中)。但是通过代码传播的大量字节转换使得它更加难以理解。

16 个答案:

答案 0 :(得分:209)

代码段的第三行:

byte z = x + y;

实际上意味着

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

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

答案 1 :(得分:162)

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

我认为这基本上是为了表现。处理器具有本机操作,可以非常快速地使用32位进行算术运算。将结果从结果转换回字节可以完成,但如果您实际上不需要这种行为,则会导致性能损失。

认为这是在一个带注释的C#标准中提到的。寻找...

编辑:令人讨厌的是,我现在已经查看了带注释的ECMA C#2规范,带注释的MS C#3规范和注释CLI规范,并且 none 他们尽可能地提到了这一点。看到。我确定
我已经看到了上面给出的理由,但如果我知道在哪里,我就会被打击。道歉,参考粉丝:(

答案 2 :(得分:67)

想到我以前见过这个。来自this article, The Old New Thing

  

假设我们生活在幻想世界中   'byte'上的操作导致了什么   '字节'。

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

在这个幻想世界中,我的价值   将是16!为什么?因为这两个   +运算符的操作数都是   字节,所以总和“b + c”计算为   一个字节,导致16由于   整数溢出。 (而且,正如我所说的那样   之前,整数溢出是新的   安全攻击向量。)

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

答案 3 :(得分:57)

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

CLI仅对6种类型(int32,native int,int64,F,O和&)进行操作。 (ECMA-335分区3第1.5节)

Byte(int8)不是这些类型之一,并且在添加之前会自动强制转换为int32。 (ECMA-335分区3第1.6节)

答案 4 :(得分:25)

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

事实上,对于x86 / 64处理器,由于必须解码的操作数前缀字节,执行32位或16位操作的效率低于64位或8位操作。在32位机器上,执行16位操作会产生相同的损失,但仍有专用的操作码用于8位操作。

许多RISC架构具有类似的本机字/字节有效指令。那些通常没有存储和转换为某些位长度的符号值的那些。

换句话说,这个决定必须基于对字节类型的感知,而不是由于硬件的潜在低效率。

答案 5 :(得分:13)

我记得曾经读过Jon Skeet的东西(现在找不到,我会继续看),关于字节实际上不会超载+运算符。实际上,当在示例中添加两个字节时,每个字节实际上都是隐式转换为int。结果显然是一个int。现在为什么这是这样设计的,我会等Jon Skeet自己发帖:)

编辑:找到了!关于这个主题here的很好的信息。

答案 6 :(得分:6)

这是因为溢出和携带。

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

示例:

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

我不确定,但我认为intslongsdoubles会给予更多空间,因为它们非常大。此外,它们是4的倍数,由于内部数据总线的宽度为4字节或32位(64位现在变得更普遍),因此它们对计算机来说更有效。字节和短字的效率要低一些,但它们可以节省空间。

答案 7 :(得分:5)

从C#语言规范1.6.7.5 7.2.6.2二进制数字促销它将两个操作数转换为int,如果它不能适合其他几个类别。我的猜测是他们没有重载+运算符以将byte作为参数,但希望它在某种程度上正常运行,所以他们只使用int数据类型。

C# language Spec

答案 8 :(得分:4)

我怀疑C#实际上正在调用operator+上定义的int(除非您处于int块中,否则会返回checked)并隐式转换您的bytes / shortsints。这就是行为看起来不一致的原因。

答案 9 :(得分:3)

这可能是语言设计者的一个实际决定。毕竟,int是Int32,一个32位有符号整数。无论何时对小于int的类型执行整数运算,无论如何,它将被大多数32位CPU转换为32位signed int。结合小整数溢出的可能性,这可能会使交易失败。它可以帮助您避免连续检查过度/不足流量的繁琐工作,并且当字节表达式的最终结果在范围内时,尽管事实上在某个中间阶段它将超出范围,您将获得正确的结果

另一个想法:必须模拟这些类型的过度/不足流量,因为它不会自然地出现在最可能的目标CPU上。为什么要这么麻烦?

答案 10 :(得分:2)

这在很大程度上是我对此主题的回答,首先提交给类似的问题here

默认情况下,在计算之前,所有小于Int32的整数运算都会向上舍入为32位。结果是Int32的原因只是在计算之后保持原样。如果检查MSIL算术操作码,则它们使用的唯一整数数字类型是Int32和Int64。这是“按设计”。

如果您希望以Int16格式返回结果,那么如果您在代码中执行强制转换,或者编译器(负责地)在“引擎盖下”发出转换,则无关紧要。

例如,要进行Int16算术:

short a = 2, b = 3;

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

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

使用短(或字节)的优势主要是存储大量数据(图形数据,流媒体等)的情况。

答案 11 :(得分:1)

我认为这是一个关于哪个操作更常见的设计决策...如果字节+字节=字节可能会因为在需要int时必须转换为int而烦恼。

答案 12 :(得分:1)

没有为字节定义添加。所以他们被添加到int中。这适用于大多数数学运算和字节。 (注意这是以前的语言,我假设它今天也适用)。

答案 13 :(得分:1)

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

答案 14 :(得分:1)

我已经测试了字节和整数之间的性能。
具有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(CPU〜= 30%
整数:4.05s 298mo,3.92s 278mo,4.28 294mo(CPU〜= 27%
结论:
字节占用更多的CPU,但它消耗的内存更少,而且速度更快(也许是因为分配的字节更少)

答案 15 :(得分:0)

除了所有其他好评之外,我想我会加一点点。很多评论都想知道为什么int,long和几乎任何其他数字类型都不遵循这个规则...返回一个“更大”的类型来响应算术。

很多答案都与性能有关(好吧,32位比8位快)。实际上,8位数仍然是32位CPU的32位数....即使你添加两个字节,cpu操作的数据块也将是32位,无论如何...所以添加int不会比添加两个字节更“快”......它与cpu完全相同。现在,添加两个整数将比在32位处理器上添加两个长度更快,因为添加两个长整数需要更多微指令,因为您使用的数字比处理器字更宽。

我认为导致字节算法导致整数的根本原因非常明确且直截了当:8位不会走得太远! :D对于8位,您的无符号范围为0-255。这是有很大的工作空间...当你在算术中使用它们时,你将遇到字节限制的可能性非常高。但是,在使用整数,长整数或双精度等时,你用完比特的可能性要低得多......低到足以让我们很少遇到更多需求。

从字节到int的自动转换是 逻辑 ,因为字节的比例非常小。从int到long,float到double等的自动转换是 不合逻辑 ,因为这些数字具有显着的比例。