PR

【IT用語解説】2進数とは?16進数との関係と0・1でデータを表す仕組みを解説

2進数とは?をコンピュータ基礎シリーズの統一デザインで表すアイキャッチ IT-Skills

2進数と16進数は、同じ数値を、異なる「桁の重み」で書く表記です。たとえば、10進数の65、2進数の01000001、16進数の41は、いずれも同じ数値を表せます。コンピュータが16進数で処理しているのではなく、コンピュータが扱うbit列を、人が4bitずつ短く読み書きするために16進数を使います。

0x41だけを見て「文字A」と決めることはできません。まず分かるのは「16進数で41と書かれた値」です。UTF-8の文字データとして読む、という文脈が加わるとAになります。

このページで学べる内容
  • 10進数・2進数・16進数に共通する「位取り記数法」の仕組み
  • 65 = 01000001(2進) = 41(16進)になる理由
  • 2進数と10進数を、暗記ではなく分解・再構成で変換する手順
  • 4bitと16進数1桁が、なぜ正確に対応するのか
  • 同じbit列の意味を、型・符号化・仕様が決める理由
スポンサーリンク

進数の本質は「各桁にいくつの重みを持たせるか」

普段使う10進数も、2進数や16進数と同じ位取り記数法です。右端の桁を0番目とすると、各桁には「基数の0乗、1乗、2乗……」という重みがあります。基数とは、その進数で1桁に使える数字の種類の数です。

表記 基数 右から並ぶ桁の重み 65の書き方
10進数 10 1、10、100、1000… 65
2進数 2 1、2、4、8、16、32、64… 1000001
16進数 16 1、16、256、4096… 41

たとえば10進数の365は、「3、6、5」という数字を並べただけではありません。各桁の数字へ100、10、1の重みを掛けて足した値です。

365(10進)
= 3 × 10^2 + 6 × 10^1 + 5 × 10^0
= 3 × 100  + 6 × 10   + 5 × 1

右端が1の重みになるのは、どの進数でも「基数の0乗が1」だからです。同じ規則で、2進数の10000011 × 2^6 + 1 × 2^0、16進数の414 × 16^1 + 1 × 16^0と読めます。

1000001(2進) = 1 × 64 + 1 × 1 = 65
41(16進)     = 4 × 16 + 1 × 1 = 65

つまり、2進数だけに特別な計算原理があるわけではありません。何進数かによって、桁が1つ左へ移るたびに何倍になるかが変わるだけです。

先頭の0は値を変えないが、桁数の情報は消してはいけない

100000101000001は、符号なしの2進数として計算すれば、どちらも65です。先頭に0を足しても、0と桁の重みを掛けた分が増えるだけなので、値は変わりません。16進数の410041も同じです。

しかし、01000001と8桁で書けば「8bit幅で表示している」ことが分かりやすくなります。1byteを表示するなら8bit、16進数では2桁です。反対に、ログが先頭0を省略すると、数字だけから元のデータ幅を復元できないことがあります。

0x0041と4桁で書いたからといって、プログラム上の型が自動的に16bitになるとは限りません。先頭0は表記上の桁埋めであり、実際の幅はデータ型、プロトコルのフィールド長、ファイル形式などの仕様が決めます。

n bitで表せるのは2n通り

1bitには0と1の2通りがあります。2bitなら、1桁目の2通りそれぞれに2桁目の2通りを組み合わせるため、2 × 2 = 4通りです。同じ考えをn桁へ延ばすと、組み合わせは2^n通りになります。

bitパターンの数 符号なし整数としての範囲
1bit 21 = 2通り 0〜1
4bit 24 = 16通り 0〜15
8bit 28 = 256通り 0〜255
n bit 2n通り 0〜2n−1

8bitの最大値は255ですが、0も1通りとして数えるため、パターンの総数は255通りではなく256通りです。bitとbyteそのものを先に整理したい場合は、ビットとバイトの違いを確認してください。

memo.txtのAが01000001になるまで

ここからは、新しいmemo.txtへ半角のAだけを入力し、UTF-8・BOMなし・改行なしで保存する例に絞ります。ファイルに別の文字、改行、BOMがあれば、その分のbyteも加わります。

  1. 文字を番号へ対応させる:Unicodeでは、文字AにコードポイントU+0041が割り当てられています。U+の後ろは16進数で書かれたコードポイントです。
  2. 番号をUTF-8のbyte列へ符号化する:UTF-8では、U+0000U+007Fの範囲を同じ値の1byteで表します。そのためAは、16進表記で41の1byteになります。
  3. 1byteを8bitで書く:byte値41を2進表記に直すと01000001です。
文字AがUnicodeコードポイントU+0041となり、UTF-8で1byteの0x41へ符号化され、その同じbyteを2進数01000001と16進数0x41で表す対応。各bitの重み64と1、および0100と0001が16進数4と1に対応することを示す
A → U+0041 → UTF-8の1byteという符号化の流れと、そのbyteを2進・16進で読み替える関係

