web-dev-qa-db-ja.com

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

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

私の質問の一部は、コンパイラによってサポートされるのではなく、なぜそれらが命令セットに含まれる必要があるのか​​ということです。

12
jtw

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

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

39
Kristian H

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

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

減算操作後にこれら3つのビットをテストする適切な組み合わせにより、すべての符号付き関係演算とすべての符号なし関係演算を決定できます(これらのビットは、オーバーフローが検出される方法、符号付きと符号なしの比較にもなります)。同じ基本ALUハードウェアを共有して、これらのすべての比較(減算命令は言うまでもありません)を実装できます。これらの3ビットの状態の最終チェックまで、必要な関係比較ごとに異なります。したがって、追加のハードウェアはそれほど多くありません。

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

19
Erik Eidt

なぜなら、何かをカウントする必要がある場合はalways>= 0、符号付き整数を使用して、カウントスペースを不必要に半分にします。

データベーステーブルに配置する可能性のある、自動インクリメントされたINT PKを検討してください。そこで符号付き整数を使用すると、テーブルには同じ数のレコードに対してHALFと同じ数のレコードcouldが格納され、メリットはありません。

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

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

ええと... パフォーマンス!

ソフトウェアよりもハードウェアで符号付き整数を実装する方が効率的です。ハードウェアは、1つの命令でいずれかのタイプの整数を計算するように指示できます。そして、それはvery goodです。これは、ハードウェアがビットをまとめて破壊するためです多かれ少なかれ並行して。ソフトウェアでそれをシミュレートしようとする場合、「シミュレート」するように選択した整数型は、間違いなくmany命令を必要とし、noticeably遅くなります。

14
svidgen

あなたの質問は2つの部分で構成されています:

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

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

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

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

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

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

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

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

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

9
Mike Nakis

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

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

7
Pieter B

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

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

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

3
riwalk

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

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

状況が異なるのは2か所です。1つは比較演算です。ある意味では、256を法とする整数は、(昔ながらのアナログ時計の文字盤で12を法とする整数が行うように)数値の円と考えるのが最もよいでしょう。数値比較(is x <y)を意味のあるものにするために、どの数値が他の数値よりも小さいかを判断する必要がありました。数学者の観点からは、256を法とする整数を何らかの方法ですべての整数のセットに埋め込む必要があります。バイナリ表現がすべて0である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つの一般的な使用例があります。1つは0以外のすべての非整数が0より大きく、もう1つは約50/50が0に分割されています。他のすべての可能性は、追加の 'addそしてsub 'beforeオペレーション、そしてこれの必要性は私が現代のソフトウェアの明示的な例を考えることができないほど非常にまれです(より大きな仮数、たとえば16ビットで作業できるため)。

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

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

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

3
John Allsup

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

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

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

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

  • ゼロ分岐
  • より大きい枝
  • 少ないブランチ
  • オーバーフローで分岐
  • 上記すべての否定バージョン

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

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

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

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

これらの否定は、それらがどのように実装されるかから明らかに従うべきです。

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

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

いずれの場合も、変更はvery simpleであり、回路の小さなセクションをオンまたはオフにゲートするか、新しいフラグレジスタを追加することで実装できます。いずれにしても、命令の実装の一部として計算する必要があります。

したがって、符号なし命令を追加するコストは非常に小さいです。理由行う必要があるについては、メモリアドレス(および配列内のオフセット)は本質的に符号なしの値であることに注意してください。プログラムはメモリアドレスの操作に多くの時間を費やすため、プログラムを正しく処理するタイプを用意することで、プログラムの記述が容易になります。

2
Periata Breatta

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

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

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)は、intが16ビット(つまり1を返す)であるシステムで1つの定義された動作、intが33ビット以上(0xFFFE0001を返す)である別の動作、および"int"が中間にあるシステムでの未定義の動作[gccは通常で計算結果が正しく、INT_MAX + 1uとUINT_MAXの間の結果が得られますが、上記の関数のコードを生成する場合があります。そのような値で失敗します!]。あまり役に立たなかった。

それでも、一貫して数値のように、または一貫して代数環のように動作する型がなくても、ある種のプログラミングでは代数環の型がほとんど不可欠であるという事実は変わりません。

2
supercat