符号なしの数値が実装されているのはなぜですか?


12

マイクロプロセッサシステムが符号なしの数値を実装する理由がわかりません。より大きい、より小さい、など、符号付きとは異なるアルゴリズムが必要なので、コストは条件付きブランチの数の2倍であると思いますが、符号なしの数値が大きな利点であるアルゴリズムはまだありますか?

私の質問の一部は、コンパイラでサポートされるのではなく、命令セットに含まれる必要がある理由です。


27
基本的に、符号なしの数値は標準であり、負の数値を提供するために符号付きが実装されています。
ピーターB

37
世界のデータの多くは非数値です。非数値データは、符号なしの型を使用して簡単に操作できます。Javaに符号なし数値型がないことは失敗であり、非数値データ(圧縮など)を操作しなければならないものに多くのバグを引き起こします。
エリックエイド

6
@jtw Erikは、負のピクセル色や負の文字などは存在しないと言っています。そのため、そのために符号付き整数を使用するのは無駄であり、アドレス空間の半分を放棄することになります。
マーティンマート

26
ここに一人かどうかはわかりませんが、アプリケーションを開発するときに符号付き整数が必要になることは驚くほどまれです。ほとんどの場合、必要なのは(符号なし)自然数(通常は正のサイズ)または符号付き浮動小数点数です。例外は通貨のようなものですが、それらは非常にまれです。私にとって、符号なし整数は標準であり、符号付き整数は例外です!
トーマス

11
CPUの観点から見ると、ほとんどすべての数値は符号なしです。いくつかの命令はビットを符号付き(例:算術右シフト)として解釈できますが、実際には2の補数により、符号付き整数を符号なし整数として扱うことができます。つまり、CPUは両方をサポートするために特別な回路を必要としません。
トウモロコシ茎

回答:


39

符号なしの数値は、ビットシーケンスの1つの解釈です。また、アドレスとオペコードは単なるビットであるため、これは最も単純で、CPUの内部で最もよく使用される解釈です。メモリ/スタックのアドレス指定と演算は、マイクロプロセッサ、つまり処理の基礎です。抽象ピラミッドを上に移動すると、ビットのもう1つのよくある解釈は文字(ASCII、Unicode、EBCDIC)としてです。次に、IEEE浮動小数点、グラフィックス用のRGBAなど、他の解釈があります。これらはいずれも単純な符号付き数値ではありません(IEEE FPは単純ではなく、それらを使用した演算は非常に複雑です)。

また、符号なしの算術では、他のものを実装することは非常に簡単です(最も効率的でない場合)。その逆は真実ではありません。


3
EBCDICには「I」が1つだけあります。
ルスラン

4
@Ruslan-しかし、2つあるように発音されます。<g>
ピートベッカー

5
@PeteBeckerいいえ、そうではありません。EBCDICはeb -see-dick と発音されます。
マイクナキス

19

比較演算のハードウェアコストの大部分は、減算です。比較で使用される減算の出力は、基本的に3ビットの状態です。

  • すべてのビットがゼロ(つまり、等しい条件)かどうか、
  • 結果の符号ビット
  • 減算のキャリービット(つまり、32ビットコンピューターの33番目の上位ビット)

減算演算後にこれら3ビットをテストする適切な組み合わせにより、すべての符号付き関係演算とすべての符号なし関係演算を判別できます(これらのビットは、オーバーフローの検出方法、符号付きvs符号なし)。同じ基本的なALUハードウェアを共有して、これらのすべての比較(減算命令は言うまでもありません)を、これらの3ビットの状態の最終チェックまで実行できます。そのため、余分なハードウェアはそれほど多くありません。

唯一の本当のコストは、命令セットアーキテクチャで比較の追加モードをエンコードする必要があることです。これにより、命令密度がわずかに低下する可能性があります。それでも、ハードウェアには、特定の言語で使用されない命令がたくさんあるのはかなり普通です。


1
符号なし数値の比較には、減算は必要ありません。これは、左から右へのビットごとの比較によって実現できます。
ジョナサンローゼンヌ

10
@JonathanRosenneしかし、それはプロセッサがそれを実装する方法ではありません。それどころか、2の補数のプロセッサがALUで減算(キャリー/ボローの有無にかかわらず)を実装しないことはほとんど考えられません。デザイナーの直後の考えは、この必要なALUを使用して、同じ石で別の鳥を殺すことです(比較)。比較は、結果がレジスタファイルに書き戻されない単純な減算になります。
Iwillnotexist Idonotexist

4
+1:これは質問に対する正しい答えです。要約:既にsignedを実装している場合、無署名の操作の実装はほとんど無料です
ペリアタブレアッタ16年

