全角・半角が混ざったデータを整える方法 — NFKC正規化と注意点
公開日:
「ABC123」と「ABC123」、「ガイド」と「ガイド」のように、見た目は同じでも全角・半角が異なる文字が混ざったデータは、検索や集計の思わぬ不具合の原因になります。この記事では、表記ゆれが起きる原因と困る場面、JavaScriptでまとめて正規化する方法、そして正規化の副作用や紛らわしい記号の扱いを解説します。
なぜ全角と半角が混ざるのか
日本語の文字には、ASCIIの英数字・記号に対応する「全角形」(A・1・!など)と、古い端末向けの規格(JIS X 0201)に由来する「半角カナ」(ア・ガなど)があります。Unicodeでもこれらは互換性のために別の文字として収録されており、見た目が似ていてもコンピューター上では別の文字です。混在が起きる主な原因は次のとおりです。
- IMEが全角英数モードのまま、数字やメールアドレスを入力した
- Excelで手入力したデータや、関数(ASC・JISなど)で変換したデータが混ざっている
- 古い基幹システムや金融機関向けのシステムから、半角カナのCSVが出力された
- Webフォームの入力値を正規化せずにそのまま保存していた
混在によって起きる問題
- 検索: 「ABC」で検索しても「ABC」がヒットしない
- 重複チェック: 同じ会社名や住所が別のデータとして登録される
- 並べ替え: 文字コード順に並べると、全角と半角が離れた位置に並ぶ
- 入力チェック: 全角数字で入力された電話番号や郵便番号が形式エラーになる
プログラミング言語による違いにも注意が必要です。JavaScriptの正規表現の \d は半角の0〜9にしかマッチせず、Number("123") も NaN になります。一方、Pythonの re モジュールでは \d が全角数字にもマッチし、int("123") は123に変換されます。同じデータでも言語やライブラリによって入力チェックの結果が変わることがあるため、入力を受け取った段階で正規化しておくのが確実です。
NFKC正規化でまとめて整える
Unicodeには、見た目や意味が同じとみなせる文字を統一する「正規化」の仕組みがあります。中でもNFKC(互換分解の後に正規合成する形式)は、全角英数字を半角に、半角カナを全角カナにそろえるため、日本語データの表記ゆれ対策によく使われます。JavaScriptでは String.prototype.normalize で実行できます。
const input = "ABC123 ガイド";
input.normalize("NFKC");
// => "ABC123 ガイド"(全角スペースも半角スペースになる)Pythonでは unicodedata.normalize("NFKC", text) で同じ処理ができます。半角カナの濁点「゙」は、直前の文字と結合できる場合(ガ → ガ)は1文字にまとまります。ただし「ア゙」のように対応する全角文字がない組み合わせでは、「ア」に結合用の濁点(U+3099)が付いた2文字になり、単独の「゙」も結合用の濁点に変わるため、表示が崩れることがあります。
NFKCの副作用に注意
NFKCは全角・半角だけでなく、「互換文字」全般を標準的な文字に置き換えます。例えば次のような変換も起こります。
- 「㈱」→「(株)」、「㍻」→「平成」、「㌔」→「キロ」
- 「①」→「1」、「²」→「2」(m² が m2 になる)
- 「½」→「1⁄2」(分数用のスラッシュを使った3文字)
- 「…」→「...」(ピリオド3つ)、「™」→「TM」
- 「~」(全角チルダ)→「~」、「¥」→「¥」
逆に、NFKCでは変わらない文字もあります。全角カタカナは半角カナにならず、ひらがなとカタカナも区別されたままです。ハイフンに似た文字(「-」「−」「‐」「ー」など)も、全角のハイフンマイナス「-」が「-」になる以外は、それぞれ別の文字として残ります。
正規化は元に戻せない変換です。元のデータは残したまま、検索用・比較用の列に正規化した値を保存するといった使い方が安全です。また「㈱」が「(株)」になると1文字が3文字に増えるように、正規化で文字数が変わることもあります。文字数に上限がある項目では、正規化後の文字数を文字数カウントで確認しましょう。
種類ごとに変換したい場合
「英数字は半角にそろえたいが、①や㈱はそのまま残したい」といった場合は、NFKCではなく対象の文字だけを変換します。全角の英数字(A〜Z・a〜z・0〜9)は、対応する半角文字とコードポイントがちょうど 0xFEE0 ずれているため、次のように変換できます。
const toHalfWidthAlnum = (s) =>
s.replace(/[A-Za-z0-9]/g, (c) => String.fromCharCode(c.charCodeAt(0) - 0xfee0));
toHalfWidthAlnum("ABC123①"); // => "ABC123①"半角カナの全角化は、濁点・半濁点の結合まで含めた対応表が必要で、自前で実装すると漏れが出やすい部分です。このサイトの全角・半角変換ツールでは、英字・数字・記号・スペース・カタカナを種類ごとにオン・オフして変換でき、ガ・パ・ヴのような濁点付きの半角カナも1文字にまとめます。英字を半角にそろえた後、変数名やキー名の命名規則(camelCase・snake_caseなど)を統一したい場合は、テキストケース変換も使えます。
波ダッシュと円記号の落とし穴
全角・半角の整理で特に混乱しやすいのが「〜」と「\」です。
「〜」には、波ダッシュ(U+301C)と全角チルダ(U+FF5E)という、見た目のよく似た2つの文字があります。Shift_JISの同じ文字が、変換表によって波ダッシュにも全角チルダにも対応付けられてきた経緯があり、WindowsのCP932では全角チルダとして扱われます。そのため「10〜20」のようなデータが、システム間を通ると別の文字に変わったり、文字化けしたりすることがあります。NFKCでは全角チルダは半角の「~」になりますが、波ダッシュは変わりません。
円記号「¥」とバックスラッシュ「\」も紛らわしい組み合わせです。日本語版のWindowsなどでは、バックスラッシュ(0x5C)が円記号の形で表示されてきたため、同じ文字が環境によって「¥」に見えたり「\」に見えたりします。一方、Unicodeの円記号(U+00A5)や全角の「¥」(U+FFE5)はバックスラッシュとは別の文字で、NFKCでも「¥」は「¥」になり、「\」にはなりません。金額やファイルパスを含むデータでは、実際にどの文字が使われているかを確認してから変換ルールを決めましょう。
このサイトの全角・半角変換ツールも、「\」と「\」、「¥」と「¥」をそれぞれ別の文字として対応させています。波ダッシュ「〜」は半角にすると「~」になり、全角に戻すと全角チルダ「~」になります。