閉じる

はじめての補数表現:コンピュータがマイナスの数を扱うしくみ

コンピュータがマイナスの数を理解すると聞くと、少し不思議に感じるかもしれません。

電気信号は0か1しか表せないのに、どうやって-1や-128といった値を扱っているのでしょうか。

そのカギとなるのが補数表現です。

本記事では、2進数の基礎から1の補数・2の補数、そして実際のプログラムでどのように利用されているのかまで、順を追って丁寧に解説していきます。

補数表現とは何か

補数表現とは、2進数だけで負の数を表現するためのルールのことです。

特に現在のコンピュータでは、ほとんどの場合2の補数と呼ばれる方法で負の整数が表されています。

補数表現と2進数の関係

コンピュータ内部では、データはすべて0と1からなる2進数で扱われます。

正の整数であれば、単に2進数に変換すればよいので話は簡単です。

  • 5 → 2進数では 0101
  • 13 → 2進数では 1101

しかし、負の数はそのまま2進数にできません

-5という「記号付きの量」を、0と1のパターンだけで表現しなければならないからです。

ここで登場するのが補数という考え方です。

補数とは、ある基準となる数からの「差」を使って数を表す方法です。

10進数の世界でも、実は似たことをしています。

例えば、10進数で「9の補数」「10の補数」という考え方があります。

  • 1桁の世界で、3の9の補数は 9 – 3 = 6
  • 2桁の世界で、37の99の補数は 99 – 37 = 62

2進数でも同じような「補数」を使い、負の数を「ある基準からの差」として表すのが補数表現です。

なぜ補数表現が必要なのか

負の数を扱う方法は、補数表現以外にもいくつか考えられます。

代表的なものは次の3種類です。

  • 符号と絶対値方式 (Sign-Magnitude)
  • 1の補数
  • 2の補数

補数表現が必要とされる理由は、大きく次の2つです。

  1. 加算回路だけで減算もできるようにしたい
  2. 回路をできるだけ簡単に、かつ高速にしたい

もし「符号と絶対値方式」を採用すると、-5は「符号ビット=1、絶対値=5」として表現できますが、減算を行うときには専用の回路や複雑な処理が必要になります。

一方で、2の補数を使うと、負の数の表現のまま加算器に入れるだけで減算も実現できます

この性質のおかげで、CPUの内部構造がシンプルになり、高速に計算できるのです。

符号付き整数と符号なし整数の違い

コンピュータで整数を扱うとき、符号付き整数(signed)符号なし整数(unsigned)があります。

両者の違いは、ビットの解釈の仕方にあります。

符号なし整数は、すべてのビットを「値」として解釈します。

  • 8ビットの場合、範囲は 0 ~ 255
  • 2進数 11111111 は 255

符号付き整数は、最上位ビット(MSB)を「符号ビット」として用い、残りのビットと合わせて2の補数で負の数を表現します。

  • 8ビットの場合、範囲は -128 ~ 127
  • 2進数 11111111 は -1 (2の補数表現)

同じビット列でも、符号付きか符号なしかで意味が変わることが重要なポイントです。

1の補数と2の補数

補数表現には主に1の補数2の補数の2種類があります。

歴史的には1の補数を使ったマシンも存在しましたが、現在はほとんど2の補数に統一されています。

1の補数(1のビット反転)とは

1の補数は、すべてのビットを反転させたものとして定義されます。

0を1に、1を0に入れ替えるだけです。

例えば、8ビットの世界を考えます。

  • +5 を 2進数にすると 00000101
  • 1の補数を取ると、すべてのビットを反転して 11111010

このとき、「+5 の 1の補数」を「-5」と解釈する、というルールを採用すれば、負の数の表現として利用できます。

1の補数は「ビット反転」という単純な操作で求められるため、概念としては分かりやすい方法です。

2の補数(2の補数表現)とは

2の補数は、1の補数に1を加えたものとして定義されます。

あるいは、「2の補数での負の数 = その絶対値を2の補数で表したもの」とも言えます。

同じく、+5 (8ビット) の場合を見てみます。

  1. +5 を2進数にする: 00000101
  2. 1の補数を取る: 11111010
  3. 1を加える: 11111010 + 00000001 = 11111011

この最終結果 11111011 を、2の補数での -5と解釈します。