U+00410x41は、Aでは数値が一致しますが、役割は別です。前者はUnicodeコードポイントを指す表記、後者はここではUTF-8で出力された1byteの値を16進数で示す表記です。ASCII範囲を外れる文字では、コードポイントとUTF-8の各byte値はこのように単純には一致しません。

ファイルの中に、紙に書いたような「A」や文字列の「01000001」が入るわけではありません。保存されるのはbyte列です。エディタがそのbyte列をUTF-8として復号し、フォントで描画することで、画面にAが見えます。

文字、コードポイント、符号化されたbyte列の違いは、文字コードとはで詳しく扱っています。本記事では、得られた1byteを2進数と16進数でどう読み替えるかへ戻ります。

2進数から10進数へ:1の立っている桁の重みを足す

01000001を10進数へ変換するときは、数字列を丸ごと暗記する必要はありません。各桁の重みを並べ、bitが1の場所だけを足します。

bit位置    7    6    5    4    3    2    1    0
桁の重み 128   64   32   16    8    4    2    1
bit        0    1    0    0    0    0    0    1
  1. 右端から2^0、2^1、2^2…の重みを割り当てる
  2. bitが1の桁を探す
  3. その桁の重みを足す
01000001(2進)
= 0×128 + 1×64 + 0×32 + 0×16
  + 0×8 + 0×4 + 0×2 + 1×1
= 64 + 1
= 65(10進)

2進数から10進数への変換は、bit列から各桁の重みを使って値を再構成する作業です。1の位置さえ分かれば、足す対象も分かります。

10進数から2進数へ:値を2のべき乗へ分解する

反対に65を2進数へ直すときは、65を「2のべき乗の和」へ分解します。今回は1byte、つまり8bitで表す前提なので、最大の重み128から順に確認します。

調べる重み 残りの値に含められるか bit 残り
128 65より大きい 0 65
64 含められる 1 1
32、16、8、4、2 残り1より大きい すべて0 1
1 含められる 1 0

65 = 64 + 1 = 2^6 + 2^0なので、6番目と0番目のbitを1にし、それ以外を0にします。その結果が01000001です。

65 = 64 + 1
   = 2^6 + 2^0
   = 01000001(8bitの2進表記)

数値65だけを表すなら先頭0を省いた1000001でも同じ値です。今回は「UTF-8で出力された1byte」を見ているため、8桁へそろえて01000001と書いています。

2進数と16進数:4bitずつ置き換えればよい

16進数1桁には0〜15の16通りがあります。2進数4桁も2^4 = 16通りです。さらに16 = 2^4なので、2進数4桁と16進数1桁を、情報を失わず1対1で対応させられます。

2進数 16進数 2進数 16進数
0000 0 1000 8
0001 1 1001 9
0010 2 1010 A
0011 3 1011 B
0100 4 1100 C
0101 5 1101 D
0110 6 1110 E
0111 7 1111 F

2進数から16進数へ変換する

右端から4bitずつ区切り、それぞれを16進数1桁へ置き換えます。左端のグループが4bitに満たなければ、値を変えない先頭0で埋めます。

01000001
→ 0100 0001
→   4    1
→ 41(16進)

16進数から2進数へ変換する

逆向きは、16進数の各桁を必ず4bitへ置き換えます。4 → 01001 → 0001なので、41 → 01000001です。

この対応により、1byteは2桁、32bitは8桁、64bitは16桁の16進数で表せます。デバッガや通信仕様で16進数が多いのは、長いbit列を短くしながら、4bitの境界を崩さず戻せるためです。

文字のAと、16進数の桁Aは別物です。 16進数のAは「10」を表す1桁です。一方、文字AをUTF-8で保存したbyteは0x41です。「Aだから0xA」ではありません。

0xと0bは値ではなく、読み方を示す目印

41だけでは、10進数の41なのか、16進数の65なのかを判別できません。そこでPythonやJavaなどのソースコードでは、0xを16進整数リテラル、0bを2進整数リテラルの接頭辞として使います。

65           # 10進表記
0b01000001   # 2進表記
0x41         # 16進表記

# いずれも同じ整数値65を表す

0x0bは、数字列を何進数として読むかをソースコードの字句解析器や人へ伝える記号です。値65の中に「0x」という成分があるわけでも、実行時のレジスタへ文字0xが一緒に入るわけでもありません。

表記例 何を示すか 注意点
0x41 16進数の数値表記 PythonやJavaなどで使われる。製品によっては接頭辞を付けない
0b01000001 2進数の数値表記 0bを受け付けるかは言語・書式の仕様次第
U+0041 Unicodeコードポイントの表記 U+は16進数のコードポイントを示し、UTF-8のbyte列そのものではない
41(hex欄) 欄の定義によって16進数と分かる表記 ログや16進ダンプでは接頭辞が省かれることがある

