UTF-8 のバイト長を返す
UTF-8 のバイト長を返す
このレッスンで分かること
ASCIIの世界は 128 文字しか扱えませんでした- このレッスンでは、1 つの
コードポイントを整数で受け取り、UTF-8に変換した場合のバイト長を返す関数を実装しますUTF-8のバイト数は、コードポイントの大きさで決まります
UTF-8 のバイト長を返す とは
1 つの Unicode コードポイントを受け取り、UTF-8 に符号化したときのバイト数を返す関数を実装します。
ASCII の世界は 128 文字しか扱えませんでした。しかし現実の世界では日本語・中国語・絵文字など、もっとたくさんの文字が必要です。そこで使われているのが UTF-8 という符号化方式です。UTF-8 は Unicode という国際標準の文字集合 (各文字に一意な番号 = コードポイント を割り当てたもの) を、1 〜 4 byte の可変長バイト列に変換します。ASCII 範囲は 1 byte のままなので、英語のテキストは従来と完全互換のまま動きます。日本語の あ は 3 byte、絵文字の多くは 4 byte です。
このレッスンでは、1 つの コードポイント を整数で受け取り、UTF-8 に変換した場合のバイト長を返す関数を実装します。実際にエンコードを書くのではなく、Unicode の値の範囲だけからバイト数を判定するロジックです。U+0000..U+007F は 1 byte、U+0080..U+07FF は 2 byte、U+0800..U+FFFF は 3 byte、U+10000..U+10FFFF は 4 byte というルールが知られています。
UTF-8は ASCII 互換の可変長エンコーディング。だから既存の英文プログラムやネット規格を壊さずに、Unicode を扱えるようになりました。
範囲とバイト数の対応
UTF-8 のバイト数は、コードポイントの大きさで決まります。次の境界値を覚えておくと判定式がスッと書けます。
| コードポイント (16 進) | バイト数 |
|---|---|
0x00 - 0x7F | 1 |
0x80 - 0x7FF | 2 |
0x800 - 0xFFFF | 3 |
0x10000 - 0x10FFFF | 4 |
これを Python で書くと次のようになります。
Python
def utf8_len(cp):
if cp < 0x80: return 1
if cp < 0x800: return 2
if cp < 0x10000: return 3
return 4JavaScript も Java も Go も同じ条件分岐で実装できます。10 進数で書くと 128、2048、65536 という値ですが、16 進で書くほうが境界の意味が読みやすくなるためおすすめです。
JavaScript
function utf8Len(cp) {
if (cp < 0x80) return 1;
if (cp < 0x800) return 2;
if (cp < 0x10000) return 3;
return 4;
}バイト割り当ての全体像
図のポイント (テキスト併記)
-
範囲を「2 倍ずつではなく、桁の境界で増える」と覚えると暗記しやすいです
範囲を「2 倍ずつではなく、桁の境界で増える」と覚えると暗記しやすいです。
7F7FFFFFF10FFFFの 4 つの上限を押さえれば OK。
実例で確かめる
各範囲の代表例を表にしておきます。実装したらこれらの値で手元確認すると安心です。
A(U+0041) は1 byteé(U+00E9) は2 byteあ(U+3042) は3 byte世(U+4E16) は3 byte🐍(U+1F40D) は4 byte
Python
print('あ'.encode('utf-8')) # b'\xe3\x81\x82' (3 byte)
print('A'.encode('utf-8')) # b'A' (1 byte)
print('🐍'.encode('utf-8')) # b'\xf0\x9f\x90\x8d' (4 byte)Unicode の最大値は
U+10FFFF(約 110 万) です。UTF-8はこの範囲をすべて1..4 byteにエンコードできます。
よくある間違い
1 つめは、境界の < と <= の取り違えです。0x80 未満 が 1 byte の領域なので、cp < 0x80 で書くのが正解です。<= を使うと 0x80 自身を 1 byte 領域に含めてしまい誤判定になります。2 つめは、Java の int が常に 32 bit signed であることを忘れて負数を渡してしまうケース。UTF-8 のコードポイントは負にならない前提なので、テストでも非負だけ扱います。3 つめは、Go の rune を int だと勘違いするケース。rune は int32 のエイリアスですが、関数シグネチャは int で十分です。
やってみよう
整数 cp (Unicode コードポイント) を受け取り、UTF-8 に変換した場合のバイト長 (1〜4) を返す関数 utf8Len(cp) を 4 言語で実装します。0x80 0x800 0x10000 の 3 つの境界で if をネストするか、elif で順番に判定するのが分かりやすいです。cp が範囲外 (< 0 や > 0x10FFFF) の場合は考慮不要です。エンコードそのものを実装する必要はなく、バイト数だけ正しく返せれば OK です。
よくある質問
Q. ASCII と UTF-8 の違いは?
A. ASCII は 7 ビットで英数字と記号 128 種類を表す古い規格です。UTF-8 は ASCII 互換で、日本語や絵文字など世界中の文字を 1〜4 バイト可変長で表現します。現代の Web は UTF-8 がデファクトで、ASCII は UTF-8 のサブセットとして自然に含まれます。
Q. 日本語 1 文字は何バイトですか?
A. UTF-8 では大半の日本語が 3 バイト、絵文字や一部の漢字(𠮟など)は 4 バイトです。UTF-16 では基本 2 バイト、サロゲートペア対象だと 4 バイトです。Java の char は UTF-16 単位なのでサロゲートペアを扱う場面で codePointAt を使う必要があります。
Q. 文字コードを意識する場面は?
A. ファイル入出力、外部 API のレスポンス、DB の Collation 設定で意識します。文字化けの 9 割は「読み込み時の charset 指定漏れ」で起きるため、IO のたびに encoding を明示する習慣が大切です。Python は open(file, encoding='utf-8') が基本です。
次のレッスン
次は AND / OR の真理値表 で、2 つの真偽値に対する論理積(AND)・論理和(OR)の振る舞いを、真理値表として整理します。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- UTF-8 バイト長 の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. UTF-8 バイト長 とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
要件
- 3 つの境界 (0x80, 0x800, 0x10000) で判定する
- 1〜4 の整数を返す
- 範囲外の入力は考慮しなくてよい
入出力例
test-cases.txt
utf8Len(65) → 1
utf8Len(127) → 1
utf8Len(128) → 2
utf8Len(233) → 2
utf8Len(2047) → 2
utf8Len(2048) → 3
utf8Len(12354) → 3
utf8Len(65535) → 3
utf8Len(65536) → 4
utf8Len(128013) → 4