10
@PeriataBreattaまた、逆に動作します。最近のCPUの符号付き数値と符号なし数値はほとんど同じであり、OPが認識しなかった主なポイントです。比較命令も符号付きと符号なしで同じです-これが、2の補数が符号付き整数の戦争に勝った理由の1つです:)
Luaan

3
@svidgen>は、他の答えが言ったように、逆に機能します。主な懸念事項は、基本的にすべて(メモリアドレス、io /ポート、文字表現など)に使用される符号なしの数値です。符号付きの数値は、符号なしで使用するとすぐに安くなり、まれな場合に便利です。
スペクトル

14

常に をカウントする必要がある場合>= 0、符号付き整数を使用して、カウントスペースを不必要に半分に削減するためです。

データベーステーブルに配置する自動インクリメントINT PKを考慮してください。そこで符号付き整数を使用する場合、テーブルには同じフィールドサイズの場合と同じ数のレコードがHALFとして格納され、メリットはありません

または、RGBaカラーのオクテット。この自然な正の数の概念を負の数で不自然に数え始めたくありません。符号付きの数字は、メンタルモデルを破壊するか、スペースを半分にします。符号なし整数は概念に一致するだけでなく、2倍の解像度を提供します。

ハードウェアの観点からは、符号なし整数は単純です。それらはおそらく、計算を実行するのに最も簡単なビット構造です。そして、間違いなく、コンパイラで整数型(または浮動小数点でさえ!)をシミュレートすることにより、ハードウェアを簡素化できました。では、なぜ符号なし整数符号付き整数の両方がハードウェアに実装されているのですか?

まあ... パフォーマンス!

ソフトウェアよりもハードウェアで符号付き整数を実装する方が効率的です。ハードウェアは、単一の命令でどちらのタイプの整数でも計算を実行するように指示できます。そして、それは非常に良いことです。なぜなら、ハードウェアは多少なりとも並行してビットを破壊するからです。ソフトウェアでそれをシミュレートしようとすると、「シミュレート」することを選択した整数型は、間違いなく多くの命令を必要とし、著しく遅くなります。


2
これらの行に沿って、配列の境界チェックを行う際の操作を省くことができます。符号なし整数を使用する場合は、指定されたインデックスが配列サイズよりも小さいことを確認するだけです(負にできないため)。
-riwalk

2
@ dan04それは確かに可能です...しかし、0または1から始まる自動インクリメントintを使用している場合(これはかなり一般的な慣行です)、利用可能な数の半分の使用を排除しました。そして、おそらく-2 ^ 31(または何でも)でカウントを開始できますが、IDスペースの真ん中に潜在的な「エッジ」ケースがあります。
svidgen

1
ただし、フィールドを半分に切ることは、ちょっとした議論のようなものです。アプリで20億を超える必要がある場合や、40億を超える必要がある場合があります。
corsiKa

1
@corsiKa:そのため、4を超える数が必要な場合は、8、16などが必要になる可能性があります。どこで終了しますか?
whatsisname

1
通常、@ whatsisnameでは、8、16、32、または64ビットの整数型を使用します。ほとんどの場合、符号付きバイト内の制限された範囲の31ビットの正の整数スペースの代わりに32ビットすべてを取得するので、符号なしの方が良いと言えます。
corsiKa

9

質問は2つの部分で構成されています。

  1. 符号なし整数の目的は何ですか?

  2. 符号なし整数は問題の価値がありますか?

1.符号なし整数の目的は何ですか?

符号なしの数値は、非常に単純に、負の値が無意味な量のクラスを表します。確かに、「私はリンゴをいくつ持っていますか?」という質問に対する答えを言うかもしれません。あなたが誰かにリンゴを借りている場合は負の数になるかもしれませんが、「私はどれくらいのメモリを持っていますか?」という質問についてはどうですか?-負の量のメモリを持つことはできません。そのため、符号なし整数はそのような量を表すのに非常に適しており、符号付き整数よりも2倍の正の値の範囲を表すことができるという利点があります。たとえば、16ビットの符号付き整数で表現できる最大値は32767ですが、16ビットの符号なし整数では65535です。

2.符号なし整数は問題の価値がありますか?

符号なし整数は実際には問題を表すものではないため、その価値はあります。お分かりのように、彼らは余分な「アルゴリズム」のセットを必要としません。それらの実装に必要な回路は、符号付き整数の実装に必要な回路のサブセットです。

