バイト+バイト= int…なぜですか?


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
ここで役立つのは、エリックの標準に関する知識ではありません。言語の設計に関する彼の知識です。何故か。しかし、はい、エリックの答えはかなり決定的です:)
Jon Skeet

143
以下のさまざまな考察は、設計上の考慮事項の妥当な近似です。より一般的には、バイトを「数値」とは見なしません。私はそれらを、数字、文字、色などと解釈できるビットのパターンと考えています。それらを計算して数値として扱う場合は、結果をより一般的には数値として解釈されるデータ型に移動するのが理にかなっています。
Eric Lippert、

28
@Eric:これは、バイトに対しては理にかなっていますが、おそらくshort / ushortに対してはそれほど意味がありません。
Jon Skeet、

23
@Eric:byte1 | byte2それらを数値として扱うことはまったくありません。これはそれらをビットのパターンとして正確に扱っています。私はあなたの見解を理解していますが、C#でバイトに対して算術演算を行うたびに、実際にはそれらを数値ではなくビットとして扱っていたため、この動作は常に妨げになっています。
ローマン・スターコフ2009

回答:


228

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

byte z = x + y;

実際に意味する

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

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


以下のコードを試しましたが、まだ機能しません。バイトz =(バイト)x +(バイト)y;
匿名

10
これは、バイトに対して+演算がないためです(上記を参照)。バイトz =(byte)((int)x +(int)y)を
試す

35
これは最も正確で簡潔な答えでなければなりません。バイトの間の追加、その代わりに、なぜ作品かどうか(「2つのバイトを追加し、」説明のために何のオペランドがありません、それは起こったことはありません)、起こった唯一のことは、結果はintで、なぜこの明確に示しているので、2つのintの追加
RichardTheKiwi

2
他のすべての答えを読んでめまいがしました(Jon Skeet氏に不快感はありません)。これは、内部で何が起こっているのかを正しく説明する最も単純な答えであることがわかりました。ありがとう!
rayryeng

ここでは、このコンパイラ駆動型自動昇進をしたときに識別するためのプログラムが含まれています、私は他の場所で書いた答えだint発生しているが:stackoverflow.com/a/43578929/4561887は
ガブリエル・ステープルズ

172

「なぜそれが起こるのか」という点では、他の人が言ったように、byte、sbyte、short、ushortを使った算術演算のためにC#で定義された演算子がないためです。この答えはそれらの演算子が定義されていない理由についてです。

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

私が考えて、これは注釈付きC#の規格の一つに記載されています。見て...

編集:不愉快なことに、私は今、注釈付きECMA C#2仕様、注釈付きMS C#3仕様、および注釈CLI仕様を調べましたが、私が知る限り、それらのどれもこれについて言及していません。私はよ確か私は、上記の理由を見てきましたが、私はどこ知っていれば私はフェラしています。謝罪、参照ファン:(


14
申し訳ありませんが、これは最善の答えではありません。
VVS

42
あなたが最善ではないことがわかったすべての回答に反対票を投じましたか?;)
Jon Skeet、

55
(明確にするために、私は実際にあなたに挑戦しているわけではありません。投票にはそれぞれ独自の基準があるようです。それは問題ありません。回答が反対であるのは、理想的ではないだけでなく積極的に有害であると思われる場合のみです。 )
ジョンスキート

21
私は投票を手段として使用して、「最高の」答えをトップに導きます。実際、私はあなたの回答であなたがあまり多くを言わなかったことがわかりました。それが私の反対票の主な理由でした。もう1つの理由は、投票に関しては担当者に大きなボーナスを与え、「より良い」回答の上に着地しているという私の主観的な気持ちかもしれません。
VVS

23
IMOが「最高」の回答を得るには、それを支持するのが最善の方法です。正直に言うと、私はそこにはないと思う(「何コンパイラの実行」の視点ではなく)設計の観点のために、それ以外のここで最も有益な答えは問題のエリックさんのコメントだと思う...しかしある多くは「パフォーマンス」を超えて答えます。特に、「オーバーフローを防ぐ」という議論(17票)は購入しません。これは、int + int = longを示唆しているためです。
Jon Skeet、