2の補数は、数学的には「ある基数(ここでは 2^n) から引いた値」としても理解できます。

8ビットの世界では 2^8 = 256 なので、

  • -5 の表現 = 256 – 5 = 251
  • 251 を2進数にすると 11111011 (さきほど求めたものと一致)

という関係があります。

1の補数と2の補数の違いと問題点

1の補数と2の補数には、次のような違いがあります。

種類負の数の求め方0の表現主な問題点
1の補数ビットをすべて反転+0 と -0 の2種類が存在0が2つあり、演算や比較が複雑になる
2の補数ビットを反転して1を足す0は1種類だけ範囲の非対称性(最小値だけ対応する正の数がない)

とくに1の補数の致命的な問題は、0が2種類(+0と-0)存在することです。

これはハードウェアや演算ルールを複雑にし、効率を下げる原因となります。

一方で2の補数では0は1種類だけなので、比較や加算・減算の処理がシンプルになります。

この性質から、現代のコンピュータでは2の補数が標準的に採用されています。

2の補数で負の数を表す方法

ここからは、実際に2の補数を使って負の数をどのように表現するのかを、具体的な手順と例で見ていきます。

正の数から2の補数で負の数を作る手順

ある正の整数 x から、-x を2の補数で表す手順は次のようになります。

  1. x を所定のビット数で2進数にする
  2. すべてのビットを反転する (1の補数)
  3. 1を加える

この3ステップだけです。

ポイントは、必ず固定のビット幅を意識することです。

8ビットなら8ビット、32ビットなら32ビットの範囲でビット反転と加算を行います。

具体例で見る2の補数

いくつかの例で、2の補数の表現を確認してみましょう。

ここでは8ビット(1バイト)の整数を考えます。

例1: -1 を表す

  1. 1 を2進数にする: 00000001
  2. ビットを反転: 11111110
  3. 1を加える: 11111110 + 00000001 = 11111111

したがって、8ビットの2の補数における -1 は 11111111です。

例2: -2 を表す

  1. 2 を2進数にする: 00000010
  2. 反転: 11111101
  3. +1: 11111110

よって、-2 は 11111110になります。

例3: -128 を表す

8ビットの世界では、最小値は -128です。

これは特別な値で、次のようになります。

  • 2の補数での -128 は 10000000

この値を「正の数に戻す」操作をしてみると、次のようになります。

  1. 2の補数表現: 10000000
  2. 1を引く (逆操作): 01111111
  3. ビット反転: 10000000

元に戻ってしまい、対応する正の値が存在しないことが分かります。

これが、2の補数での最小値だけは対応する正の値がないと言われる理由です。

2の補数での最小値と最大値

nビットの2の補数表現では、表現できる範囲は次のようになります。

  • 最小値: -2^(n-1)
  • 最大値: 2^(n-1) - 1

代表的なビット幅の場合を表にまとめると次のようになります。

ビット数最小値最大値
8ビット-128127
16ビット-3276832767
32ビット-21474836482147483647
64ビット-2^632^63 – 1

このように、負の側のほうが1つだけ広いのが2の補数の特徴です。

符号ビットと範囲の考え方

2の補数では、最上位ビット(MSB)が符号ビットとして機能します。

  • 符号ビットが 0 → 非負の値 (0以上)
  • 符号ビットが 1 → 負の値

ただし注意すべきなのは、「符号ビット」という特別なビットがあるわけではなく、あくまで2の補数のルールに則った結果として、MSBが符号を決めているということです。

2の補数と計算のしくみ

2の補数の優れた点は、加算回路だけで減算を行えることです。

この性質により、CPUの設計が大幅に簡略化されます。

2の補数での加算と減算

2の補数を使うと、減算は「負の数を足す」ことに変換できます

  • a – b = a + (-b)

ここで -b を2の補数で表現しておけば、あとは通常の2進数の足し算を行うだけで済みます。

例: 5 – 3 を8ビット2の補数で計算する

  1. 5 を表す: 00000101
  2. 3 を表す: 00000011
  3. 3 の2の補数(= -3)を求める
    1. 反転: 11111100
    2. +1: 11111101 (これが -3)
  4. 5 + (-3) を計算
    1. 00000101
    1. 11111101
      =00000010 (桁あふれの1は捨てる)

結果は 00000010 で、これは 10進数の 2 です。