CPUには、符号付き整数用の乗算器と符号なし整数用の異なる乗算器がありません。乗算器は1つだけで、動作の性質に応じてわずかに異なる方法で動作します。符号付き乗算をサポートするには、符号なしよりわずかに多くの回路が必要ですが、とにかくサポートする必要があるため、符号なし乗算は実質的に無料で提供され、パッケージに含まれています。

加算と減算に関しては、回路にまったく違いはありません。整数のいわゆる2の補数表現を読むと、整数の性質に関係なく、これらの操作をまったく同じ方法で実行できるように巧妙に設計されていることがわかります。

比較も同じように機能します。これは結果の減算と破棄にすぎないため、唯一の違いは条件分岐(ジャンプ)命令にあり、CPUの異なるフラグを調べることで動作します。先行(比較)命令。この回答:https : //stackoverflow.com/a/9617990/773113では、Intel x86アーキテクチャでの動作の説明を見つけることができます。条件付きジャンプ命令の符号付きまたは符号なしの指定は、検査するフラグによって異なります。


1
私の質問はこれをすべて仮定しました、アルゴリズムにより、iはより大きいなどのルールが異なっていたことを意味しました。私が見るコストは、余分な指示がたくさんあることです。高レベルのプログラムがデータをビットのパターンとして見たい場合、これはコンパイラによって簡単に実装できます
-jtw

3
@jtw-しかし、ポイントは、これらの追加の命令が実際符号付き数値に必要な命令に非常に類似しており、それらに必要なほとんどすべての回路を共有できることです。両方のタイプを実装する追加費用はほとんどゼロです。
ペリアータブレアッタ

1
はい、それは私の質問に答えます、余分な分岐命令を追加することはわずかな費用で来ます、そして、それらはしばしば実際に役立ちます
-jtw

1
「除算と乗算に関しては、符号なしの演算では追加の処理が必要です」と思います。乗算と除算は、符号なしの値を使用すると簡単になります。符号付きオペランドを処理するには、追加の処理が必要です。
コーディグレー

@CodyGray誰かがこれを言うために現れることを知っていました。もちろん、あなたは正しいです。これは私の文の背後にある理由です。簡潔にするために最初は省略しました。CPUは符号なしバージョンの乗算と除算を提供できない可能性があります。実際のところ、符号付きの乗算と除算は必須です。符号なしはオプションです。したがって、符号なし提供される場合、これはわずかに多くの回路を必要とするものとみなすことができます。
マイクNakis

7

マイクロプロセッサは本質的に署名されていません。符号付きの数値は実装されたものであり、その逆ではありません。

コンピューターは符号付きの数字がなくても問題なく機能しますが、負の数を必要とする人間であるため、符号付きが発明されました。


4
多くのマイクロプロセッサには、さまざまな操作のための署名付きおよび署名なしの命令があります。
whatsisname

1
@whatsisname:それは逆です。多くのマイクロプロセッサには、未署名の命令しかありません。いくつかは、説明書に署名しました。これは、2の補数演算ではビット値が天候に関係なく同じであるため、数値は符号付きまたは符号なしであり、数値の読み取り方法は解釈の問題であるため、コンパイラ機能として実装する方が簡単です。一般に、プログラマーがコンパイラーを使用しないと想定している古いマイクロのみが、アセンブリコードを読み取り可能にするための派手な署名付き命令を持っています。
スリーブマン

3

ストレージに簡単に利用できるもう1つのビットがあり、負の数を心配する必要がないためです。それ以上のものはありません。

ここで、この余分なビットが必要になる場所の例を必要とする場合、見ればたくさん見つかります。

私のお気に入りの例は、チェスエンジンのビットボードです。チェス盤には64個のマスがありunsigned long、動きの生成を中心としたさまざまなアルゴリズムに最適なストレージを提供します。バイナリ演算(およびシフト演算!!)が必要であるという事実を考慮すると、MSBが設定されている場合にどのような特別なことが起こるかを心配する必要がない方が簡単な理由は簡単にわかります。符号付きlongで実行できますが、符号なしを使用する方がはるかに簡単です。


3

純粋な数学のバックグラウンドを持っているため、これは興味のある人にとっては少し数学的なものです。

8ビットの符号付きおよび符号なし整数で開始する場合、負の整数を表すために2の補数が使用される場合、加算と乗算に関する限り、基本的に256を法とする整数です(そしてこれはすべての現代のプロセッサがそれを行う方法です) 。

