web-dev-qa-db-ja.com

ushort + ushortがintに等しいのはなぜですか?

以前、今日は2つのushortを追加しようとしていましたが、結果をushortにキャストし直す必要があることに気付きました。 (意図しないオーバーフローを防ぐために)uintになったのではないかと思いましたが、驚いたことに、それはint(System.Int32)でした。

これにはいくつかの賢い理由がありますか、それともintが「基本的な」整数型と見なされているためでしょうか?

例:

ushort a = 1;
ushort b = 2;

ushort c = a + b; // <- "Cannot implicitly convert type 'int' to 'ushort'. An explicit conversion exists (are you missing a cast?)"
uint d = a + b; // <- "Cannot implicitly convert type 'int' to 'uint'. An explicit conversion exists (are you missing a cast?)"

int e = a + b; // <- Works!

編集:GregSの回答が言うように、C#仕様では、両方のオペランド(この例では「a」と「b」)をintに変換する必要があると述べています。これが仕様の一部である理由の根本的な理由に興味があります。C#仕様でushort値を直接操作できないのはなぜですか。

28
lesderid

単純で正しい答えは、「C#言語仕様がそう言っているからです」です。

明らかにあなたはその答えに満足しておらず、「なぜそう言うのか」を知りたいのです。あなたは「信頼できるおよび/または公式の情報源」を探しています、それは少し難しいでしょう。これらの設計上の決定はずっと前に行われたもので、13年はソフトウェアエンジニアリングで多くの犬の生活を送っています。それらは、Eric Lippertが呼んでいる「古いタイマー」によって作成されたものであり、より大きく、より良いものに移行し、公式の情報源を提供するためにここに回答を投稿していません。

ただし、単に信頼できるというリスクがあると推測できます。 C#のようなマネージドコンパイラには、.NET仮想マシンのコードを生成する必要があるという制約があります。そのためのルールは、CLI仕様に注意深く(そして非常に読みやすく)説明されています。これはEcma-335仕様であり、無料でダウンロードできます ここから

パーティションIIIの3.1章と3.2章を参照してください。これらは、加算を実行するために使用できる2つのIL命令、addおよびadd.ovfについて説明しています。表2「2進数演算」へのリンクをクリックすると、これらのIL命令で許可されるオペランドが説明されています。そこにリストされているタイプはほんのわずかであることに注意してください。バイトとショート、およびすべての符号なしタイプが欠落しています。 int、long、IntPtr、および浮動小数点(floatおよびdouble)のみが許可されます。 xでマークされた追加の制約がある場合、たとえばlongにintを追加することはできません。これらの制約は完全に人為的なものではなく、利用可能なハードウェアで合理的に効率的に実行できることに基づいています。

マネージコンパイラは、有効なILを生成するために、これに対処する必要があります。それは難しいことではありません。ushortをテーブルにあるより大きな値型に変換するだけです。この変換は常に有効です。 C#コンパイラは、テーブルに表示される次に大きい型であるintを選択します。または、一般に、オペランドのいずれかを次に大きい値の型に変換して、両方が同じ型を持ち、テーブルの制約を満たすようにします。

しかし、今、新しい問題がありますが、C#プログラマーをかなり厄介にする問題です。追加の結果はプロモートタイプになります。あなたの場合、それはintになります。したがって、たとえば0x9000と0x9000の2つのushort値を追加すると、完全に有効なint結果が得られます:0x12000。問題は:それはushortに戻らない値です。値オーバーフロー。しかし、それはdid n't IL計算でオーバーフローし、コンパイラがそれをushortに詰め込もうとしたときにのみオーバーフローします。 0x12000は0x2000に切り捨てられます。 10本ではなく2本または16本の指で数えた場合にのみ意味をなす、当惑するような異なる値。

注目すべきは、add.ovf命令がこの問題を処理しないことです。オーバーフロー例外を自動的に生成するために使用する命令です。しかし、そうではありません。変換されたintの実際の計算はオーバーフローしませんでした。

ここで、実際の設計上の決定が重要になります。昔の人たちは、intの結果をushortに切り捨てるだけでバグファクトリであると判断したようです。確かにそうです。彼らは、あなたがknow加算がオーバーフローする可能性があり、それが発生しても問題がないことを認める必要があると判断しました。彼らはそれを作りましたyour問題は、主に彼らがそれを彼らのものにし、それでも効率的なコードを生成する方法を知らなかったためです。キャストする必要があります。はい、それは腹立たしいことです、あなたもその問題を望まなかったと確信しています。