つまり、5 – 3 = 2が正しく求まりました。

このように、加算器一つで加算と減算の両方をこなせることが、2の補数が広く使われる大きな理由です。

オーバーフローの判定方法

2の補数で演算を行うときに重要なのが、オーバーフローの検出です。

オーバーフローとは、結果がそのビット幅で表せる範囲を超えてしまうことです。

2の補数の世界では、次のような場合にオーバーフローが発生します

  • 2つの正の数を足して、負の数になってしまったとき
  • 2つの負の数を足して、正の数になってしまったとき

逆に、正と負を足した場合にはオーバーフローは起きません

例: 8ビットで 100 + 50

  • 100 → 01100100
  • 50 → 00110010

足し算の結果は 150 で、8ビットの2の補数で表せる範囲(-128 ~ 127)を超えています。

  • 実際の計算結果: 10010110

このビット列は、2の補数では負の数として解釈されます。

つまり、正 + 正 の結果が負になったため、オーバーフローが発生したと判定できます。

ビット演算と2の補数

プログラミングでは、ビットシフトやAND/OR/XORなどのビット演算を利用する場面がよくあります。

2の補数表現では、これらの演算の結果がどのように解釈されるかに注意が必要です。

左シフト(<<)

左シフトは、基本的に「2倍」に相当します。

  • 00000101 (5) を 1ビット左シフト → 00001010 (10)

ただし、符号付き整数に対して左シフトすると、符号ビットも動いてしまうため、オーバーフローや符号の反転が起きる可能性があります。

右シフト(>>)

右シフトには2種類あります。

  • 論理右シフト: 左を0で埋める (符号なし用)
  • 算術右シフト: 左を符号ビットで埋める (符号付き用)

2の補数で符号付き整数を扱う場合は、算術右シフトを使うことで、負の数も含めて「2で割った」ような振る舞いを実現できます。

プログラミング言語での符号付き整数と2の補数

多くのプログラミング言語では、符号付き整数型は2の補数表現を前提としています。

ただし、仕様書上は「実装依存」とされている場合もあり、言語や環境によっては細かい差異が存在します。

代表的な言語での扱いを簡単に見てみましょう。

C / C++

  • int, short, long などの符号付き整数は、実装上ほぼすべて2の補数
  • ビットシフト演算子 >>の挙動(算術か論理か)は型によって変わる
    • 符号付き整数: 通常は算術右シフト
    • 符号なし整数: 論理右シフト

Java

  • byte, short, int, long はすべて2の補数で定義
  • >> は論理右シフト、>> は算術右シフトとして明確に区別されている

Python

  • Pythonの int は理論上無限精度だが、内部で2の補数に基づくようなビット表現を利用している
  • ビット演算子(&, |, ^, <<, >>)は、数が無限長の2の補数で表されているかのように振る舞う

プログラムを書く際には、「この整数型は何ビットで、どの範囲を表せるのか」、「シフト演算は符号付きと符号なしでどう違うのか」を意識しておくと、オーバーフローや予期せぬ符号反転といったバグを防ぎやすくなります。

まとめ

補数表現、とくに2の補数は、コンピュータが負の整数を扱うための基盤となる仕組みです。

本記事で扱った内容を整理すると、次のようになります。

まず、コンピュータは0と1しか扱えないため、負の数を表現するには特別な工夫が必要でした。

そのために考案されたのが補数表現であり、とくに2の補数は、加算回路だけで減算も行えるという大きな利点から、現代のほぼすべてのCPUで採用されています。

1の補数では0が2種類存在するという問題がありましたが、2の補数ではその問題を解決し、表現範囲は -2^(n-1) から 2^(n-1)-1 までとなります。

最上位ビットは符号ビットとして機能し、0なら非負、1なら負の数として解釈されます。

また、2の補数表現を理解すると、加算・減算の内部動作、オーバーフローの判定、ビットシフトやビット演算の意味がより明確になり、C/C++ や Java、Python といった言語での整数処理をより安全かつ正確に行えるようになります。

プログラム中で「なぜこの値でオーバーフローしたのか」「ビットシフトしたらなぜ符号が変わったのか」といった疑問に出会ったときは、本記事で紹介した2の補数の考え方に立ち返ってみてください。

整数とビット列を結びつけて考えられるようになると、低レイヤの挙動がぐっと理解しやすくなります。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!