物事の違いは2つの場所にあります。1つは比較演算です。ある意味で、256を法とする整数は数字の円と最もよく考えられます(12を法とする整数が旧式のアナログ文字盤で行うように)。数値比較(x <y)を意味のあるものにするために、どの数値が他の数値よりも小さいかを判断する必要がありました。数学者の観点から、256を法とする整数を何らかの形ですべての整数のセットに埋め込みたいと思います。バイナリ表現がすべてゼロである8ビット整数を整数0にマッピングするのは明らかです。その後、他のマップに進み、「0 + 1」(レジスタ、たとえばaxをゼロにし、「inc ax」を介して1ずつインクリメントした結果)が整数1になるようにします。-1でも同じことができます。たとえば、「0-1」を整数-1にマッピングし、「0-1-1」をマッピングします。整数-2に。この埋め込みが関数であることを確認する必要があるため、単一の8ビット整数を2つの整数にマッピングすることはできません。そのため、これは、すべての数値を整数のセットにマッピングすると、0より小さい整数と0より大きい整数とともに0が存在することを意味します。8ビット整数でこれを行うには、本質的に255の方法があります( 0から-255までの最小値まで)。次に、「0 <y-x」の観点から「x <y」を定義できます。

ハードウェアサポートが賢明な2つの一般的なユースケースがあります。0より大きいすべてのゼロ以外の整数を持つものと、0を中心とした約50/50の分割を持つものです。そして、操作の前にこれを行う必要があり、現代のソフトウェアの明示的な例を考えることができないほど、この必要性は非常にまれです(16ビットなどのより大きな仮数で作業することができるからです)。

もう1つの問題は、8ビット整数を16ビット整数の空間にマッピングすることです。-1は-1になりますか?これは、0xFFが-1を表す場合に必要なものです。この場合、符号拡張は賢明なことであるため、0xFFは0xFFFFになります。一方、0xFFが255を表すことを意図していた場合は、0xFFFFではなく、255にマッピングされるため、0x00FFにマッピングされます。

これは、「シフト」操作と「算術シフト」操作の違いでもあります。

しかし、最終的には、ソフトウェアのintは整数ではなく、バイナリの表現であり、表現できるのは一部のみであるという事実に帰着します。ハードウェアを設計する場合、ハードウェアでネイティブに行う方法を選択する必要があります。2の補数では加算と乗算の演算が同一であるため、このように負の整数を表すことは理にかなっています。次に、バイナリ表現が表す整数に依存する操作の問題のみです。


私は数学的なアプローチが好きですが、特定の大きなサイズへの昇格だけを考えるのではなく、無限長の2進数の演算に関して一般化する方が良いと思います。右端のk桁が0で、結果の右端のk桁が1である任意の数から1を引きます。無限のビット数で数学を実行すると、すべてのビットが1になることを帰納法で証明できます。数学では、数値の下位ビット以外はすべて無視されます。
-supercat

2

既存の符号付き整数を使用してCPU設計に符号なし整数を追加するための実装コストを調べてみましょう。

一般的なCPUには、次の算術命令が必要です。

  • ADD(2つの値を追加し、操作がオーバーフローした場合にフラグを設定します)
  • SUB(1つの値を別の値から減算し、さまざまなフラグを設定します。これらについては以下で説明します)
  • CMP(基本的に「SUBで結果を破棄し、フラグのみを保持する」)
  • LSH(左シフト、オーバーフロー時にフラグを設定)
  • RSH(右シフト、1がシフトアウトされた場合にフラグを設定)
  • フラグからのキャリー/ボローイングを処理する上記のすべての命令のバリアント。したがって、CPUレジスタよりも大きなタイプで操作するために命令を簡単にチェーン化できます。
  • MUL(乗算、フラグの設定など-普遍的には利用できません)
  • DIV(分割、フラグの設定など-多くのCPUアーキテクチャにはこれがありません)
  • 小さい整数型(16ビットなど)から大きい型(32ビットなど)に移動します。符号付き整数の場合、これは通常MOVSX(符号拡張で移動)と呼ばれます。

論理的な指示も必要です。

  • ゼロ分岐
  • 大枝
  • 少ない分岐
  • オーバーフロー時に分岐
  • 上記のすべての否定バージョン

符号付き整数比較で上記の分岐を実行するための最も簡単な方法は、SUB命令に次のフラグを設定させることです。

  • ゼロ。減算の結果がゼロの場合に設定します。
  • オーバーフロー。減算が最上位ビットから値を借用した場合に設定します。
  • 符号。結果の符号ビットに設定します。

次に、算術分岐は次のように実装されます。

  • ゼロで分岐:ゼロフラグが設定されている場合
  • 少ない分岐:符号フラグがオーバーフローフラグと異なる場合
  • より大きい分岐:符号フラグがオーバーフローフラグと等しく、ゼロフラグがクリアされている場合。

これらの否定は、それらの実装方法から明らかに続くはずです。

