「コンピュータは0と1しか分からない」——この言葉を聞いたことがあるだろう。だが、なぜ0と1なのか、その答えを正確に言える人は意外と少ない。
今から80年前、コンピュータは存在しなかった。あったのは、ただの電気回路だった。
電圧こそが「0」と「1」の正体
コンピュータが「0と1しか扱えない」というのは比喩ではなく、物理現象の直接の写像だ。
現代のCPU内部では、電圧が約0V付近なら 0、約1.0〜3.3V付近なら 1 として扱われる。これを論理レベル(Logic Level)と呼ぶ。
なぜ2値なのか?答えはシンプルだ。電圧は連続的な値を取るが、「高い/低い」という二値判定ならノイズに強い。0.3Vのノイズが乗っても、1.0V閾値を超えない限り誤動作しない。三値・四値でも理論上は可能だが、ノイズ耐性と回路の複雑さのトレードオフで、人類は二値に落ち着いた。
トランジスタ — 電気で電気を制御する革命
二値信号を計算に使うには、信号を「スイッチング」する部品が必要だ。それがトランジスタだ。
1947年、ベル研究所のショックレー、バーディーン、ブラッテンが世界初のトランジスタを発明した。コンピュータ史上、最も重要な瞬間の一つだ。
コレクタ (C)
|
|
ベース (B) ──[N P N]──
|
エミッタ (E)
NPN型トランジスタの動作原理はシンプルだ:
- ベース に小さな電流を流す → コレクタ〜エミッタ間 が導通する(スイッチ ON =
1) - ベースに電流を流さない → 導通しない(スイッチ OFF =
0)
小さな電流で大きな電流を制御できる。これがトランジスタの本質だ。
現代のCPUには、このトランジスタが数百億個集積されている。台湾のTSMCが製造する3nmプロセスでは、髪の毛の太さ(約70,000nm)の中に2万個以上のトランジスタが並ぶ。
ブール代数 — 数学が回路になる瞬間
電気スイッチを組み合わせると、数学的な論理演算が実現できる。これを体系化したのがブール代数(Boolean Algebra)だ。1854年、ジョージ・ブールが提唱した。
基本は3つの演算だ:
| 演算 | 記号 | 意味 |
|---|---|---|
| AND | AかつB(両方1なら1) | |
| OR | AまたはB(どちらか1なら1) | |
| NOT | Aの否定 |
これら3つを組み合わせることで、任意の論理演算が実現できる(関数完全性)。
半加算器 — 1ビットの足し算
最も基本的な計算回路として「半加算器(Half Adder)」を例に取ろう。 1ビット同士の加算 を計算すると:
はXOR(排他的論理和)を表す。真理値表で確認しよう:
| A | B | S (Sum) | C (Carry) |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 0 | 1 |
1 + 1 = 10(二進数)。10進数で言えば「2」だ。これが1ビット加算の全てだ。
二進数 — 重みの体系
人間が10進数(0〜9)を使うのは「指が10本だから」という説がある。コンピュータが2進数を使うのは「電圧の状態が2つだから」だ。
10進数の仕組みを思い出してほしい:
二進数も同じ原理で、基数が2になるだけだ:
Pythonで確認する
# 10進数 → 2進数
n = 42
binary = bin(n)
print(binary) # '0b101010'
print(binary[2:]) # '101010' (プレフィックス除去)
# 2進数 → 10進数
b = '101010'
decimal = int(b, 2)
print(decimal) # 42
# ビット演算で確認
print(42 == (32 + 8 + 2)) # True
print(42 == (1<<5 | 1<<3 | 1<<1)) # True
8ビットで表現できる範囲
符号なし8ビット整数(uint8)が表現できる範囲は:
符号あり8ビット整数(2の補数表現)では:
「2の補数」については第2章で詳しく扱う。
論理ゲート — トランジスタの組み合わせ
ブール代数の演算は、トランジスタを特定のパターンで接続することで実現する。この「演算を行う最小回路ブロック」を論理ゲート(Logic Gate)と呼ぶ。
AND ゲート:
A ──┐
[AND]── 出力
B ──┘
NAND ゲート(ANDの否定):
A ──┐
[NAND]── 出力
B ──┘
面白い事実がある:NANDゲートだけあれば、AND・OR・NOT・XORを全て構成できる(NANDの汎用性)。実際の集積回路設計でも、論理合成ツールは最終的にNANDとNORの組み合わせへ変換することが多い。
/* Cで論理演算をビット演算で表現 */
#include <stdio.h>
#include <stdint.h>
int main(void) {
uint8_t a = 0b10110100; /* 180 */
uint8_t b = 0b01101011; /* 107 */
printf("AND : %08b (%d)\n", a & b, a & b); /* 論理積 */
printf("OR : %08b (%d)\n", a | b, a | b); /* 論理和 */
printf("XOR : %08b (%d)\n", a ^ b, a ^ b); /* 排他的論理和 */
printf("NOT : %08b (%d)\n", ~a & 0xFF, ~a & 0xFF); /* 論理否定 */
printf("SHL : %08b (%d)\n", a << 1, a << 1); /* 左シフト = ×2 */
printf("SHR : %08b (%d)\n", a >> 1, a >> 1); /* 右シフト = ÷2 */
return 0;
}
実行結果:
AND : 00100000 (32)
OR : 11111111 (255)
XOR : 11011111 (223)
NOT : 01001011 (75)
SHL : 01101000 (104)
SHR : 01011010 (90)
左シフト1ビットが「×2」になるのは、各桁の重み()が一段上がるからだ。これは10進数で「0を付け足すと×10になる」のと同じ原理だ。
まとめ — 「おまじない」の消し方
この章で分かったことを整理しよう:
- 「0と1」の正体は電圧の高低:物理現象が直接、論理値になっている
- トランジスタがスイッチ:電気で電気を制御し、論理演算を実現する
- ブール代数が橋渡し:数学的な論理が回路設計の言語になる
- 二進数は電圧の数学:基数2の位取り記数法が、電気回路と自然に対応する
「なぜビット演算は速いのか?」「なぜintは-2147483648〜2147483647なのか?」——これらの問いに答える下地が整った。
次章では、この二進数表現を使ってクロックとレジスタがどのように「時間」と「記憶」を生み出すかを見ていく。
「すべての複雑さは、単純さの組み合わせから生まれる。コンピュータが神秘的に見えるのは、単純なものが10億回繰り返されているからに過ぎない。」