2の補数表現における演算

2の補数表現のメリットとして、四則演算(特に加減算)が楽にできること があげられる。

※注意: 以下の説明は、2の補数表現に対してしか通用しない。 符号+絶対値方式の表現やげたばき表現には使えないので注意。

2の補数表現を使うのだから、全体のビット数は固定されているものとする。

加法

単に足して、あふれたら、あふれを無視する。 (あふれたら、というのは、全体のビット数を越えた時のこと。)

(注!!!: 2の補数表現でない場合は、あふれを無視してはいけない。)

8ビットで考える。 最初の例として 7 + 5 を計算してみる。 2進では、00000111 + 00000101 である。

             00000111
           + 00000101
           ----------
             00001100 (あふれがないので、これがそのまま答。)

次に、一方が負数であるような例として、4 + (-3)を考えよう。

             00000100           ← 4
           + 11111101           ← -3 の2の補数表現
           ----------           ← 普通の足し算のつもりで足す
            100000001           ← 9ビットになっている
            ↑あふれが出た。これを無視すると
             00000001 となるが、これはもちろん 10進法の1である。

あふれが出た時に、1番の上のケタの1をなぜ捨てて良いのだろうか? もともと、-3 の2の補数表現を求めたとき、 00000000 から無理に 3 を引いている。 無理に引く時に、仮に1番上に1があると考えて、 10000000 から 3 を引いた。 従って、元々、余分に100000000が足されていたと考えられる。 あふれが出た時にこの100000000 を捨てていることになる。

これで、符号を含めた加法ができた。

減法

減法については、引きたい数の符号を逆転させて加えればよい。 例えば、4 - 3 は 4 + (-3) として計算すればよい。

符号を逆転させるには、先程説明したように、全ビット反転してから1を 加えればよい。

そこで 4 + (-3) の計算を2進で書いてみると、

             00000100 + (-00000011)
           = [00000100 + {11111100 + 1} のあふれを無視したもの]

となるが、加算の順序を変えて

           = [{00000100 + 11111100} + 1 のあふれを無視したもの]

としてもよいはずである。

すなわち、x - y を計算したければ、

     x + (y の全ビットを反転したもの) + 1

を求め、あふれが発生したら、それを無視すればよい、ということになる。

このように、2の補数表現では減算も容易にできる。回路設計の観点から 言えば、加算回路とビット反転回路を用意しておけば、減算ができるこ とになる。

演習

8ビットの2の補数表現で表された2つの数の間で以下のような演算を行 ないなさい:

(i) 01001010 - 00101101 を以下のようにして計算する。

      01001010 + (-00101101) を計算すればよい。
      まず、-00101101 を求める。
      00101101 の全ビットを反転すると、???????? となる。
      これに1を加えると ???????? となる。これが -00101101 の2の補数表
      現。そこで、以下の筆算を行う。

         01001010
      +) ????????
      -----------
        ????????? ←これは9ビットになっているので、下位8ビットだけを
                    とると、答は ????????

(ii) 上と同様にして、00110100 - 01001011 を計算しなさい。 今度はあふれが出るだろうか? 出ないだろうか? 予想してから計算してみなさい。

算術オーバフロー

n ビットで表せる数は最大でも 2^n 通りしかないので、 算術演算の結果がnビットで表せる範囲を越えてしまうことがある。

例えば、8ビットの2の補数表現の場合、 -128から+127までしか表せないので、例えば 64+64を計算すると、 128になってしまい、この範囲を飛び出してしまう:

        01000000(2進)+01000000(2進)=10000000(2進)
                                    ↑
                                    符号ビットが立ってしまった!

            01000000
          +)01000000
          ----------
            10000000

また、-128 - 1 を計算しようとした場合も、 上の範囲を飛び出してしまう:

          10000000 - 00000001 = 10000000 + 11111111 のあふれを無視したもの
                              = 101111111 のあふれを無視したもの
                              =  01111111
                                 ↑
                                 符号ビットが0になってしまった
                                 (正の数として扱われてしまう!)

このように、算術演算の結果が、 現在考えている表現で表せる数の範囲を越えてしまうことを算術オーバフローと言う。

特に、絶対値が大きくなり過ぎて範囲を越える場合にそう言う。 あとで学ぶ小数の表現の場合は、 絶対値が小さくなり過ぎるために範囲をはみ出すケースもある。 その場合は、別の用語が使われるのが普通。

ケタのシフトと負数について

以前に、2進数では2をかけるとケタが1つ左へずれ、 2で割ると1つ右へずれる、ということを言った。 しかし、これは正の数を普通に2進表現した時の話であり、 負の数も考えた時には必ずしも成り立たないので注意しなければならない。

すなわち、2の補数表現で表された負の数を1ビット右にずらしても、 2で割ったことにはならない。

