ぱんだツールズぱんだツールズ

技術背景

2進数・10進数・16進数の変換 — ビット・バイト・カラーコードの基礎

約6分

CSSで色を指定するときに書く #ff5733 の 「ff」って、どうやって数字になるのか考えたことはありますか。 この「ff」は16進数で、10進数に直すと255——つまりRGBの赤の最大値です。 コンピュータの世界では10進数だけでなく、2進数・16進数が頻繁に顔を出します。 この記事では、なぜコンピュータは2進数で動くのか、16進数が使われる本当の理由、 そして手計算とJavaScriptでの変換方法を、カラーコード・IPアドレス・ファイルパーミッションなど 身近な実例を交えて解説します。

なぜコンピュータは2進数で動くのか

コンピュータの内部では、すべてのデータが0と1の2つの値で表現されています。 この2進数(binary)が選ばれた理由は、人間の都合ではなく物理の都合です。

電子回路において「電圧が高い/低い」という2つの状態は、ノイズや温度変化があっても 確実に区別できます。もし10進数を直接扱おうとすれば、10段階の電圧を正確に判別する必要があり、 少しの電気的な揺らぎで値が化けてしまいます。2状態しかない2進数なら、トランジスタのON/OFFという もっともシンプルなスイッチだけで論理演算を構成でき、信頼性が高く高速に動作します。

人間が10進数を使っているのは、単に指が10本あるから——それ以上の深い理由はありません。 10進数はコンピュータにとってむしろ不自然な数え方なのです。 一方で2進数は桁数がすぐに膨らむという欠点があります。 10進数の255はわずか3桁ですが、2進数では 11111111 と 8桁にもなります。これを人間が読み書きするのは現実的ではありません。そこで登場するのが16進数です。

16進数が使われる本当の理由

16進数(hexadecimal)が広く使われる理由は、2進数との変換が圧倒的に楽だからです。 16は2の4乗(2^4)なので、16進数1桁 = 2進数4桁という完璧な対応関係があります。

たとえば1バイト(8ビット = 2進数8桁)は、16進数ちょうど2桁で表せます。 2進数の 11111111 は16進数ではFF、10進数では255です。 これだけで桁数が4分の1になり、メモリダンプやカラーコードが一気に読みやすくなります。

もし10進数を使うと、2進数8桁は最大で10進数3桁(0〜255)になり、桁数が変わるうえに ビット単位での対応が崩れます。16進数なら上位4ビットと下位4ビットをそのまま別々に読めるため、 CPUレジスタやビットフラグを扱うエンジニアには欠かせない記法なのです。

10進・2進・16進の対応表

0〜16の値を3種類の基数で並べると、変換の規則がひと目で掴めます。

10進数2進数16進数
000000
100011
200102
300113
401004
501015
601106
701117
810008
910019
101010A
111011B
121100C
131101D
141110E
151111F
161000010

注目したいのは10進数の10〜15が16進数ではA〜Fというアルファベットで表現される点です。 10進数でいう「10」は2桁になってしまい、1桁で15までを表したい16進数には使えないので、 続きの値をA・B・C・D・E・Fで埋めるわけです。そして16に達した瞬間、16進数も2桁目に繰り上がって10になります。

手計算で変換してみる

2進数 → 10進数

各桁に2の累乗を対応させて足すだけです。2進数の1011なら右から2^0・2^1・2^2・2^3の重みで、1×8 + 0×4 + 1×2 + 1×1 = 11(10進数)となります。

2進数: 1 0 1 1

重み: 8 4 2 1 (2^3 / 2^2 / 2^1 / 2^0)

計算: 8 + 0 + 2 + 1 = 11

10進数 → 2進数(2で割って余りを並べる)

10進数を2で割り続け、出てきた余りを下から読むと2進数になります。 13を例にすると以下のようになります。

13 ÷ 2 = 6 余り 1 ← 下位ビット

6 ÷ 2 = 3 余り 0

3 ÷ 2 = 1 余り 1

1 ÷ 2 = 0 余り 1 ← 上位ビット

余りを下から読むと 1101 → 13 = 1101(2)

10進数 → 16進数(16で割って余りを並べる)