そのため、既存の設計では、これらすべてを符号付き整数に既に実装しています。次に、符号なし整数を追加するために必要なことを考えてみましょう。

  • ADD-ADDの実装は同じです。
  • SUB-フラグを追加する必要があります。レジスタの最上位ビットを超えて値が借用されると、キャリーフラグが設定されます。
  • CMP-変わらない
  • LSH-変わらない
  • RSH-符号付き値の右シフトは、最上位ビットの値を保持します。符号なしの値については、代わりにゼロに設定する必要があります。
  • MUL-出力サイズが入力と同じ場合、特別な処理は必要ありません(x86に特別な処理がありますが、これはレジスタペアへの出力があるためですが、この機能は実際にはめったに使用されないため、符号なしの型よりもプロセッサから除外するより明白な候補)
  • DIV-変更は不要
  • 小さいタイプから大きいタイプに移動します-MOVZXを追加する必要があり、ゼロ拡張で移動します。MOVZXの実装は非常に簡単です。
  • ゼロで分岐-変更なし
  • 少ない分岐-キャリーフラグが設定されている場合にジャンプします。
  • より大きい分岐-キャリーフラグとゼロの両方がクリアされている場合にジャンプします。

いずれの場合も、変更は非常に簡単であり、回路の小さなセクションをオンまたはオフにするか、新しいとにかく命令の実装。

したがって、無署名の命令を追加するコストは非常に小さいです。なぜそうするべきかについて、メモリアドレス(および配列内のオフセット)は本質的に符号なしの値であることに注意してください。プログラムはメモリアドレスの操作に多くの時間を費やすので、それらを正しく処理する型を持っていると、プログラムを作成しやすくなります。


ありがとう、これは私の質問に答えます、コストは小さく、指示は頻繁に役立ちます
-jtw

1
符号なし倍精度乗算は、多精度演算を行う場合に不可欠であり、RSA暗号化を実行する場合、全体的な速度を2倍以上向上させるのにおそらく適しています。また、除算は符号付きの場合と符号なしの場合で異なりますが、符号なしの場合は簡単で、除算は非常にまれで遅いため、少数の命令を追加してもそれほど害はないため、最も簡単なこと符号なし除算のみを実装することですそして、それをいくつかの記号処理ロジックでラップします。
supercat

2

符号なしの数値は、主にラッピング代数リングが必要な状況を処理するために存在します(16ビットの符号なしの型の場合、6565の合同の整数のリングになります)。値を取り、モジュラスより少ない量を追加すると、2つの値の差が追加された量になります。実際の例として、ユーティリティメーターが月の初めに9995を読み取り、23ユニットを使用する場合、メーターは月末に0018を読み取ります。代数リング型を使用する場合、オーバーフローに対処するために特別なことをする必要はありません。0018から9995を引くと、0023、正確には使用されたユニットの数が得られます。

Cが最初に実装されたマシンであるPDP-11には、符号なし整数型はありませんでしたが、符号付き型は、65535と0ではなく、32767と-32768の間をラップするモジュラー演算に使用できます。ただし、プラットフォームは物事をきれいにラップしませんでした。実装がPDP-11で使用される2の補数整数をエミュレートする必要があることを要求するのではなく、言語は代わりに、主に代数リングとして振る舞わなければならなかった符号なしの型を追加し、符号付き整数型がオーバーフローの場合に他の方法で振る舞うことを許可しました。

Cの初期には、32767(一般的なINT_MAX)を超えることができたが、65535(一般的なUINT_MAX)を超えることができなかった多くの量がありました。そのため、そのような量を保持するために符号なしの型を使用することが一般的になりました(たとえば、size_t)。残念ながら、言語には、正の範囲の余分なビットを持つ数値のように振る舞うタイプと、代数リングのように振る舞うタイプを区別するものはありません。代わりに、言語は「int」より小さい型を数値のように動作させ、フルサイズの型は代数リングのように動作させます。したがって、次のような関数を呼び出します。

uint32_t mul(uint16_t a, uint16_t b) { return a*b; }

with(65535、65535)は、int16ビット(つまり、1を返す)のシステムで1つの定義済み動作、int33ビット以上(0xFFFE0001を返す)の異なる動作、および「int」がどこかにあるシステムでの未定義の動作を持ちます。 between [gccは通常、INT_MAX + 1uとUINT_MAXの間の結果を伴う算術的に正しい結果を生成しますが、このような値で失敗する上記の関数のコードを生成する場合があることに注意してください!] あまり役に立たない。

それでも、一貫して数字のように、または常に代数環のように振る舞う型の欠如は、代数環型がいくつかの種類のプログラミングにほとんど不可欠であるという事実を変えません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.