web-dev-qa-db-ja.com

byte + byte = int ...なぜですか?

このC#コードを見ると:

byte x = 1;
byte y = 2;
byte z = x + y; // ERROR: Cannot implicitly convert type 'int' to 'byte'

byte(またはshort)タイプで実行された計算の結果は、暗黙的に整数にキャストバックされます。解決策は、結果を明示的に1バイトにキャストすることです。

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未満)で長い計算リストを実行し、中間結果を大きな配列に格納しています。 byte配列(int配列の代わり)の使用は高速です(キャッシュヒットのため)。しかし、コード全体に広がる広範なバイトキャストにより、コードはさらに読みにくくなります。

351
Robert Cartaino

コードスニペットの3行目:

byte z = x + y;

実際に

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

したがって、バイトに対する+演算はありません。バイトは最初に整数にキャストされ、2つの整数の加算結果は(32ビット)整数になります。

214
azheglov

「なぜ起こるのか」という点では、バイト、sbyte、short、またはushortを使用した算術演算のためにC#で定義されている演算子が他にないためです。この答えは、なぜこれらの演算子が定義されていないかについてです。

基本的にパフォーマンスのためだと思います。プロセッサには、32ビットで非常に高速に算術演算を行うためのネイティブ操作があります。結果からバイトへの変換を自動的に戻すcouldを行うことができますが、実際にそれを望まない場合はパフォーマンスが低下します動作。

Ithinkこれは、注釈付きのC#標準の1つで言及されています。探しています...

編集:迷惑なことに、注釈付きECMA C#2仕様、注釈付きMS C#3仕様、注釈CLI仕様、およびnone私が見る限り、彼らはこれに言及しています。私はsureです謝罪、参照ファン:(

166
Jon Skeet

thoughtこれはどこかで見たことがあります。 この記事、The Old New Thing から:

「バイト」に対する操作が「バイト」をもたらす幻想的な世界に住んでいたと仮定します。

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

このファンタジーの世界では、iの値は16になります!どうして? +演算子の2つのオペランドは両方ともバイトであるため、合計 "b + c"はバイトとして計算され、整数オーバーフローのため16になります。 (また、前述したように、整数オーバーフローは新しいセキュリティ攻撃ベクトルです。)

EDIT:レイモンドは、基本的にCとC++が最初に取ったアプローチを擁護しています。コメントでは、言語の後方互換性を理由に、C#が同じアプローチを採用しているという事実を擁護しています。

67

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のルールによって最適な暗黙のキャストを見つける必要があります。

Int(T1)へのキャスト(C1)は、uint(T2)またはulong(T2)へのキャスト(C2)よりも優れています。

  • T1がintで、T2がuintまたはulongの場合、C1がより良い変換です。

Intからlongへの暗黙的なキャストがあるため、キャスト(C1)からint(T1)へのキャストは、キャスト(C2)からlong(T2)へのキャストよりも優れています。

  • T1からT2への暗黙的な変換が存在し、T2からT1への暗黙的な変換が存在しない場合、C1がより良い変換です。

したがって、intを返すint + int関数が使用されます。

C#仕様の非常に深いところに埋まっていると言うのは、非常に長い方法です

CLI

CLIは6つのタイプ(int32、ネイティブint、int64、F、O、および&)でのみ動作します。 (ECMA-335パーティション3セクション1.5)

バイト(int8)はこれらのタイプの1つではなく、追加する前に自動的にint32に強制変換されます。 (ECMA-335パーティション3セクション1.6)

57
Alun Harford

バイトを追加し、結果をバイトに切り捨てる非効率性を示す答えは正しくありません。 x86プロセッサには、8ビット単位の整数演算用に特別に設計された命令があります。

実際、x86/64プロセッサの場合、32ビットまたは16ビット操作の実行は、デコードする必要があるオペランドプレフィックスバイトのため、64ビットまたは8ビット操作よりも効率が低くなります。 32ビットマシンでは、16ビット操作を実行すると同じペナルティが伴いますが、8ビット操作専用のオペコードがまだあります。

多くのRISCアーキテクチャには、同様のネイティブなWord /バイト効率の高い命令があります。一般に、いくつかのビット長の符号付き値へのストアおよび変換を持たないもの。

言い換えれば、この決定は、ハードウェアの根本的な非効率性によるものではなく、バイト型の目的の認識に基づいていたに違いありません。

25
Christopher

バイトが実際に+演算子をオーバーロードしない方法について、Jon Skeetから何かを読んだことを覚えています(今は見つからないので、探し続けます)。実際、サンプルのように2バイトを追加すると、実際には各バイトが暗黙的にintに変換されます。その結果は明らかにintです。なぜこれがこのように設計されたのかについては、ジョン・スキート自身が投稿するのを待っています:)

EDIT:見つけた!このまさにトピックに関する素晴らしい情報 こちら

13
BFree

これは、オーバーフローとキャリーが原因です。

2つの8ビット数を追加すると、9ビット目にオーバーフローする可能性があります。

例:

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