例えば、8ビットの2 の補数表現で -3 を表すと 11111101 で、 これを右に1ケタずらすと、1111110になる。 これでは7ビットしかないので、上に1ケタ補わないといけない。 しかし、0を補うと 01111110 となって、一番上の符号ビットが 0になるから、 正の数になってしまう。そこで、1を補うことにしたとしても、 11111110になる。これは、-2 を表す2の補数表現である。 つまり、-3 を1ケタ右にずらしても、-2にしかならず、 -3 を 2 で割った商である -1 にはならない。

しかし、この -2 という数は、 -3/2 = -1.5 を越えない最大の整数という意味を持つから、 このような右シフト(けたのずらしをシフトと言う)にもそれなりの意義がある。 また、正の数の場合には、2の補数表現を使っている場合にも、 右シフトはちゃんと2で割った結果と一致する。 ただし、1ケタ右にずらしたあと、先頭に0を補わなければならない。

そこで、2の補数表現に対して

という規則に従った右シフトがよく用いられる。 これを算術右シフトと言う。 細かく言えば、1ビットの算術右シフトである。

2ビット以上の算術右シフトは、単に1ビットの算術右シフトのくり返しである。 (だから、負の数に対してnビットの算術右シフトを行なうと、 最上位ケタに1を補う処理が n 回くり返されるので、 先頭にn個の1が補われることになる。)

算術右シフトでは、 右シフトをしても符号ビットは変化しないことがポイントである。

これに対して、正負を考えず、単に右にシフトして、 最上位ケタに0を補うシフトを(1ビットの)論理右シフトと言う。 2ビット以上の論理右シフトは単に1ビットの論理右シフトのくり返しである。

では、左シフトの場合はどう考えるべきだろうか。

8ビットの2の補数表現を使っているとして、 10進の3にあたる 00000011 を左にずらすと、 最下位に0を補うことになり、000000110となるが、 全部で9ケタになってしまいまうから、 最上位のケタを消すのが自然である。 つまり、左シフトの結果は 00000110 とすべきだろう。 これは10進数で言えば 6 (= 3×2) である。

このように、左にケタをずらして、左にはみ出した部分は捨てて、 右に0を補う形のシフトを考えるのが普通である。 これを論理左シフトと言う。

ところが、01111111(これは10進の127)を論理左シフトすると、 11111110 になってしまう。 これは、127×2を計算した結果、 8ビットの2の補数表現で表せる範囲を越えた数254になったと考えられる。 11111110を無理に2の補数表現と見なすと負の数になってしまう。

では、左シフトについても、 符号ビットを変化させないようなシフトを考えたらどうか、 と思われるかも知れないが、 そういうシフトは実際には特に役立たないようである。 上の例だと、11111110 というパターンができたあと、 無理矢理符号ビットを0にしてやると01111110になり、 これは10進の126であるが、 こういう計算は大して面白くなさそうに思える。

別のパターンで考えると、01000000 (10進法の64)を論理左シフトしてやると、 10000000 になるが、 これの符号ビットを無理矢理0にしてやると、 00000000 となり、0 になってしまう。 このようなことから、左シフトの場合には、 論理左シフトだけを考え、算術左シフトという言葉は使われないようである。 だから、単に左シフトと言えば、論理左シフトを指すと考えてよい。

まとめて言えば、2の補数表現において、1ビット左シフトする場合は、 1けた左にずらして、1番上のケタはすてる。 そして、1番右のケタに0を補う。

例えば、10110010 を1ビット(論理)左シフトすると、

          10110010
             ↓ずらす
         10110010
         ↓一番上の1をすてる
          0110010
         ↓一番右のケタに0を補う
          01100100
          これが答となる。

C 言語のプログラムでは、x を n ビット右シフトする演算は x >> n と書く。 これは算術右シフトである。 同じく、nビットの左シフトは x << n になる。 論理右シフトを表す式は C 言語にはない。 その代り、変数 x の型を「符号なしの整数」と宣言しておけば、 x >> n で論理右シフトが行われる。 Java 言語では、論理右シフトと算術右シフトの区別がある。 算術右シフトはJavaでは x >>> n と表される。 (蛇足ながら、Java 言語には「符号なしの整数」という型はなく、 整数はすべて2の補数表現で表された整数である。)

げたばき表現に対してシフトを行うとどうなるだろうか。

げたばき表現についても、1ケタ右にずらすことで2での割算はできないし、 1ケタ左にずらすことで2を掛けることはできない。

例えば、8 ビット使い、128をげたとしたげたばき表現で、 -3 を表すと、-3 + 128 = 125の2 進表現と同じだから、 01111101 になるが、これを1ケタ右にずらすと00111110 となり、 これは、62-128 = -66 のげたばき表現になってしまう。