Unicode 正規化
同じ文字の異なる表現を統一する処理。NFC, NFD, NFKC, NFKD の 4 形式がある。
Unicode 正規化とは、同じ文字を表す異なるコードポイント列を統一的な形式に変換する処理です。Unicode では同一の文字を複数の方法で表現できる場合があり、たとえば「が」は 1 つのコードポイント U+304C (合成済み形式) でも、「か」(U+304B) +「゛」(U+3099、結合濁点) の 2 つのコードポイントでも表現できます。見た目は同じでもバイト列が異なるため、正規化なしでは文字列比較が正しく機能しません。
正規化には 4 つの形式があります。NFC (Canonical Decomposition followed by Canonical Composition) は分解してから再合成する形式で、Web 標準として推奨されています。NFD (Canonical Decomposition) は正準分解のみを行い、結合文字を分離した形式にします。NFKC (Compatibility Decomposition followed by Canonical Composition) は互換分解後に合成する形式で、全角英数字を半角に変換するなどの互換性変換を含みます。NFKD (Compatibility Decomposition) は互換分解のみを行います。Unicode テキスト処理の書籍で正規化の詳細を学べます。
実務では正規化の選択がシステムの挙動に大きく影響します。検索エンジンやデータベースでは、ユーザーの入力と格納データの正規化形式が一致していないと、見た目が同じ文字列でも検索にヒットしない問題が発生します。たとえば、あるユーザーが NFC で入力した「が」と、別のユーザーが NFD で入力した「が」は、正規化なしでは別の文字列として扱われます。JavaScript では String.prototype.normalize() メソッドで任意の形式に変換でき、Python では unicodedata.normalize() 関数が利用できます。
macOS のファイルシステム (APFS および旧 HFS+) は NFD に近い独自の正規化形式を使用するため、他の OS との間でファイル名の互換性問題が生じることがあります。たとえば、macOS で作成した「ファイル.txt」を Windows や Linux に転送すると、ファイル名の比較で不一致が起きる場合があります。Git でもこの問題は知られており、core.precomposeunicode 設定で対処できます。
よくある誤解として、正規化は日本語や韓国語など特定の言語だけの問題と思われがちですが、実際にはラテン文字のアクセント記号 (é、ñ など) やアラビア文字の結合形式など、多くの言語で正規化が必要です。また、NFKC 正規化は全角英数字を半角に変換するため、意図せず文字の見た目が変わることがあり、検索用途では便利ですが表示用途では注意が必要です。国際化プログラミングの書籍で正規化の実践的な活用方法が解説されています。
文字数カウントの観点では、正規化形式によって同じ文字のコードポイント数が変わるため、カウント結果に差が生じます。NFC では「が」は 1 コードポイントですが、NFD では 2 コードポイントになります。正確な文字数を把握するには、カウント前にテキストを統一的な正規化形式に変換しておくことが推奨されます。