nfc nfd 違い:PythonとJavaScriptで「が」は等しい?

要点

  • NFCとNFDで作った「が」は、PythonとJavaScriptのどちらでも比較で一致しなかった(出典: 自社の実測・2026-10-25)。
  • 今回の「が」は、NFCでは1コードポイント、NFDでは2コードポイントだった(出典: 自社の実測・2026-10-25)。
  • NFD側をNFCへ正規化すると、両言語ともNFC側の文字列と一致した(出典: 自社の実測・2026-10-25)。

見た目が同じ「が」は、比較でも同じなのか

画面上で同じ「が」に見える文字列を用意しても、プログラムの比較で一致するとは限らない。今回確認したのは、NFCとNFDという異なる正規化形式で作った「が」を、そのまま比較した場合の違いだ。

Unicode正規化とは、文字列をNFCやNFDなどの指定した正規化形式に揃える処理のことだ。

今回のNFCの「が」は、コードポイントU+304Cの1文字だった。一方、NFDの「が」は、「か」に当たるU+304Bと、結合文字の濁点に当たるU+3099で構成されていた。見た目は同じでも、比較対象となる文字列の構成が異なる。

ここでの問いは、画面に何文字として見えるかではない。異なる構成で用意した文字列が、等価比較で同じものとして扱われるか、という問いである。Pythonでは==、JavaScriptでは===を使って確認したところ、どちらも一致しなかった。

実測日は2026-10-25。記録にある実行環境はPython 3.9.0とNode.js v22.23.4である。以下の比較結果は、この環境で実行した記録に基づく。

出典: 自社の実測(2026-10-25)

公式資料が示す正規化の指定方法

素材に収録されたPython公式ドキュメントの記述では、unicodedata.normalize(form, unistr)が、NFC・NFD・NFKC・NFKDの4形式を扱うとされている。今回の実測で使ったのは、そのうちNFCとNFDだけだ。

Pythonでは、形式を第1引数に、正規化する文字列を第2引数に指定している。実測コードでも、同じ「が」に対して'NFC'と'NFD'をそれぞれ渡し、比較する文字列を作っている。

出典: https://docs.python.org/3/library/unicodedata.html (取得日 2026-10-25)

JavaScriptについては、素材に収録されたMDNの記述で、String.prototype.normalize()の既定の正規化形式がNFCであると示されている。ただし、今回の実測コードは既定値に任せず、normalize('NFC')とnormalize('NFD')のように形式を明示している。

比較のために読むコードでは、この指定が重要になる。どの形式の文字列を作り、どの形式へ変換してから比較したのかを追えるためだ。今回の結果も、形式を明示したコードと組み合わせて読む必要がある。

出典: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize (取得日 2026-10-25)

なお、提供された素材には、これらの公式ページの説明要約とAPI表記が収録されている。公式本文の原文そのものは収録されていないため、説明要約を公式の逐語引用としては扱わない。

PythonとJavaScriptの実測結果を比べる

Pythonで実行した記録を、そのまま示す。コメントにある値は、素材に収録された実測結果である。

import unicodedata
nfc = unicodedata.normalize('NFC', 'が')
nfd = unicodedata.normalize('NFD', 'が')
print(len(nfc), [hex(ord(c)) for c in nfc])  # 1 ['0x304c']
print(len(nfd), [hex(ord(c)) for c in nfd])  # 2 ['0x304b', '0x3099']
print(nfc == nfd)  # False

この記録では、長さだけでなく、各文字のコードポイントも確認している。len()の結果が異なることに加え、NFC側とNFD側で文字列の構成が異なることを読み取れる。

JavaScriptでも、同じ「が」をそれぞれの形式に正規化し、長さと等価比較の結果を確認している。両言語の結果を並べると、今回の対象では次のようになった。

比較した言語 NFCの「が」の長さ NFDの「が」の長さ 正規化形式が異なるまま比較 NFD側をNFCへ正規化して比較
Python 1 2 False True
JavaScript 1 2 false true

Pythonの長さはlen()、JavaScriptの長さは.lengthで確認した値だ。この表は、今回の「が」に対する結果を並べたものであり、別の文字列について両者の長さの数え方まで同じだと示すものではない。

比較演算子にも違いがある。Pythonでは==、JavaScriptでは===を使用した。それぞれのコードで、形式が異なるままでは不一致となり、NFD側をNFCへ揃えると一致した。

出典: 自社の実測(2026-10-25)

比較前にどちらへ揃えるかと、結果の限界

今回、対処として実測したのは、NFDの文字列をNFCへ変換してから比較する方法である。Pythonでは次の行で一致を確認した。

print(unicodedata.normalize('NFC', nfd) == nfc)  # True

JavaScriptでも、同じ方向の変換で一致した。

console.log(nfd.normalize('NFC') === nfc); // true

もともとNFCで用意した側に、NFD側の形式を合わせたことになる。この結果から、今回の「が」の不一致には、比較前に正規化形式を揃える処理が有効だったと分かる。NFCとNFDのどちらがあらゆる用途で優れているかを、この記録だけで決めることはできない。

検索キーや重複チェックに文字列を使う場面でも、まず比較前の形式を確認する、という切り分けにつながる。ただし、実際に測ったのは文字列の等価比較であり、辞書やSetを使った処理そのものではない。それらの挙動を、今回の実行結果として載せることはできない。

ファイル名についても別の実測がある。NFCの名前でファイルを作成し、globで取得した名前を比較すると、NFC側とは一致し、NFD側とは一致しなかった。ただし、素材にはOSやファイルシステムの詳細が記載されていない。この結果は今回のマシンでの記録に限られ、別の環境へ一般化できない。

また、NFKCとNFKDは未測定である。今回確認できたのは、NFCとNFDで用意した「が」の不一致と、NFCへ揃えた後の一致までだ。

出典: 自社の実測(2026-10-25)

自分の環境で確かめる手順

Pythonで同じ比較を確かめるには、端末で次のコマンドを実行する。これは読者が自分の環境で確認するための手順であり、このコマンドを新たに実行した結果は掲載していない。

python3 --version
python3 -c "import unicodedata as u; a=u.normalize('NFC','が'); b=u.normalize('NFD','が'); print(len(a),len(b)); print(a==b); print(a==u.normalize('NFC',b))"

確認する順序は、実行環境の版、正規化後のそれぞれの長さ、形式を揃える前の比較、揃えた後の比較となる。見た目だけで一致を判断せず、同じ入力から作った文字列について、正規化前後の比較を対にして確認できる。

コメント

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