Mímisbrunnr知恵の泉

← コンピュータ基礎 一覧

🎓 レベル:基礎 | 重要度:A(必須)

📎 前提:なし(この章の出発点) | 関連:CPUと命令実行メモリ階層とキャッシュ

要点(BLUF)

概念 ── ビットは「意味のない0/1」、解釈が意味を与える

1バイト(8ビット)の 11101001 という並びは、それ自体には意味がありません。これを「符号なし整数」と読めば233、「符号付き整数」と読めば-23、「ある文字コードの1バイト」と読めば別の文字。同じビット列でも、解釈する型が違えば別の値になります。

要するに、データ表現とは「ビット列 ↔ 人間が扱う値」の変換ルールです。CPUやプログラミング言語の「型」は、このルールを指定する札に過ぎません。

仕組み① ── 整数と2の補数

正の整数は素直に2進数です。問題は負の数で、現代のCPUはほぼ例外なく 2の補数(two’s complement) を使います。8ビットなら「-x256 - x のビット列で表す」と決めます。

要するに2の補数は、「最上位ビットの重みだけをマイナスにした」位取りです。8ビットなら最上位の重みが -128、残りは +64, +32, …, +1。だから 10000000 は -128、11111111-128+64+…+1 = -1 になります。

なぜこの面倒な表現か。引き算を足し算で実装できるからです。a - ba + (-b) として、負数も同じ加算回路に流せる。符号の場合分け回路が要らず、ハードが単純になります。これが「設計の理由」です。

検証してみます([[#対応ラボ]] の 01-01_data_representation.py)。

for x in (-1, -128, 127):
    bits = x & 0xFF            # 下位8ビットだけ取り出す=2の補数表現
    print(f"{x:4d} -> 0b{bits:08b} = 0x{bits:02X}")
ov = (127 + 1) & 0xFF
signed = ov - 256 if ov >= 128 else ov
print("127+1 を8ビットで解釈 ->", signed)

実行結果(実機):

  -1 -> 0b11111111 = 0xFF
-128 -> 0b10000000 = 0x80
 127 -> 0b01111111 = 0x7F
127+1 を8ビットで解釈 -> -128

127 + 1-128 に「巻き戻った」のがオーバーフローです。8ビットの世界は -128〜127 の輪(時計の文字盤)になっていて、上限を超えると下限に回り込みます。C言語の signed char で起きるバグはこれです。

仕組み② ── 浮動小数点(IEEE754)

小数は IEEE754 という規格で、値 = 符号 × 仮数 × 2^指数 の形に分解して格納します。単精度(float32)なら 符号1ビット・指数8ビット・仮数23ビットです。

flowchart LR
    S["符号 s (1bit)"] --> E["指数 e (8bit)"] --> M["仮数 m (23bit)"]
    classDef box fill:#eef,stroke:#557
    class S,E,M box

ここで重要なのは、2進の小数で割り切れる数は限られること。0.5 = 2^-10.25 = 2^-2 は表せますが、0.11/10 で、2進では循環小数になり割り切れません。だから格納時に最も近い値へ丸められます。

import struct
b = struct.pack(">f", 0.1)
print("0.1 の float32 ビット列:", b.hex())
print("float32 に丸めた 0.1 =", repr(struct.unpack(">f", b)[0]))
print("0.1+0.2 == 0.3 ?", (0.1 + 0.2) == 0.3, " 実際:", 0.1 + 0.2)

実行結果(実機):

0.1 の float32 ビット列: 3dcccccd
float32 に丸めた 0.1 = 0.10000000149011612
0.1+0.2 == 0.3 ? False  実際: 0.30000000000000004

0.1 + 0.20.3 ぴったりにならないのは、Pythonのバグではなく2進浮動小数の根本的な性質です。だから金額計算には浮動小数を使わず、整数(最小単位を「円」でなく「銭」にする等)や十進小数型を使います。

仕組み③ ── 文字コード

文字も番号(コードポイント)に対応づけ、それをバイト列に符号化します。今の標準は Unicode + UTF-8。ASCII範囲(英数記号)は1バイトのまま、日本語などは複数バイトで表します。

s = "亜A"
print("UTF-8 バイト列:", s.encode("utf-8").hex(), "バイト数:", len(s.encode("utf-8")))
print("'A':", ord("A"), " '亜':", ord("亜"))

実行結果(実機):

UTF-8 バイト列: e4ba9c41 バイト数: 4
'A': 65  '亜': 20124

が3バイト(e4 ba 9c)、A が1バイト(41)。「文字数」と「バイト数」は別物で、これを混同すると文字化けやバッファ溢れの原因になります。

仕組みの直観 ── なぜこの設計か

⚠️ よくある誤解・落とし穴

対応ラボ

cs-foundations-study/labs/01-01_data_representation.py(実行して上記の出力を確認済み)。

関連

第1章 計算機の構成 目次