非常に注目すべきは、VB.NETの設計者がこの問題に対して別の解決策を採用したことです。彼らは実際にそれを成し遂げました彼らの問題であり、お金を渡しませんでした。 2つのUShortを追加して、キャストなしでUShortに割り当てることができます。違いは、VB.NETコンパイラが実際にextra ILを生成して、オーバーフロー状態をチェックすることです。これは安価なコードではなく、短い追加はすべて約3倍遅くなります。しかし、それ以外の点では、Microsoftがtwo他の点では非常に類似した機能を持つ言語を維持している理由を説明しています。

簡単に言うと、最新のCPUアーキテクチャとの相性があまり良くないタイプを使用しているため、代償を払っています。それ自体が、ushortの代わりにuintを使用する本当に良い理由です。 ushortから牽引力を引き出すのは困難です。それらを操作するコストがメモリの節約を上回る前に、それらをたくさん必要とします。 CLI仕様が制限されているだけでなく、x86コアは、マシンコードのオペランドプレフィックスバイトのために、16ビット値をロードするために余分なCPUサイクルを必要とします。それが今日でも当てはまるかどうかは実際にはわかりませんが、サイクルのカウントにまだ注意を払っていたときに戻ってきました。一年前の犬。


C#コンパイラにVB.NETコンパイラが生成するのと同じコードを生成させることで、これらの醜く危険なキャストについて気分が良くなることに注意してください。したがって、キャストが賢明でないことが判明した場合、OverflowExceptionが発生します。 [プロジェクト]> [プロパティ]> [ビルド]タブ> [詳細設定]ボタンを使用し、[算術オーバーフロー/アンダーフローを確認する]チェックボックスをオンにします。デバッグビルドのためだけに。このチェックボックスがプロジェクトテンプレートによって自動的にオンにならない理由は、もう1つの非常に神秘的な質問です。これは、かなり前に行われた決定です。

56
Hans Passant
ushort x = 5, y = 12;

次の代入ステートメントはコンパイルエラーを生成します。これは、代入演算子の右辺の算術式がデフォルトでintと評価されるためです。

ushort z = x + y;   // Error: conversion from int to ushort

http://msdn.Microsoft.com/en-us/library/cbf1574z(v = vs.71).aspx

編集:
ushortの算術演算の場合、オペランドはすべての値を保持できる型に変換されます。そのため、オーバーフローを回避できます。オペランドは、int、uint、long、ulongの順に変更できます。 C#言語仕様 を参照してください。このドキュメントでは、セクション4.1.5整数型(Wordドキュメントの80ページ前後)に進んでください。ここにあなたが見つけるでしょう:

バイナリ+、–、*、/、%、&、^、|、==、!=、>、<、> =、および<=演算子の場合、オペランドはタイプTに変換されます。ここでTは最初の演算子です。 int、uint、long、およびulongのうち、は両方のオペランドのすべての可能な値を完全に表すことができます。次に、タイプTの精度を使用して演算が実行され、結果のタイプはT(または関係演算子の場合はbool)になります。二項演算子を使用して、一方のオペランドをlong型にし、もう一方をulong型にすることはできません。

エリックリッパーは 質問 で述べています

C#では、算術演算がショートパンツで行われることはありません。算術演算はint、uints、longs、およびulongsで実行できますが、算術演算はshortsでは実行されません。前に述べたように、算術計算の大部分はintに収まるため、shortはintに昇格し、算術はintで行われます。大多数はショートに収まりません。短い演算は、int用に最適化された最新のハードウェアではおそらく遅くなり、短い演算はそれ以下のスペースを占有しません。それはチップ上でintまたはlongで実行されます。

17
Habib

C#言語仕様から:

7.3.6.2二項数値昇格二項数値昇格は、事前定義された+、–、*、/、%、&、|、^、==、!=、>、<、> =、および<=二項演算子のオペランドに対して発生します。 。 2進数の昇格は、両方のオペランドを共通の型に暗黙的に変換します。これは、非関係演算子の場合、演算の結果型にもなります。 2進数の昇格は、次のルールをここに表示される順序で適用することで構成されます。

・一方のオペランドがdecimal型の場合、もう一方のオペランドはdecimal型に変換されます。または、他方のオペランドがfloat型またはdouble型の場合、バインド時エラーが発生します。

・それ以外の場合、いずれかのオペランドがdouble型の場合、もう一方のオペランドはdouble型に変換されます。

・それ以外の場合、いずれかのオペランドがfloat型の場合、もう一方のオペランドはfloat型に変換されます。