方法は2進数と同じで、割る数を16にするだけです。余りが10以上ならA〜Fに変換します。 255を例にすると、255 ÷ 16 = 15 余り 15 → 15 ÷ 16 = 0 余り 15。 余りを下から読むと15・15なので、16進数では FF になります。

16進数と2進数の相互変換は、対応表を使って4ビットずつ置き換えるのが最速です。 2進数の 11010110 なら、 1101 → D、0110 → 6なので16進数では D6 です。

JavaScriptでの基数変換

JavaScriptなら toString(基数)parseInt(文字列, 基数) で 2〜36までの任意の基数に相互変換できます。

// 10進数 → 2進数 / 16進数
(255).toString(2)   // "11111111"
(255).toString(16)  // "ff"
(255).toString(8)   // "377"

// 2進数 / 16進数 → 10進数
parseInt('11111111', 2)  // 255
parseInt('ff', 16)       // 255
parseInt('0xff', 16)     // 255(0xプレフィックス付きでもOK)

// 基数リテラル(そのまま数値として書ける)
0b11111111  // 255
0xff        // 255
0o377       // 255(8進数)

// 0埋めで表示する(RGBカラーコード用)
const hex = (255).toString(16).padStart(2, '0')  // "ff"

注意点として、parseIntは必ず第2引数で基数を指定してください。省略すると環境によって解釈が変わる場合があり、 特に「0」から始まる文字列が8進数扱いされる歴史的な罠があります。 またtoString(16)は小文字を返すので、 大文字で出力したいときは .toUpperCase() を追加します。

実例で見る16進数と2進数

RGBカラーコード

CSSの #ff5733 は、 R=0xFF(255)・G=0x57(87)・B=0x33(51)を意味します。各色2桁ずつの16進数で0〜255の範囲を表現しており、 これは1バイト(8ビット)をちょうど16進数2桁で表せる性質を活用したものです。 色の細かい調整をしたいときは、色コード変換ツールでHEX・RGB・HSLを相互に見比べると直感的に理解できます。

IPv4アドレス

192.168.1.1 のような IPアドレスは、内部では32ビットの2進数です。これを1バイトずつ10進数にしてドットで区切っているだけで、 10進数表記の各数値(0〜255)は必ず1バイト分の情報量に収まります。 サブネットマスクを考えるときは2進数に戻すと理解しやすくなります。

Unicodeコードポイント

絵文字や日本語の文字は、Unicodeにおいて U+3042(あ)やU+1F600(😀)のように 16進数のコードポイントで識別されます。JavaScriptでは'あ'.codePointAt(0).toString(16)で取得できます。

ファイルパーミッション(chmod 755)

Linuxの chmod 755 の「755」は 8進数で、2進数の111 101 101に分解されます。3ビットずつ「所有者・グループ・その他」に対応し、 各ビットが「読み・書き・実行」を意味します。7 = 111は全権限、5 = 101は読み+実行というわけです。 8進数が現役で使われている数少ない実用シーンです。

基数変換を手軽に行うツール

手計算で全部やるのは大変なので、普段の開発では数値基数変換ツールを使うのがおすすめです。2進・8進・10進・16進を同時に表示するため、値を入れた瞬間に対応関係を確認できます。 デバッグ時にレジスタ値を読むときや、CSSカラーコードの計算にも便利です。

また、16進数はハッシュ値の表現にも使われます。SHA256の出力が64文字の16進数文字列になるのは、 256ビット(= 32バイト)を16進数2桁×32で表現しているためです。 ハッシュ値を生成したい場合は ハッシュ値生成ツールで実際の出力を確認してみてください。どちらのツールも処理はブラウザ内で完結し、入力値がサーバーに送信されることはありません。

まとめ

  • コンピュータが2進数を使うのは、電子回路で2状態(電圧の高低)が最も安定して扱えるから
  • 16進数1桁は2進数4桁に対応するため、1バイトを16進数2桁で簡潔に表せる
  • 10進数→2進数・16進数は「割って余りを下から読む」手順で変換できる
  • JavaScriptでは toString(基数)parseInt(文字列, 基数) で相互変換可能
  • RGBカラーコード・IPアドレス・Unicode・chmodなど、身近なところに16進数と2進数が登場する
  • 基数を同時表示したいときは 数値基数変換ツール が便利

