基数変換 -- 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進数100 ~ 9424 x 101 + 2 x 100 = 42
2進数20, 11010101x25+0x24+1x23+0x22+1x21+0x20 = 42
16進数160~9, A~F2A2 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進数に変換するとしよう。

手順は機械的だ:

  1. 42 を 2 で割ると、商 21 余り0
  2. 21 を 2 で割ると、商 10 余り1
  3. 10 を 2 で割ると、商 5 余り0
  4. 5 を 2 で割ると、商 2 余り1
  5. 2 を 2 で割ると、商 1 余り0
  6. 1 を 2 で割ると、商 0 余り1
  7. 余りを下から上に読む:101010
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):

位置543210
ビット101010
重み(2^n)32168421
桁 × 重み1x320x161x80x41x20x1

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進数
000000100088
000111100199
001022101010A
001133101111B
010044110012C
010155110113D
011066111014E
011177111115F

この表が16進数の核心だ。

これを覚えてしまえば、2進数から16進数への変換はどれも「表を引いて該当文字を得る」機械的な操作になる。

例:2進数 11111111 を16進数に変換

8ビットの2進数を2つのグループに分け、各グループ4桁にする:

1111 = F    1111 = F→ 結果:0xFF

プログラミングでは、0x0x 接頭辞は16進数を表す(例:0xFF)、0xFF),0b0b 接頭辞は2進数を表す(例:0b111111110b11111111)。これらの接頭辞はコンパイラに、これは10進数ではないと伝える。


インタラクティブデモ:3カラム同期コンバーター

次のコンバーターを使うと、3種類の進数間の対応関係をリアルタイムで確認できる。

いずれかの欄に数値を入力すると、残りの2つの欄は自動的に同期更新される。

2進数の欄の下にある色ブロックのハイライトに注目してほしい——これは2進数4桁が16進数1桁に対応するという関係を示している。

10進数 Decimal

基数=10、数字=0〜9

2進数 Binary

基数=2、数字=0,1

16進数 Hex

基数=16、数字=0〜F

試してみよう: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進数 ←→ 2進数 ←→ 16進数

10進数と16進数の直接変換は計算量が大きいが、それぞれと2進数の変換はどちらも非常に簡単だからだ。

10進数→2進数:2で割って余りを取る。

16進数→2進数:表を引いて展開(各1桁を4桁にする)。

その他の拡張