・それ以外の場合、いずれかのオペランドがulong型の場合、もう一方のオペランドはulong型に変換されます。または、他方のオペランドがsbyte、short、int、またはlong型の場合、バインディングタイムエラーが発生します。

・それ以外の場合、一方のオペランドがlong型の場合、もう一方のオペランドはlong型に変換されます。

・それ以外の場合、いずれかのオペランドがuint型で、もう一方のオペランドがsbyte、short、またはint型の場合、両方のオペランドがlong型に変換されます。

・それ以外の場合、いずれかのオペランドがuint型の場合、もう一方のオペランドはuint型に変換されます。

・それ以外の場合、両方のオペランドはint型に変換されます。

5
James K Polk

意図されている理由はありません。これは、引数に適合する暗黙の変換がパラメータにある最初のオーバーロードが使用されることを示すオーバーロード解決のルールを適用または適用するだけです。

これは、C#仕様のセクション7.3.6に次のように記載されています。

数値の昇格は明確なメカニズムではなく、事前定義された演算子に過負荷解決を適用する効果です。

それは例で説明を続けます:

数値昇格の例として、二項*演算子の事前定義された実装を検討してください。

int演算子*(int x、int y);

uint演算子*(uint x、uint y);

長い演算子*(長いx、長いy);

ulong演算子*(ulong x、ulong y);

float演算子*(float x、float y);

二重演算子*(double x、double y);

10進演算子*(10進x、10進y);

この演算子のセットに過負荷解決規則(§7.5.3)を適用すると、オペランドタイプから暗黙的な変換が存在する最初の演算子が選択されます。たとえば、b * s(bはバイト、sは短い)の場合、過負荷解決により、演算子*(int、int)が最適な演算子として選択されます。

3
Mzn

実際、あなたの質問は少し注意が必要です。この仕様が言語の一部である理由は...彼らが言語を作成したときにその決定を下したからです。これは残念な答えのように聞こえますが、それはまさにその通りです。


しかし、本当の答えはおそらく1999年から2000年の当時の多くの文脈決定を伴うでしょう。 C#を作成したチームは、これらすべての言語の詳細についてかなり活発な議論を行ったと確信しています。

  • .。
  • C#は、シンプルでモダンな汎用のオブジェクト指向プログラミング言語を目的としています。
  • ソースコードの移植性は、プログラマーの移植性と同様に非常に重要です。特に、CおよびC++に既に精通しているプログラマーにとってはなおさらです。
  • 国際化のサポートは非​​常に重要です。
  • .。

上記の引用はウィキペディアC# からのものです

これらの設計目標はすべて、彼らの決定に影響を与えた可能性があります。たとえば、2000年には、ほとんどのシステムがすでにネイティブ32ビットであったため、算術演算を実行するときに32ビットで変換されるため、変数の数をそれよりも少なく制限することを決定した可能性があります。これは一般的に遅いです。

その時点で、あなたは私に尋ねるかもしれません。それらのタイプに暗黙の変換がある場合、なぜそれらを含めたのですか?上で引用したように、彼らの設計目標の1つは、移植性です。

したがって、古いCまたはC++プログラムの周りにC#ラッパーを作成する必要がある場合は、いくつかの値を格納するためにそれらの型が必要になる場合があります。その場合、これらのタイプは非常に便利です。

それは決定ですJavaはしませんでした。たとえば、C++プログラムと相互作用するJavaプログラムを作成すると、ushort値を受け取ります。 well Javaは短い(署名されている)だけなので、簡単に別の値に割り当てて正しい値を期待することはできません。

Javaはint(もちろん、32ビット)です。ここでメモリが2倍になりました。大したことではないかもしれませんが、代わりに、100000要素の配列をインスタンス化する必要があります。

実際、これらの決定は、あるものから別のものへのスムーズな移行を提供するために、過去と未来を見ることによって行われることを覚えておく必要があります。

しかし今、私は最初の質問から分岐していると感じています。


ですから、あなたの質問は良いものであり、おそらくあなたが聞きたかったものではないとわかっていても、私があなたにいくつかの答えをもたらすことができたと思います。

必要に応じて、以下のリンクからC#仕様の詳細を読むこともできます。あなたにとって興味深いかもしれないいくつかの興味深いドキュメントがあります。

整数型

チェックされている演算子とチェックされていない演算子

暗黙の数値変換テーブル

ちなみに、最初の質問には適切なリンクでかなり良い答えを提供してくれたので、おそらく報酬を与えるべきだと思います habib-os 。 :)

よろしく

2
ForceMagic