コンピュータ内部のデータ型と2の補数表現を解説

現在のコンピュータでは、負の数を表現する際に、2の補数表現を利用します。

2の補数表現においては、最上位ビット(MSB, Most Significant Bit)が符号ビットとなり、MSBが1の場合に負の数を表します。

本記事の内容

  • コンピュータ上のデータ型の基礎
  • 2の補数表現の原理と導出
  • オーバーフローの仕組み
  • 浮動小数点数の表現(IEEE 754)
  • Pythonでの動作確認

コンピュータ上のデータ型

PythonのnumpyやC言語では、int64int32uint64などのデータ型を指定します。データ型の本質は、メモリやCPU上でデータをどのように表現し、扱うかを規定するものです。

例えば、32bit整数の10は次のように表現されます。

$$ 10_{10} = \underbrace{0000\;0000\;0000\;0000\;0000\;0000\;0000\;1010}_{32 \text{ bits}} $$

基本的に、CPUはデータを固定長のビット列として扱います。

2の補数表現

原理

$n$ ビットの2の補数表現では、数値 $N$ は次のように解釈されます。

$$ N = -d_{n-1} \cdot 2^{n-1} + \sum_{k=0}^{n-2} d_k \cdot 2^k $$

ここで $d_{n-1}$ は符号ビット(MSB)です。MSBが0なら非負、1なら負の数です。

表現範囲

$n$ ビットの2の補数で表現できる範囲は、

$$ -2^{n-1} \leq N \leq 2^{n-1} – 1 $$

データ型 ビット数 最小値 最大値
int8 8 $-128$ $127$
int16 16 $-32{,}768$ $32{,}767$
int32 32 $-2{,}147{,}483{,}648$ $2{,}147{,}483{,}647$
uint8 8 $0$ $255$
uint32 32 $0$ $4{,}294{,}967{,}295$

2の補数の求め方

ある2進数 $X$ の負の値 $-X$ を求めるには、

  1. $X$ を2進数で表現する
  2. 全ビットを反転する(NOT演算)
  3. 得られた数に1を加える

数学的には、$n$ ビットの場合、

$$ -X = \overline{X} + 1 = 2^n – X $$

ここで $\overline{X}$ はビット反転(1の補数)です。

具体例: 5の2の補数表現(8ビット)

$$ \begin{align} 5_{10} &= 0000\;0101_2 \\ \overline{5} &= 1111\;1010_2 \quad (\text{ビット反転}) \\ -5_{10} &= 1111\;1011_2 \quad (+1) \end{align} $$

検算: $-128 + 64 + 32 + 16 + 8 + 0 + 2 + 1 = -5$ で正しいです。

オーバーフロー

固定長のビット数では、演算結果が表現範囲を超えるとオーバーフローが発生します。

8ビット符号付き整数の場合、$127 + 1$ を計算すると、

$$ 0111\;1111 + 0000\;0001 = 1000\;0000 = -128 $$

最大値に1を足すと、最小値に「ラップアラウンド」します。これは $\bmod 2^n$ の演算として理解できます。

浮動小数点数(IEEE 754)

実数を表現するには浮動小数点数が使われます。IEEE 754規格では、数値を次の形式で表します。

$$ (-1)^s \times 1.f \times 2^{e – \text{bias}} $$

ここで、 * $s$: 符号ビット(1ビット) * $e$: 指数部 * $f$: 仮数部(小数部分)

精度 総ビット 符号 指数 仮数 バイアス
単精度(float32) 32 1 8 23 127
倍精度(float64) 64 1 11 52 1023

マシンイプシロン

浮動小数点数の相対精度の限界をマシンイプシロン $\epsilon$ と呼びます。

$$ \epsilon = 2^{-(p-1)} $$

ここで $p$ は仮数部のビット数+1です。float64では $\epsilon \approx 2.22 \times 10^{-16}$ です。

Pythonでの動作確認

import numpy as np
import matplotlib.pyplot as plt

# 2の補数表現
print("=== 2の補数表現 ===")
for dtype in [np.int8, np.int16, np.int32]:
    info = np.iinfo(dtype)
    print(f"{dtype.__name__:6s}: [{info.min:>12d}, {info.max:>12d}]")

# 2の補数の計算過程
def twos_complement(n, bits=8):
    """2の補数を計算して過程を表示"""
    if n >= 0:
        binary = format(n, f'0{bits}b')
        return binary

    pos = format(abs(n), f'0{bits}b')
    inverted = ''.join('1' if b == '0' else '0' for b in pos)
    result = format(int(inverted, 2) + 1, f'0{bits}b')

    print(f"  {abs(n):4d} = {pos}")
    print(f"  反転  = {inverted}")
    print(f"  +1   = {result}")
    return result

print("\n=== -5 の2の補数表現(8bit) ===")
twos_complement(-5, 8)

print("\n=== -42 の2の補数表現(8bit) ===")
twos_complement(-42, 8)

# オーバーフローの実演
print("\n=== オーバーフロー ===")
a = np.int8(127)
b = np.int8(1)
# numpy はオーバーフロー時にラップアラウンドする
result = np.int8(np.int16(a) + np.int16(b))
print(f"int8: {a} + {b} = {result} (オーバーフロー!)")

# 浮動小数点数の精度
print("\n=== 浮動小数点数の精度 ===")
for dtype in [np.float32, np.float64]:
    info = np.finfo(dtype)
    print(f"{dtype.__name__}: eps={info.eps:.2e}, "
          f"min={info.tiny:.2e}, max={info.max:.2e}")

# 浮動小数点数の丸め誤差の可視化
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# int8 の全値と2の補数表現
values_int8 = np.arange(256, dtype=np.uint8)
signed_values = values_int8.astype(np.int8)
axes[0].plot(values_int8, signed_values, 'b.', markersize=2)
axes[0].set_xlabel('Unsigned Value (bit pattern)')
axes[0].set_ylabel('Signed Value (int8)')
axes[0].set_title("Two's Complement Interpretation (8-bit)")
axes[0].axhline(0, color='gray', linestyle='--', alpha=0.5)
axes[0].axvline(128, color='red', linestyle='--', alpha=0.5, label='MSB=1')
axes[0].legend()
axes[0].grid(True)

# 浮動小数点数の分布
# float の分布は対数的に不均等
x = np.linspace(0, 1, 10000).astype(np.float32)
gaps = np.diff(x)
axes[1].semilogy(x[:-1], gaps, 'b.', markersize=1)
axes[1].set_xlabel('Value')
axes[1].set_ylabel('Gap to Next Float')
axes[1].set_title('Float32 Distribution (gaps between consecutive values)')
axes[1].grid(True)

plt.tight_layout()
plt.show()

まとめ

本記事では、コンピュータ内部のデータ型と2の補数表現について解説しました。

  • 2の補数表現は $-X = \overline{X} + 1$ で求められ、加算回路で減算も実行できる
  • $n$ ビットの符号付き整数の範囲は $[-2^{n-1}, 2^{n-1}-1]$ である
  • オーバーフローは $\bmod 2^n$ のラップアラウンドとして理解できる
  • 浮動小数点数はIEEE 754規格で $(-1)^s \times 1.f \times 2^{e-\text{bias}}$ と表現される
  • マシンイプシロンが浮動小数点演算の相対精度の限界を決める