合同会社小村ソフト
第 3 章

UTF-8 を手で組み立てる

コードポイントのビットを 1〜4 バイトのテンプレートに流し込む — UTF-8 の変換を U+3042 → E3 81 82 で手計算し、ASCII 互換の理由と BOM まで確かめる。

「なぜ 3 バイトなのか」を、計算で答えられるようになる

「UTF-8 だと日本語は 3 バイト」— どこかで聞いたことはあっても、なぜ 3 なのか、その 3 バイトの中身が何なのかを説明できるでしょうか。この章の目標は 1 つ、U+3042「あ」が E3 81 82 になるまでを自分の手で導くことです。一度導けば、UTF-8 は暗記物ではなく「計算できるもの」に変わります。

UTF-8 は可変長の符号化です。コードポイントの大きさに応じて、1 文字を 1〜4 バイトで表します。

コードポイントの範囲バイト数テンプレート(x に番号のビットを流し込む)
U+0000〜U+007F10xxxxxxx
U+0080〜U+07FF2110xxxxx 10xxxxxx
U+0800〜U+FFFF31110xxxx 10xxxxxx 10xxxxxx
U+10000〜U+10FFFF411110xxx 10xxxxxx 10xxxxxx 10xxxxxx

テンプレートの黒い部分(111010)は固定の「印」です。先頭バイトの 1 の個数が文字のバイト数を名乗り、続きのバイトは必ず 10 で始まる — この規則だけで、バイト列のどこからでも文字の切れ目が見つけられます。

手計算 — U+3042 を 3 バイトにする

「あ」U+3042 は U+0800〜U+FFFF の範囲なので 3 バイトのテンプレートを使います。手順は 2 ステップです。

Step 1 — 番号を 2 進数にして、右から 6・6 で区切る
0x3042 = 0011 0000 0100 0010。右から 6 ビットずつ切ると 0011 / 000001 / 000010(4 + 6 + 6 ビット)
Step 2 — テンプレートの x に前から埋める
11100011 10000001 10000010 → 16 進に直すと E3 81 82

検算してみましょう。E3 = 1110 0011 — 先頭に 1 が 3 個なので「3 バイト文字の先頭」。81 = 1000 0001、82 = 1000 0010 — どちらも 10 で始まる「続きのバイト」。印を剥がして残りを繋げば 0011 000001 000010 = 0x3042 に戻ります。行きも帰りも、ただのビットの詰め替えです。

U+3042 の 16 ビットを 4・6・6 に区切り、1110xxxx 10xxxxxx 10xxxxxx のテンプレートに流し込んで E3 81 82 を得る図

コードポイントのビットがテンプレートに流し込まれる様子。色の付いたビットが「あ」の番号の中身で、黒い部分は UTF-8 の固定の印です。

小問 3-1 — テンプレートに流し込む

本文の手順どおり、紙に 2 進数を書きながら確かめてください。

Q1. UTF-8 のバイト列を先頭から読むとき、そのバイトが「何バイト文字の先頭か」はどう分かりますか。

Q2. U+3042(2 進で 0011 0000 0100 0010)を 3 バイトのテンプレート 1110xxxx 10xxxxxx 10xxxxxx に流し込む正しい手順はどれですか。

Q3. ASCII 文字は UTF-8 で 1 バイト、日本語の漢字やひらがなの多くは 3 バイトになります。文字列「Hello世界」は UTF-8 で何バイトですか。

バイト

なぜ ASCII 互換が「効く」のか

1 バイトのテンプレート 0xxxxxxx をよく見てください。U+0000〜U+007F、つまり ASCII の 128 文字は、ASCII 時代とまったく同じ 1 バイトになります。英数字だけのファイルは、ASCII として読んでも UTF-8 として読んでも同一 — 既存のプログラムやプロトコルをほとんど変えずに移行できたのは、この設計のおかげです。

もう 1 つ、見逃されがちな急所があります。2 バイト以上の文字を構成するバイトは、先頭(11 で始まる)も続き(10 で始まる)も必ず 0x80 以上です。つまり、日本語の文字のバイト列の途中に、/(0x2F)や "(0x22)のような ASCII の記号と同じ値が絶対に現れません。区切り文字をバイト単位で探すプログラムが、文字の途中を誤って区切る事故が構造的に起きないのです。この安心が当たり前でなかったことを、次章の Shift_JIS で思い知ることになります。

シミュレータで確かめる

下のシミュレータに好きな文字列を入れると、この章で手計算した変換をまとめて確認できます。まずは初期値の「こんにちは」で、5 文字が 15 バイトになること、どの文字も E3 で始まることを見てください。自分の名前、絵文字、英日混在の文字列もおすすめです。入力はブラウザ内だけで処理され、サーバには送信されません。

シミュレータの動作概要(テキスト版): テキスト入力欄に文字列を入れると、文字ごとにコードポイント(U+XXXX)・UTF-8 のバイト列(16 進)・UTF-16 のコード単位が表になって更新されます。あわせて UTF-8 の総バイト数・UTF-16 のコード単位数・コードポイント数・見た目の文字数(書記素)の 4 つの数値が表示され、最後に「この UTF-8 バイト列を Shift_JIS として誤読するとどう見えるか」が表示されます(お使いのブラウザが Shift_JIS のデコードに対応していない場合、その欄には「お使いのブラウザでは表示できません」と表示されます)。

BOM — ファイル先頭の小さな「しるし」

シミュレータの最後の欄で、UTF-8 のバイト列を Shift_JIS として誤読した姿を見ました。「読む側が符号化を当てられるように、ファイルの先頭に目印を置けないか」— その答えの 1 つが BOM(Byte Order Mark)です。コードポイント U+FEFF を UTF-8 で符号化した EF BB BF の 3 バイトをファイルの先頭に置きます。

名前のとおり、本来は UTF-16 でバイトの並び順(上位が先か下位が先か)を示すための仕組みです。UTF-8 にはバイト順の問題がないので、BOM は純粋に「これは UTF-8 です」という任意の目印として使われます。付いていれば手がかりになる一方、先頭の 3 バイトを想定していないプログラムには余計なデータとして誤動作の種にもなります。「BOM は必須ではない。ただし Excel など、BOM を頼りに符号化を判定するソフトがある」— この綱引きの実務的な帰結は第 6 章で扱います。

小問 3-2 — ASCII 互換と BOM

UTF-8 が世界標準になれた理由と、ファイル先頭の「しるし」を確認します。

Q4. UTF-8 の「ASCII 互換」が安全に成り立つ理由として正しいものはどれですか。

Q5. 「こんにちは」(ひらがな 5 文字、すべて 3 バイト)は UTF-8 で何バイトですか。

バイト

Q6. UTF-8 の BOM(Byte Order Mark)の説明として正しいものはどれですか。

この章で持ち帰ること

  • UTF-8 は 1〜4 バイトの可変長。先頭バイトの 1 の個数がバイト数を名乗り、続きは必ず 10 で始まる
  • U+3042 → 0011 / 000001 / 000010 → E3 81 82。変換はビットの詰め替えにすぎない
  • ASCII は UTF-8 でも同じ 1 バイト、多バイト文字の構成バイトはすべて 0x80 以上 — この 2 点が ASCII 互換の芯
  • BOM は U+FEFF の符号化 EF BB BF。UTF-8 では必須でない「目印」

次章は、UTF-8 より前から日本語を支えてきた Shift_JIS へ。「多バイト文字の途中に ASCII の値が現れない」という UTF-8 の安心が、いかに貴重だったかが分かります。