よくある質問

0xや0bはどういう意味ですか?

数値リテラルの「基数プレフィックス」と呼ばれる記法です。0xは16進数(hexadecimal)を示し、0x1Aと書けば10進数の26を意味します。0bは2進数(binary)を示し、0b1010なら10進数の10です。0(ゼロ)から始まるだけの0755は8進数(octal)でC言語・Pythonなどでは昔からの慣習ですが、JavaScriptの新しい記法では0o755と明示的に書きます。プレフィックスがない数字はすべて10進数として扱われます。

なぜ16進数は4ビット単位で扱えるのですか?

16進数の1桁は0〜15の16通りを表し、これは2の4乗(2^4 = 16)と等しいためです。つまり16進数の1桁は2進数4桁にぴったり対応します。たとえば2進数の1010は16進数ではA、11111111は2桁のFFになります。この性質があるので、1バイト(8ビット)を16進数2桁でコンパクトに表現でき、エンジニアが暗算でも相互変換しやすくなっています。これが16進数がプログラミングで広く使われる最大の理由です。

8進数はまだ使われていますか?

LinuxやmacOSのファイルパーミッション(chmod 755など)で今も現役です。755は2進数111 101 101に分解され、所有者・グループ・その他の3ビット(読み・書き・実行)をそれぞれ1桁で表現できるため8進数が便利なのです。それ以外の用途では16進数に置き換わっているケースが多く、C言語の古い定数定義(0755など)を読むときに知っておく程度で十分です。

負の数は2進数でどう表しますか?

現代のコンピュータでは「2の補数」という方式で負の数を表します。たとえば8ビットで-1は11111111、-2は11111110です。符号ビット(最上位ビット)が1なら負の数を意味し、-1に1を足すと0(00000000、オーバーフロー分の9桁目を捨てる)になる、という計算が単純な加算回路で成立する利点があります。JavaScriptの(-1).toString(2)は符号付きの"-1"を返すため、ビットパターンが見たい場合は(-1 >>> 0).toString(2)のように符号なし右シフトで変換する必要があります。

JavaScriptのBigIntで基数変換はできますか?

はい、BigInt型もtoString(基数)をサポートしています。たとえば(255n).toString(16)は"ff"を返し、通常の数値と同じ感覚で使えます。逆に文字列からBigIntへの変換ではparseIntではなくBigIntコンストラクタを使い、基数プレフィックスが必要です(BigInt("0xff")で255n)。Number.MAX_SAFE_INTEGER(2^53-1)を超える大きな数を扱う場合はBigInt一択です。

なぜコンピュータは10進数ではなく2進数を使うのですか?

電子回路で「電圧が高い/低い」の2状態は物理的に安定して扱えるからです。10進数を直接扱おうとすると10段階の電圧を正確に区別する必要があり、ノイズ耐性が極端に下がります。トランジスタのON/OFFという単純な2状態で論理演算を構成できるため、現代のCPU・メモリはすべて2進数で動作しています。人間が10進数を使うのは指が10本あった歴史的経緯にすぎず、コンピュータには不自然な選択なのです。

16進数のAは大文字と小文字どちらが正しいですか?

どちらも有効で、規格上は等価です。慣習として、CSSのカラーコードでは小文字(#ff5733)、MACアドレスやUUIDでは大文字(FF:AA:BB)が使われる傾向があります。JavaScriptのtoString(16)は小文字を返し、大文字にしたい場合は.toUpperCase()を追加します。ただし1つのプロジェクト内では大文字か小文字かを統一する方が読みやすく、文字列比較やdiffで余計な差分が出ないのでおすすめです。

基数変換はブラウザで行えますか?サーバーに送信されますか?

ファイルや値はサーバーに送信されません。ぱんだツールズの数値基数変換ツールはすべてブラウザ内(JavaScript)で処理されます。機密性のある数値や内部コードを変換する場合でも、外部に漏れる心配はありません。実装もNumber.prototype.toStringとparseIntを使った数十行程度のシンプルなもので、オフライン状態でも動作します。

この記事で紹介したツール