68

私は思った私はこの前にどこかを見ていました。この記事から、The Old New Thing

「バイト」を操作すると「バイト」が生まれるファンタジーの世界に住んでいたとしましょう。

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

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

編集:レイモンドは本質的に、CおよびC ++が最初に採用したアプローチを擁護しています。彼はコメントで、言語の下位互換性を理由に、C#が同じアプローチを取るという事実を擁護しています。


42
整数では、追加してオーバーフローした場合、自動的に別のデータ型としてキャストされませんが、なぜバイトでそれを行うのですか?
ライアン、

2
intsではオーバーフローします。あなたの代わりに2147483648の-2147483648を取得int.MaxValue + 1を追加してみてください
デビッドBasarab

8
@ Longhorn213:うん、それはライアンの言っていることです:int mathはオーバーフローする可能性がありますが、int mathはlongを返しません。
Michael Petrotta

28
丁度。これがセキュリティ対策であることを意図している場合、それは非常に不十分に実装されたものです;)
Jon Skeet

5
@Ryan: "lazy"は、原始的な数学と同じくらい基本的なものに対して、C#言語デザイナーに対してかなり多額の費用を課します。それらを非難したい場合は、「C / C ++との後方互換性が高すぎる」ようにしてください。
Michael Petrotta

58

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

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)


ECMAがそれらの特定の操作のみを指定することは、言語が他のルールを実装することを妨げないでしょう。VB.NETはbyte3 = byte1 And byte2キャストなしで有効に許可int1 = byte1 + byte2しますが、255を超える値が生成されると、実行時例外をスローしますbyte3 = byte1+byte2。255を超えると例外が許可されてスローされる言語があるかどうかはわかりませんが、int1 = byte1+byte2生成される場合は例外をスローしません256〜510の範囲の値。
スーパーキャット2014

26

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

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

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

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


+1; これだけ認識は間違っているすべての単一の私が今までにシフトしている時間と論理和C#で2バイト...でなかった場合
ローマStarkov

結果を切り捨てるためのパフォーマンスコストはありません。x86アセンブリでは、レジスタから1バイトをコピーするか、レジスタから4バイトをコピーするかの違いだけです。
ジョナサンアレン

1
@JonathanAllenその通りです。唯一の違いは、皮肉なことに、拡大変換を実行するときです。現在の設計は広く命令を実行するための性能ペナルティを被る(いずれかの拡張または延びUNSIGNED符号付き)
reirab

バイト型の目的の認識」-これはbyte(およびchar)のこの動作を説明する可能性がありますがshort、意味的に明らかに数値ではありません。
smls '19

13

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

編集:それを見つけました!このまさにこのトピックについての素晴らしい情報はここにあります


9

これはオーバーフローとキャリーのためです。

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

例:

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

私は確かに知らないが、私はそれを想定しintslongsし、doublesそれがあるとして、彼らはかなり大きいため、より多くのスペースを与えられています。また、これらは4の倍数であり、内部データバスの幅が4バイトまたは32ビット(現在、64ビットが普及している)であるため、コンピューターがより効率的に処理できます。Byteとshortは少し非効率的ですが、スペースを節約できます。


23
しかし、より大きなデータ型は同じ動作に従いません。
イニシア

12
オーバーフローの問題は別として。ロジックを取り、それを言語に適用する場合、すべてのデータ型は加算算術後に大きなデータ型を返しますが、これは間違いなくそうではありません。int + int = int、long + long = long。問題は矛盾についてだと思います。
ジョセフ

それが最初の考えでしたが、なぜint + int = longにならないのですか?だから私は「オーバーフローの可能性」の議論を購入していません...まだ<にやにや>。
Robert Cartaino 2009年

11
ああ、そして「起こり得るオーバーフロー」の議論について、なぜバイト+バイト=ショートではないのですか?
Robert Cartaino 2009年