表記法は共通の自然法則ではなく、言語や製品が決める構文です。たとえばPythonの言語リファレンスJava言語仕様は、どちらも0x0bを定めていますが、別のツールやデータ形式へそのまま貼り付けられるとは限りません。

同じbit列でも、意味は型・符号化・仕様が決める

bit列は、それだけでは「文字」「正の整数」「負の整数」「命令」といった札を持っていません。どの規則で読むかが決まって、初めて意味が生まれます。

bit列 読む規則 得られる意味
01000001 8bitの符号なし整数 65
01000001 UTF-8の文字データ 文字A
01000001 16進表示 41
01000001 命令・画像・通信データ中の1byte その仕様と位置が定めるフィールドやデータの一部

符号付きと符号なしの違いも、同じ原理です。8bitの11111111を符号なし整数として読めば255ですが、8bitの2の補数による符号付き整数として読めば−1です。bitが途中で変わったのではなく、型が適用する解釈規則が変わったのです。

Javaではbyteを8bitの符号付き2の補数整数と定めています。一方、通信仕様などには8bitの符号なしフィールドもあります。「1byteだから0〜255」と決めつけず、型や仕様のsigned/unsignedを確認します。

文字なら文字コード、数値ならデータ型、通信データならプロトコル、機械語なら命令セットアーキテクチャ(ISA)が解釈を決めます。CPUも「このbit列はAだ」と自由に判断するのではなく、実行中の命令と定められた命令形式に従ってbitを処理します。CPU側の続きを知りたい場合は、CPUとはへ進んでください。

「コンピュータは16進数で処理する」は誤解

16進数は、bit列を人間向けに表示する方法です。ソースコードに0x41と書けば、コンパイラや言語処理系が「整数値65を表すリテラル」として解析します。実行時のCPUやメモリに、接頭辞0x付きの形がそのまま格納されるわけではありません。

デバッガ、メモリダンプ、文字コード表、通信ログが16進数を使うのは、1桁が4bitへ正確に対応し、2進数より短く読めるからです。10進数でも同じ値を表示できますが、bit境界との対応は見えにくくなります。

よくある誤解 正しい捉え方
コンピュータは2進数と16進数を使い分けて計算する ハードウェア上の状態はbitとして扱われ、16進数は主に人が読む表記
01000001は必ずAである UTF-8など、Aへ対応させる符号化の文脈が必要
0xも値の一部である 16進数として読むための表記上の目印
先頭0はいつでも無意味である 整数値は変えないが、表示幅やフィールド幅を考える手掛かりになる
8bitなら最大255なので255通りである 0を含むため256通り。符号なしなら0〜255

実際のログやダンプは「表記・幅・解釈」の順で読む

たとえば、UTF-8・BOMなし・改行なしでAだけを保存したmemo.txtを、Unix系環境のodで確認すると、16進数のbyte値と文字表示を分けて見られます。

$ od -An -tx1 -tc memo.txt
  41
   A

1行目の41は、-tx1で「1byte単位の16進数」と指定した結果です。2行目のAは、-tcで文字として表示した結果です。同じbyteを別の読み方で見せており、ファイル内に「41」という2文字も追加保存されているわけではありません。

  1. 表記を確認する:0x0b、列見出し、仕様書から何進数かを特定する
  2. 幅を確認する:8bit、16bit、32bitなど、何桁のフィールドかを特定する
  3. 解釈を確認する:符号付き整数、符号なし整数、UTF-8、命令、アドレスなど、何のデータかを特定する
  4. 必要な部分だけ変換する:2進数と16進数なら4bitごと、10進数なら桁の重みで分解・再構成する

変換式より先にこの3点を確認しないと、計算だけ合って意味を取り違えることがあります。2進数と16進数をマスターするとは、数字を素早く変換するだけでなく、どの規則で、そのbit列を読んでいるのかを説明できることです。

まとめ:2進数と16進数は、同じbit列を見るための表記

  • 位取り記数法では、各桁の数字に「基数の何乗」という重みを掛けて値を作る
  • 10進数65、2進数01000001、16進数41は、符号なし整数として同じ値
  • 4bitは24=16通りなので、16進数1桁と正確に1対1対応する
  • 先頭0は整数値を変えないが、幅は型やフィールド仕様から確認する
  • 0x0bは表記の目印であり、値や保存bitの一部ではない
  • bit列の意味は、データ型、文字エンコーディング、プロトコル、ISAなどの文脈が決める

memo.txtのAは、UnicodeでU+0041に対応し、UTF-8では1byteの値0x41へ符号化されます。その同じbyteを2進数で書けば01000001です。2進数から10進数へは桁の重みを足し、10進数から2進数へは値を2のべき乗へ分解し、16進数とは4bitずつ置き換えます。この「値は同じ、表記と解釈は別」という軸を持てば、ログやダンプで見かけるbit列を暗記ではなく根拠から読めます。

タイトルとURLをコピーしました