一文で言うと
N進数の変換とは、ある数の実際の値を変えずに、ある記数法(我々が慣れ親しんだ10進数など)から別の記数法(コンピューターがネイティブに使う2進数など)へと翻訳するアートのことだ。
これが解決する問題
人間には、だいたい指が10本ある。この生物学的なハードウェアのおかげで、10をひとまとめにして数を数える10進法(decimal or base-10)が、ものすごく自然に感じられるようになった。我々には0から9までの10個のユニークな記号があり、9に達すると桁が上がる。左に新しい桁を加えて、また0から始めるんだ。10は 10、100は 100 といった具合に。チョロいもんだ。
一方、コンピューターの「指」はちょっと違う。その基本的なハードウェアはトランジスタで、これはオンかオフのどちらかになれる超小型のスイッチだ。それだけ。2つの状態しかない。このため、2つの記号を使うシステム、つまり2進法(binary or base-2)がコンピューターの母国語になっている。その2つの記号とは、0(オフ)と 1(オン)だ。
これが翻訳の問題を生み出す。コンピューターにとって、人間が使う数字の 237 は意味をなさない。一連のオンとオフとして表現される必要があるのだ。その同じ数字 237 は、2進数では 11101101 になる。
コンピューターは2進数に堪能だけど、人間は…そうでもない。1と0の長い羅列を読むなんて、頭痛と眼精疲労のもとだ。このギャップを埋めるために、開発者たちは便利なショートカットを考え出した。それが8進法(octal or base-8)と16進法(hexadecimal or base-16)だ。これらの「2のべき乗」を基数とするN進法は、2進数との相互変換が驚くほど簡単で、しかも長い2進数の文字列をずっと読みやすい塊に圧縮してくれる。
N進法は、人間が数字について考える方法と、マシンが物理的にそれを保存する方法との間の、この根本的なミスマッチを解決するために存在する。N進法は、1と0の海で迷子になることなく、マシンの心の中を覗き見ることを可能にするロゼッタストーンなのだ。
舞台裏の仕組み
あらゆるN進法の裏にある魔法は、位取り記数法という概念だ。数字の位置がその値を決定する。分解して見てみよう。
そもそも「基数」って何よ?
車のオドメーターを思い浮かべてほしい。一番右の桁が9から0になると、その左の桁が1つ繰り上がる。それぞれの桁の「位」は10のべき乗を表している。
我々が日常的に使う10進法の数字 427 は、本当はこういう意味だ:
4× 百 (4 × 10²)2× 十 (2 × 10¹)7× 一 (7 × 10⁰)
これらを足し合わせると 400 + 20 + 7 = 427 となる。
「基数」とは、各々の位に対してべき乗する数のことだ。10進法(base-10)なら10。2進法(base-2)なら2。16進法(base-16)なら16だ。式は同じで、基数(b)が変わるだけ。
... + (digit × b²) + (digit × b¹) + (digit × b⁰)
我々の世界(10進数)から彼らの世界(任意のN進数)へ
10進数の 427 を2進数(base-2)に変換してみよう。アルゴリズムは「ターゲットの基数で繰り返し割り、その余りを記録する」だ。
| 計算式 | 結果 | 余り |
|---|---|---|
| 427 ÷ 2 | 213 | 1 |
| 213 ÷ 2 | 106 | 1 |
| 106 ÷ 2 | 53 | 0 |
| 53 ÷ 2 | 26 | 1 |
| 26 ÷ 2 | 13 | 0 |
| 13 ÷ 2 | 6 | 1 |
| 6 ÷ 2 | 3 | 0 |
| 3 ÷ 2 | 1 | 1 |
| 1 ÷ 2 | 0 | 1 |
さあ、この余りを下から上へと読んでいこう。
つまり、10進数の 427 は2進数で 110101011 となる。
この方法はどの基数にも使える。427 を16進数(base-16)に変換するには、16で割るだけだ。(注意:16進数では10個以上の記号が必要なので、10には A、11には B、...、15には F を使う)。
427 ÷ 16 = 26、余りは11(16進数ではB)。26 ÷ 16 = 1、余りは10(16進数ではA)。1 ÷ 16 = 0、余りは1。
下から読むと 1AB となる。つまり、10進数の 427 は16進数で 1AB だ。
彼らの世界(任意のN進数)から我々の世界(10進数)へ
逆の変換をするには、先ほど見た位取り記数法の式を使う。2進数の 110101011 を10進数に戻してみよう。各桁に、その位置に応じた2のべき乗(右から0で始まる)を掛けていく。
1 * 2^8 = 256
+ 1 * 2^7 = 128
+ 0 * 2^6 = 0
+ 1 * 2^5 = 32
+ 0 * 2^4 = 0
+ 1 * 2^3 = 8
+ 0 * 2^2 = 0
+ 1 * 2^1 = 2
+ 1 * 2^0 = 1
----------------
合計 = 427
うまくいった!16進数の 1AB でも試してみよう:
1 * 16^2 = 256
+ A * 16^1 = (10 * 16) = 160
+ B * 16^0 = (11 * 1) = 11
-------------------------
合計 = 427
ドーン。同じ数字が、違う服を着ているだけだ。
16進数と8進数のショートカット
じゃあ、なぜわざわざ16進数や8進数を使うのか? それは、これらが「2のべき乗」を基数にしているからだ。
- 8進数はbase-8で、8 = 2³。これは、全ての8進数の桁が、3つの2進数の桁のグループに完璧に対応することを意味する。
- 16進数はbase-16で、16 = 2⁴。これは、全ての16進数の桁が、4つの2進数の桁のグループ(「ニブル」と呼ばれる)に完璧に対応することを意味する。
大きな2進数を見てみよう:1101011101001111
これを読むには、人間はすべての桁をスキャンしなければならない。しかし、16進数に変換するには、右から4つずつのグループに分けるだけだ:
1101 | 0111 | 0100 | 1111
次に、各チャンクを変換する:
1101は8+4+0+1 = 13、つまりD0111は0+4+2+1 = 70100は0+4+0+0 = 41111は8+4+2+1 = 15、つまりF
というわけで、1101011101001111 は16進数で単に D74F となる。無限に読みやすく、エラーも起こしにくい。これは単なる計算ではなく、直接的な書き起こしだ。これこそが、開発者が16進数を愛する理由だ。バイナリデータにとって、人間が読める完璧なラッパーなのだ。
実社会でのストーリー
謎めいた色の事件
ある若手フロントエンド開発者が、UIデザイナーからデザインモックアップを受け取った。ブランドのプライマリーカラーは #E63946 と記載されている。開発者はこれが赤系の色合いであることは知っているが、どれくらい赤いのか?他の色はどうなっている?彼はその16進コードをコンバーターに入力してみた。E63946 は実際には3つの独立した16進数だったのだ:E6 が赤、39 が緑、46 が青。
- 16進数の
E6は10進数で230に変換される。なるほど、かなり赤が強い(最大値255のうち)。 - 16進数の
39は10進数で57に変換される。少しだけ緑が入っている。 - 16進数の
46は10進数で70に変換される。少しだけ青が入っている。
16進コードを分解することで、開発者はその色の構成を理解した。それは単なる「赤」ではなく、「ほとんどが赤で、それに近い少量の緑と青を加えて、きつさを和らげた色」だったのだ。
教訓: 16進数はWebにおける色の言語だ。これを理解することで、コードをコピペするだけでなく、自分が扱っている色を真に理解できるようになる。
ファイルシステム権限のパズル
あるシステム管理者が、Linuxサーバーに新しくアップロードしたスクリプト deploy.sh をセキュアにしようとしていた。所有者には読み取り・書き込み・実行権限を与え、グループとその他のユーザーには読み取りと実行権限のみを与えたい。彼は chmod 755 deploy.sh というコマンドがどこでも使われているのを見てきたが、ずっと魔法の呪文として扱ってきた。
今回、彼はそれを調べてみた。755 という数字は8進数だった。これは3つの権限セットを表している:所有者、グループ、その他。
- 所有者の権限は
7。2進数では111。 - グループの権限は
5。2進数では101。 - 「その他」の権限は
5。2進数では101。
各2進数の桁は、特定の権限に対応している:読み取り(r)、書き込み(w)、実行(x)。
111はrwxを意味する(読み取り、書き込み、実行がすべてオン)。101はr-xを意味する(読み取りと実行がオン、書き込みはオフ)。
つまり、chmod 755 は権限を rwxr-xr-x に設定する。まさに必要としていたものだった。謎は解けたのだ。
教訓: 8進数は、ファイルパーミッションを管理するための超簡潔なショートカットを提供する。ファイルパーミッションは、本質的にはオン/オフのフラグ(ビット)の集合なのだ。
ビット反転バグ
ある開発者が、スマートサーモスタットの組み込みシステムを構築していて、デバイス設定をメモリの小さな塊に保存する必要があった。スペースを節約するため、彼は1バイト(8ビット)を「フラグ・レジスタ」として使用した。各ビットが設定を表す:isHeating、isCooling、fanOn、hasWifiConnection など。
ある日、「ファンをオンにすると、暖房もオンになる!」というバグレポートが殺到した。開発者は困惑した。ファンをオンにするコードは正しく見える。しかし、メモリ内の設定バイトを調べてみると、問題がわかった。ファンが2ビット目(値 2¹ = 2)、暖房が3ビット目(値 2² = 4)だとしよう。コードはバイトを ...010(ファンオン)に設定するはずだった。しかし、実際には ...110(ファンオン かつ 暖房オン)に設定してしまっていた。10進数の値は 2 ではなく 6 になっており、すぐには気づきにくい。しかし、2進数表現 00000110 を見れば、問題は一目瞭然だった。1つのビットではなく、2つのビットが反転していたのだ。欠陥のあるビットワイズ演算が原因だった。
教訓: 低レベルのプログラミング、メモリのデバッグ、ハードウェアレジスタを扱う場合、2進数で考えることは選択肢ではなく、実際に何が起こっているかを見るための唯一の方法なのだ。
よくある間違いと落とし穴
- プレフィックスを忘れること。 多くのプログラミング言語(C、Java、JavaScriptなど)では、
0で始まる数字は8進数として解釈される。010は10ではなく8だ!0xで始まる数字は16進数。0x10は16だ。これを知らないと、本当に奇妙なバグにつながることがある。 O/0とI/1を混同すること。 長い16進数や2進数の文字列を睨んでいると、目がかすんで文字のOをゼロと、Iを1と見間違えやすい。16進数はOやIを使わないが、他のN進数変換では使う可能性があり、これは典型的な「ファットフィンガー」エラー(打ち間違い)だ。- 16より大きい基数に標準があると仮定すること。 2進数(0-1)、8進数(0-7)、10進数(0-9)、16進数(0-9, A-F)の文字は標準化されている。しかし、22進数や36進数ではどうだろう?ほとんどのシステムでは
0-9に続いてA-Zを使うが、これは普遍的な法則ではない。あまり一般的でない基数を扱うときは、使用されている文字セット(または「アルファベット」)を必ず確認しよう。 - 整数リミットを無視すること。
0x7FFFFFFFFFFFFFFFのような64ビットの16進数は、とてつもなく大きな数を表す。これを変換してプログラム内の標準的な32ビット整数変数に保存しようとしても、収まらない。これは「整数オーバーフロー」を引き起こし、数値がラップアラウンドして負の値になったり、エラーをスローしたりする。
なぜこれを気にかけるべきか
たとえ低レベルのCプログラマーでなくても、さまざまなN進数に常に遭遇することになる。次のようなときには、N進数について考えるべきだ:
- CSSやデザインツールで色を選ぶとき(
#RRGGBB)。 - APIキーやGitコミットのSHAのようなハッシュ値を扱うとき。これらはほとんど常に16進数だ。
- Webサーバーでパーミッションを設定するとき(
chmod)。 - 低レベルのデータフォーマット、ネットワークプロトコル、またはビットマスクを使用するもの(例:機能フラグ)を扱うとき。
- デバッガーでエラーコードやメモリアドレスに遭遇したとき。
- コンピューターが実際にどのように機能するかを理解しようとするとき。これは多くのデジタルマジックの謎を解き明かす、基礎的な概念だ。
もっと深く知るには
- Positional Notation (Wikipedia): あらゆるN進法の背後にある理論を学術的に深く掘り下げたページ。
- Hexadecimal (Wikipedia): 16進法、その歴史、コンピューティングでの一般的な用途についての優れた概説。
- **MDN Web Docs:
<color>: 16進数(#RRGGBB)記法についての徹底的な説明を含む、Web開発者向けCSSカラー値の決定版ガイド。 chmodmanual page: ファイルパーミッション設定のための8進数の使用法を詳述した、chmodコマンドの正典的なLinuxマニュアル。- Bitwise operations in JavaScript (MDN): 高水準言語でビットレベルの操作がどのように行われるかを示す実践的なガイド。base-2の知識を応用する一般的なシナリオ。