基数変換 -- 2進数・10進数・16進数
この講義では、3種類の進数の変換方法を習得し、16進数がなぜ2進数の「最良の省略表記」なのかを理解できます。
生活に身近な例え:複数の記数法
異なる進数は、異なる「単位系」のようなものだ。
人間は10進数に慣れている。それは「メートル」で身長を測るのに慣れているのと同じだ。
コンピュータは2進数を使う。それは「スイッチのオン/オフ状態」で考えるようなものだ。
一方、16進数はエンジニアの「速記記号」だ。長々とした2進数をより短い方法で表現する。
例を挙げよう。「サンプル」という言葉を3種類の「言語」で表現すると:
- 10進数という人間の言語:82 85 78 79 79 66
- 2進数という機械の言語:01010010 01010101 01001110 01001111 01001111 01000010
- 16進数という略記法:52 55 4E 4F 4F 42
同じ内容でも、2進数の表現の長さは16進数の4倍になる。
これが基数変換を学ぶ必要がある理由だ。
記数法の本質:桁の重みで展開する
すべての記数法の原理はまったく同じだ——各桁の数字に基数のべき乗を掛けて、すべて合計する。
数字42を例に、それが3種類の進数でそれぞれどのように解釈されるかを見てみよう:
| 記数法 | 基数 | 使用できる数字 | 表記 | 桁の重みで展開 |
|---|---|---|---|---|
| 10進数 | 10 | 0 ~ 9 | 42 | 4 x 101 + 2 x 100 = 42 |
| 2進数 | 2 | 0, 1 | 101010 | 1x25+0x24+1x23+0x22+1x21+0x20 = 42 |
| 16進数 | 16 | 0~9, A~F | 2A | 2 x 161 + 10 x 160 = 42 |
重要な規則:記数法の「基数」が各桁の重みを決める。
10進数の基数は10、一の位の重みは10^00=1、十の位は10^11=10、百の位は10^22=100。
2進数の基数は2、右から左への重みは 1, 2, 4, 8, 16, 32, 64, 128。
16進数の基数は16、右から左への重みは 1, 16, 256, 4096。
「桁の重みで展開する」を理解すれば、すべての基数変換が簡単になる。覚える必要があるのはただ一つ:各桁の数字 × 基数^位置番号 を合計するだけ。
方法1:10進数から2進数へ(2で割って余りを取る方法)
10進数の42を2進数に変換するとしよう。
手順は機械的だ:
- 42 を 2 で割ると、商 21 余り0
- 21 を 2 で割ると、商 10 余り1
- 10 を 2 で割ると、商 5 余り0
- 5 を 2 で割ると、商 2 余り1
- 2 を 2 で割ると、商 1 余り0
- 1 を 2 で割ると、商 0 余り1
- 余りを下から上に読む:101010
なぜこの方法は有効なのか?
2進数の本質は:N = d0x20 + d1x21 + d2x22 + ...
N を 2 で割ったとき、余りが d になる0(最下位桁)。商をさらに2で割って次の桁を得る。
この論理は、小学校で習う「10進数を10で割って一の位を取り出す」方法とまったく同じだ。
方法2:2進数から10進数へ(桁の重みで展開する方法)
2進数から10進数への変換はさらに簡単だ——重みの通りに直接足し合わせるだけだ。
2進数101010(右から左へ位置番号 0〜5):
| 位置 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|
| ビット | 1 | 0 | 1 | 0 | 1 | 0 |
| 重み(2^n) | 32 | 16 | 8 | 4 | 2 | 1 |
| 桁 × 重み | 1x32 | 0x16 | 1x8 | 0x4 | 1x2 | 0x1 |
1x32 + 0x16 + 1x8 + 0x4 + 1x2 + 0x1 = 42
16進数:2進数の「速記記号」
2進数の最大の問題は書くと長すぎる。
8ビットの値なら8個の数字、32ビットなら32個の数字を書くことになる。
これは人間が読むのに不便で、間違いも起こしやすい。
16進数はこの問題を解決する。重要な数学的偶然があるからだ:
242^4 = 16 → 2進数の4桁はちょうど16進数の1桁に対応する
2進数4桁と16進数1桁の対応表
| 2進数(4桁) | 10進数 | 16進数 | 2進数(4桁) | 10進数 | 16進数 |
|---|---|---|---|---|---|
| 0000 | 0 | 0 | 1000 | 8 | 8 |
| 0001 | 1 | 1 | 1001 | 9 | 9 |
| 0010 | 2 | 2 | 1010 | 10 | A |
| 0011 | 3 | 3 | 1011 | 11 | B |
| 0100 | 4 | 4 | 1100 | 12 | C |
| 0101 | 5 | 5 | 1101 | 13 | D |
| 0110 | 6 | 6 | 1110 | 14 | E |
| 0111 | 7 | 7 | 1111 | 15 | F |
この表が16進数の核心だ。
これを覚えてしまえば、2進数から16進数への変換はどれも「表を引いて該当文字を得る」機械的な操作になる。
例:2進数 11111111 を16進数に変換
8ビットの2進数を2つのグループに分け、各グループ4桁にする:
プログラミングでは、
0x0x 接頭辞は16進数を表す(例:0xFF)、0xFF),0b0b 接頭辞は2進数を表す(例:0b111111110b11111111)。これらの接頭辞はコンパイラに、これは10進数ではないと伝える。
インタラクティブデモ:3カラム同期コンバーター
次のコンバーターを使うと、3種類の進数間の対応関係をリアルタイムで確認できる。
いずれかの欄に数値を入力すると、残りの2つの欄は自動的に同期更新される。
2進数の欄の下にある色ブロックのハイライトに注目してほしい——これは2進数4桁が16進数1桁に対応するという関係を示している。
10進数 Decimal
2進数 Binary
16進数 Hex
試してみよう:10進数の欄に255を入力し、2進数の欄がオール1、16進数の欄がFFになるのを確認しよう。
これが「1バイトの最大値」だ。これを理解すれば、多くの概念がつながる。
コードデモ:完全な基数変換
例
def decimal_to_binary(n):
"""10進数から2進数へ:2で割って余りを取る方法、完全な手順を表示"""
if n == 0:
return "0"
steps = []
original = n
while n > 0:
remainder = n % 2
quotient = n // 2
steps.append((n, quotient, remainder))
n = quotient
result = ''.join(str(r) for _, _, r in reversed(steps))
print(f"{original} を2進数に変換:")
for val, quot, rem in steps:
print(f" {val:3d} ÷ 2 = {quot:3d} 余り {rem}")
print(f" → 結果: {result}")
return result
def binary_to_decimal(bin_str):
"""2進数から10進数へ:桁の重みで展開する方法"""
decimal = 0
length = len(bin_str)
print(f"2進数 {bin_str} の桁の重みによる展開:")
for i, ch in enumerate(bin_str):
power = length - 1 - i
if ch == '1':
value = 2 ** power
decimal += value
print(f" 桁{power}: 1 x 2^{power} = {value}")
print(f" → 合計: {decimal}")
return decimal
def bin_to_hex(bin_str):
"""2進数から16進数へ:4桁ごとに区切って表を引く"""
while len(bin_str) % 4 != 0:
bin_str = '0' + bin_str
hex_map = {
'0000':'0','0001':'1','0010':'2','0011':'3',
'0100':'4','0101':'5','0110':'6','0111':'7',
'1000':'8','1001':'9','1010':'A','1011':'B',
'1100':'C','1101':'D','1110':'E','1111':'F'
}
result = ''
groups = []
for i in range(0, len(bin_str), 4):
group = bin_str[i:i+4]
hex_char = hex_map[group]
groups.append(f"{group} → {hex_char}")
result += hex_char
print(f"2進数 {bin_str} を16進数に変換:")
for g in groups:
print(f" {g}")
print(f" → 0x{result}")
return result
def hex_to_bin(hex_str):
"""16進数から2進数へ:各桁を4桁に展開する"""
rev_map = {
'0':'0000','1':'0001','2':'0010','3':'0011',
'4':'0100','5':'0101','6':'0110','7':'0111',
'8':'1000','9':'1001','A':'1010','B':'1011',
'C':'1100','D':'1101','E':'1110','F':'1111'
}
hex_str = hex_str.upper()
result = ''
for ch in hex_str:
expanded = rev_map[ch]
result += expanded
print(f" {ch} → {expanded}")
# 先頭のゼロを除去(ただし最低1桁は残す)
result = result.lstrip('0') or '0'
print(f" → {result}")
return result
# 総合テスト:サンプル の文字 'R' の ASCII コード 82 を中心に
print("=" * 50)
print("テスト値: 82(ASCII 'R' from サンプル)")
print("=" * 50)
bin_result = decimal_to_binary(82)
print(f"82 → 2進数: {bin_result}\n")
dec_result = binary_to_decimal(bin_result)
print()
hex_result = bin_to_hex(bin_result)
print()
print("逆方向の検証:16進数 → 2進数")
hex_to_bin(hex_result)
print("\n" + "=" * 50)
print("テスト値: 255(1バイトの最大値)")
print("=" * 50)
bin_255 = decimal_to_binary(255)
print(f"2進数: {bin_255}")
hex_255 = bin_to_hex(bin_255)
print(f"16進数: 0x{hex_255}")
print("注意:255 の2進数は8桁すべて1で、16進数では2桁のFFになる")
発展:16進数から10進数への高速変換
10進数への変換方法は2つすでに身につけている:1つは桁の重みで展開する方法(各16進数の桁 × 16のべき乗)、もう1つは先に2進数へ変換してから10進数へ変換する方法だ。
しかし、もっと実用的な方法がもう1つある——2進数を「中間言語」として使う。
あらゆる基数変換は、2進数を橋渡しにして行える:
10進数と16進数の直接変換は計算量が大きいが、それぞれと2進数の変換はどちらも非常に簡単だからだ。
10進数→2進数:2で割って余りを取る。
16進数→2進数:表を引いて展開(各1桁を4桁にする)。
その他の拡張