現在のコンピュータでは、負の数を表現する際に、2の補数表現を利用します。
2の補数表現においては、最上位ビット(MSB, Most Significant Bit)が符号ビットとなり、MSBが1の場合に負の数を表します。
本記事の内容
- コンピュータ上のデータ型の基礎
- 2の補数表現の原理と導出
- オーバーフローの仕組み
- 浮動小数点数の表現(IEEE 754)
- Pythonでの動作確認
コンピュータ上のデータ型
PythonのnumpyやC言語では、int64、int32、uint64などのデータ型を指定します。データ型の本質は、メモリや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$ を求めるには、
- $X$ を2進数で表現する
- 全ビットを反転する(NOT演算)
- 得られた数に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}}$ と表現される
- マシンイプシロンが浮動小数点演算の相対精度の限界を決める