確かなことはわかりませんが、intslongs、およびdoublesは、それ自体がかなり大きいため、より多くのスペースが与えられると思います。また、4の倍数であり、内部データバスの幅が4バイトまたは32ビット(現在は64ビットが普及している)であるため、コンピューターの処理がより効率的です。バイトとショートはもう少し非効率ですが、スペースを節約できます。

7
samoz

C#言語仕様1.6.7.5 7.2.6.2バイナリ数値プロモーションから、他のいくつかのカテゴリに収まらない場合、両方のオペランドをintに変換します。私の推測では、パラメータとしてバイトを取るために+演算子をオーバーロードしなかったが、それが多少正常に動作するようにしたいので、intデータ型を使用するだけです。

C#言語仕様

5
Ryan

私の疑いは、C#が実際にintで定義されたoperator+intブロックにいない限りcheckedを返す)を呼び出し、暗黙的にbytes/shortsの両方をintsにキャストしていることです。そのため、動作に一貫性がないように見えます。

4
mqp

これはおそらく、言語設計者側の実際的な決定でした。結局、intはInt32、32ビット符号付き整数です。 intよりも小さな型で整数演算を行うと、ほとんどの場合、ほとんどの32ビットCPUによって32ビット符号付きintに変換されます。それは、小さな整数のオーバーフローの可能性と相まって、おそらく取引を封印しました。オーバーフロー/アンダーフローを継続的にチェックする面倒な作業からあなたを救い、バイト上の式の最終結果が範囲内にある場合、何らかの中間段階で範囲外になるという事実にもかかわらず、あなたは正しい結果。

別の考え:これらのタイプのオーバーフロー/アンダーフローは、最も可能性の高いターゲットCPUでは自然に発生しないため、シミュレートする必要があります。なぜわざわざ?

3
PeterAllenWebb

これはほとんどの場合、このトピックに関連する私の回答であり、最初に同様の質問 here に送信されます。

Int32より小さい整数の演算はすべて、デフォルトで計算前に32ビットに切り上げられます。結果がInt32である理由は、計算後そのままにするためです。 MSIL算術オペコードをチェックする場合、それらが動作する整数型はInt32とInt64のみです。それは「設計による」ものです。

結果をInt16形式に戻す必要がある場合、コードでキャストを実行するか、コンパイラが(潜在的に)変換を "内部で"実行するかどうかは関係ありません。

たとえば、Int16算術演算を行うには:

short a = 2, b = 3;

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

2つの数値は32ビットに拡張され、追加されてから16ビットに切り捨てられます。これがMSの意図です。

ショート(またはバイト)を使用する利点は、主に大量のデータ(グラフィックデータ、ストリーミングなど)がある場合のストレージです。

2
Kenan E. K.

加算はバイトに対して定義されていません。したがって、追加のためにintにキャストされます。これは、ほとんどの数学演算とバイトに当てはまります。 (これが以前の言語で使用されていた方法であることに注意してください。今日はそれが当てはまると思います)。

1
Jim C

どの操作がより一般的であるかは設計上の決定だと思います... byte + byte = byteの場合、結果としてintが必要なときにintにキャストしなければならないことにより多くの人々が悩まされるでしょう。

0
fortran

.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;
}

Simplify .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
serhio

バイトと整数の間でパフォーマンスをテストしました。
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:CPUで3.57s 157mo、3.71s 171mo、3.74s 168mo〜= 30%
int:CPUで4.05s 298mo、3.92s 278mo、4.28 294mo〜= 27%
結論:
byteはCPUをより多く使用しますが、メモリのコストがかかり、高速です(おそらく、割り当てるバイトが少ないため)

0
puipuix

他のすべての素晴らしいコメントに加えて、ちょっとしたちょっとしたアドバイスを追加すると思いました。多くのコメントは、なぜint、long、および他のほとんどの数値型もこの規則に従わないのか疑問に思っています...算術に応じて「より大きな」型を返します。

多くの回答がパフォーマンスに関係していました(32ビットは8ビットよりも高速です)。実際には、8ビットの数値は32ビットCPUに対する32ビットの数値のままです。..2バイトを追加しても、CPUが操作するデータのチャンクは32ビットになります...したがって、intの追加は行われません。 2バイトを追加するよりも「高速」になります...すべてCPUに同じです。さて、32ビットプロセッサで2つのlongを追加するよりも2つのintを追加する方が速くなります。2つのlongを追加すると、プロセッサのWordよりも幅の広い数値で作業するため、より多くのマイクロ操作が必要になるためです.

バイト演算をintにする基本的な理由は、かなり明確で簡単だと思います:8ビットは、それほど遠くまで行かないのです! :D 8ビットの場合、符号なし範囲は0〜255です。それはnotを扱うための十分なスペースです...バイト制限に遭遇する可能性は、算術演算で使用するときに非常に高くなります。ただし、int、long、またはdoubleなどを操作するときにビットが足りなくなる可能性はかなり低くなります...十分に低いので、もっと必要になることはほとんどありません。

バイトからintへの自動変換はlogicalです。これは、バイトのスケールが非常に小さいためです。 intからlong、floatからdoubleなどへの自動変換はnot logicalです。これは、これらの数値に大きなスケールがあるためです。

0
jrista