UTF-8 のバイト長を返す

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

UTF-8 のバイト長を返す

このレッスンで分かること

  • ASCII の世界は 128 文字しか扱えませんでした
  • このレッスンでは、1 つの コードポイント を整数で受け取り、UTF-8 に変換した場合のバイト長を返す関数を実装します
  • UTF-8 のバイト数は、コードポイントの大きさで決まります

UTF-8 のバイト長を返す とは

1 つの Unicode コードポイントを受け取り、UTF-8 に符号化したときのバイト数を返す関数を実装します。

ASCII の世界は 128 文字しか扱えませんでした。しかし現実の世界では日本語・中国語・絵文字など、もっとたくさんの文字が必要です。そこで使われているのが UTF-8 という符号化方式です。UTF-8Unicode という国際標準の文字集合 (各文字に一意な番号 = コードポイント を割り当てたもの) を、14 byte の可変長バイト列に変換します。ASCII 範囲は 1 byte のままなので、英語のテキストは従来と完全互換のまま動きます。日本語の 3 byte、絵文字の多くは 4 byte です。

このレッスンでは、1 つの コードポイント を整数で受け取り、UTF-8 に変換した場合のバイト長を返す関数を実装します。実際にエンコードを書くのではなく、Unicode の値の範囲だけからバイト数を判定するロジックです。U+0000..U+007F1 byteU+0080..U+07FF2 byteU+0800..U+FFFF3 byteU+10000..U+10FFFF4 byte というルールが知られています。

UTF-8 は ASCII 互換の可変長エンコーディング。だから既存の英文プログラムやネット規格を壊さずに、Unicode を扱えるようになりました。

範囲とバイト数の対応

UTF-8 のバイト数は、コードポイントの大きさで決まります。次の境界値を覚えておくと判定式がスッと書けます。

コードポイント (16 進)バイト数
0x00 - 0x7F1
0x80 - 0x7FF2
0x800 - 0xFFFF3
0x10000 - 0x10FFFF4

これを Python で書くと次のようになります。

Python

def utf8_len(cp): if cp < 0x80: return 1 if cp < 0x800: return 2 if cp < 0x10000: return 3 return 4

JavaScriptJavaGo も同じ条件分岐で実装できます。10 進数で書くと 128204865536 という値ですが、16 進で書くほうが境界の意味が読みやすくなるためおすすめです。

JavaScript

function utf8Len(cp) { if (cp < 0x80) return 1; if (cp < 0x800) return 2; if (cp < 0x10000) return 3; return 4; }

バイト割り当ての全体像

diagram (will load when visible)

図のポイント (テキスト併記)

  • 範囲を「2 倍ずつではなく、桁の境界で増える」と覚えると暗記しやすいです

範囲を「2 倍ずつではなく、桁の境界で増える」と覚えると暗記しやすいです。 7F 7FF FFFF 10FFFF の 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 つめは、Javaint が常に 32 bit signed であることを忘れて負数を渡してしまうケース。UTF-8 のコードポイントは負にならない前提なので、テストでも非負だけ扱います。3 つめは、Goruneint だと勘違いするケース。runeint32 のエイリアスですが、関数シグネチャは int で十分です。

やってみよう

整数 cp (Unicode コードポイント) を受け取り、UTF-8 に変換した場合のバイト長 (14) を返す関数 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 つができることを確認しましょう。

  1. UTF-8 バイト長 の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. UTF-8 バイト長 とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. 3 つの境界 (0x80, 0x800, 0x10000) で判定する
  2. 1〜4 の整数を返す
  3. 範囲外の入力は考慮しなくてよい

入出力例

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

ヒント

main.py
main.py
学習モード

メモ

UTF-8 のバイト長を返す

⌘S で保存