A)C#のルールを前提にすると、なぜ機能するのですか?以下の私の答えを参照してください。B)なぜそれがそのように設計されたのですか?おそらく、ほとんどの人がintおよびbytesを使用する傾向にある主観的な判断に基づいて、ユーザビリティの考慮事項のみ。
mqp 09年

5

C#言語仕様1.6.7.5 7.2.6.2から、2進数の数値プロモーションは、他のいくつかのカテゴリに収まらない場合、両方のオペランドをintに変換します。私の推測では、バイトをパラメーターとして取るために+演算子をオーバーロードしませんでしたが、通常どおりに動作させてintデータ型を使用したいと考えています。

C#言語仕様


4

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


3
両方のバイトをスタックにプッシュしてから、「add」コマンドを呼び出します。ILでは、2つの値を「食べる」ように追加し、それらをintに置き換えます。
ジョナサンアレン

3

これはおそらく言語デザイナーの側の実際的な決定でした。結局のところ、intは32ビットの符号付き整数であるInt32です。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);

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

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


1

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


0

どちらの操作がより一般的であったかは、設計上の決定だと思います...バイト+バイト=バイトの場合、結果としてintが必要なときにintにキャストする必要があるため、はるかに多くの人が気になるでしょう。


2
私は一度反対の方法で困っています:)私は常にバイトの結果を必要とするようですので、常にキャストする必要があります。
ローマスターコフ2009

ただし、intにキャストする必要はありません。キャストは暗黙的です。他の方法のみが明示的です。
Niki

1
@nikie私の答えが理解できなかったと思います。2バイトを追加すると1バイトが生成される場合、オーバーフローを防ぐために、誰かがオペランド(結果ではなく)を加算前に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);
    }
}

ここでの結果:
バイト:3.57秒157か月、3.71秒171か月、3.74秒168か月でCPU〜= 30%
int:4.05秒298か月、3.92秒278か月、4.28 294か月でCPU〜= 27%
結論:
バイトはより多くのCPUを使用しますが、それ以上メモリーが少なくて高速です(割り当てるバイトが少ないためか)


-1

他のすべてのすばらしいコメントに加えて、私は1つの小さなヒントを追加すると思いました。多くのコメントは、なぜint、long、および他のほとんどの数値型もこのルールに従っていないのか疑問に思っています...算術に応じて「より大きな」型を返します。

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

バイト演算の結果としてintが発生する根本的な理由は、かなり明確でわかりやすいと思います。8ビットはそれほど遠くないです。:D 8ビットでは、0〜255の符号なし範囲があります。それで作業する余地はそれほど多くありません ...バイト制限を実行する可能性は、それらを算術で使用すると非常に高くなります。ただし、int、long、doubleなどを処理するときにビットが足りなくなる可能性は大幅に低くなります。それ以上必要になることはめったにありません。

バイトのスケールが非常に小さいため、byteからintへの自動変換は論理的です。intからlong、floatからdoubleへの自動変換などは、これらの数値のスケールが大きいため、論理的ではありません


これはまだ理由を説明しないbyte - byteリターンをint、またはそれらがにキャストしない理由short...
KthProg

加算で減算とは異なる型を返すのはなぜですか?がbyte + byte返された場合int、255 +何かがバイトが保持できるよりも大きいため、任意のバイトから他のバイトを差し引いて、戻り値型の一貫性の観点からint以外のものを返すことは意味がありません。
jrista 2017年

上記の理由がおそらく正しくないことを示しているだけです。結果に「フィット」する必要がある場合、byte減算はaを返し、byteバイト加算はaを返しますshortbyte+ byteは常ににフィットしますshort)。あなたが言うように一貫性についてだったshortとしても、ではなく両方の操作で十分ですint。明らかに理由はさまざまですが、必ずしもすべてがよく考えられているわけではありません。または、以下に示すパフォーマンスの理由の方が正確な場合があります。
KthProg 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.