Unicodeで半角全角を扱う Ambiguous（曖昧さ）とUncertainty（不確実性）の恐怖

# はじめに

この記事は[こちらで発表した](https://csharp-tokyo.connpass.com/event/178164/)内容を、読み物としてまとめなおしたものです。[こちら](https://youtu.be/pbCRHAM5NG0)に動画も公開していますので、よかったら合わせてご覧ください。

さて、みなさんも「コンソールで全角半角を適切に扱って表示したい」なんて考えたことが、一度はあるのではないでしょうか？

かくいう私は、ツール作成やAPIのお試しのときに、コンソールにテーブル状に結果表示したいと思うことが何度かありました。そのため[FluentTextTable](https://qiita.com/Nuits/items/88b52cffc0d96e806609)というコンソールに表を出力するテキストテーブルライブラリを自作し、その中でUnicodeも文字列の全角・半角判定を実装したのですが・・・そこで遭遇してしまいました。

**「Unicodeで半角全角を扱う Ambiguous（曖昧さ）とUncertainty（不確実性）の恐怖」**

に。

結論から言いますと、Unicodeで半角全角を完璧に扱い分けることは不可能です。

今回はこのあたりの闇についてお伝えしたいと思います。

# Unicodeにおける半角全角判定

まずは基本から。Unicodeには、各種文字種別を扱うための複数の辞書が規定されています。

半角全角を規定した辞書も存在していて、それが「[UAX #11: East Asian Width](http://www.unicode.org/reports/tr11/tr11-38.html)」で、実際の辞書は[こちら](http://ftp.unicode.org/Public/UNIDATA/EastAsianWidth.txt)に公開されています。

中を覗いてみると、つぎのように記述されているのが見て取れます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99262/8a97aa4b-3aca-fe8b-cd7a-40909b1a2d87.png)

半角全角を判定する上で必要なのは、#より左の「;」で区切られた2つの領域です。

「;」の左側は文字コードの範囲を表していて、例えば「0000..001F」であれば「0x0000～0x001F」の範囲を表しますし、「0020」であれば単独の「0x0020」の文字を表します。

「;」の右側は文字の種別で、East Asian Widthでは6種類規定されています。それぞれの種別は東アジア圏の文章か、それ以外で記述されたかどうかによって振る舞いが変わります。

具体的には以下の通りです。

|種別|東アジア|それ以外|
|:--|:--|:--|
|Ambiguous|全角|半角（正確にはnarrow）|
|Fullwidth|全角|未使用|
|Halfwidth|半角|未使用|
|Narrow|半角|半角（正確にはnarrow）|
|Wide|全角|未使用|
|Neutral|半角|半角（正確にはnarrow）|

Ambiguousだけ東アジアか否かによって扱いを変える必要があります。

FullwidthとWideは東アジア圏では全角で扱いますが、それ以外の文化圏の文章には登場しないため考慮する必要がありません。

東アジア圏かどうか？をどう判定するべきかはプラットフォームによって異なります。私は.NETで扱ったのでデフォルトはCurrentUICultureInfoで処理分岐するようにしました。

さて、ここまでが基本です。

ここから先が闇です。

# 闇の始まり

さて、先ほどの扱いについては、[UAX #11: East Asian Width](http://www.unicode.org/reports/tr11/tr11-38.html)に明確に記載されています。

しかし、実際に文字をひとつずつ追いかけていくと怪しい文字が頻出します。

ここからは日本で最も著名な等幅フォントである「MS ゴシック」で見ていきたいと思います。

さてAmbiguousは全角で扱います。Ambiguousには「☎」や「®」が含まれます。これをWindowsのメモ帳で見てみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99262/24fb170c-40a6-34c3-c5ef-d0592e7f73cc.png)

は？私は初めて見た時、辞書を読み間違えたのかと思いました。

しかし「®」は0x00aeで、確かにAmbiguousと規定されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99262/e4753497-bb6b-1395-0172-518c61024554.png)

これを皮切りに、出るわ出るわ。怪しい文字が・・・

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99262/36e0ccb5-0e2f-debf-381a-7c302d908697.png)

どちらもNeturalで半角表示のはずです。「©」はちゃんと表示されていますが「⏭」はなぜか全角です。

また「⏭」に似てるのに、なぜかWideな「⏩」ですが、同じWideの「⌛」はここに極まれりという感じで・・・

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99262/f2767448-c187-5214-432f-ba5e9212a7e0.png)

は？0.75角？でもこれ実はよくよく見ると⏭も⏩も実はおかしくて・・・

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99262/f48be711-ceaf-ba7b-3475-c227c3e8fbc1.png)


ずれとる・・1.03125角・・・・。

# バグも

Linuxのコンソールでは半角全角の扱いは難しいようですが、まともだと思っていたWindowsのコマンドプロンプトでもバグを踏んでしまいました。

先にも書いたように、私は今回コンソール出力するためのテキストテーブルライブラリを作成していました。

日本人ならテキストでテーブル記述するなら罫線文字「┌─┬┐」を使いたいですよね？FluentTextTableでは罫線をカスタマイズ可能にしていたので、罫線文字をつかうプリセットを用意してみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99262/94f927c3-f316-b335-c86d-0775854488df.png)

出力しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99262/a4fd4414-7c24-be04-fc22-00c66fdd1cee.png)

はぁ？これを選択して、メモ帳様にコピペすると・・・

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99262/0c5f677b-a194-140e-bde1-095922c91fa3.png)

ちゃんと表示されます。なぜだ・・・なぜなんだ・・・。はっもしかして！？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99262/1145b135-6e40-015d-71e1-d5dda74b25e1.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99262/070ef87e-e209-0663-fdfe-c7ed6e7271ac.png)

バグか・・・

いやでもコマンドプロンプトはWindowsでももうオワコン。これからの時代はクロスプラットフォーム化されたWindows Terminal様の出番のはずだ！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99262/0f47d53e-8176-4069-5a54-04b5a6f750f3.png)

貫通はしない。貫通はしないが半角！ずれるじゃん！

# まとめ

という訳で、Unicodeで半角全角を扱う場合、まずフォントがEast Asian Widthの規定を正しく守っている必要があります。でも実際にはそうとは限りません。

またそれを取り扱うプラットフォームも適切に実装されている必要がありますが、必ずしもそうとは限りません。

まぁ後者はOSSであれば修正に協力することも可能でしょうが、前者は・・・

Unicode時代に半角全角を扱う際は、ある程度は「諦め」が必要です。

というわけで

**「Unicodeで半角全角を扱う Ambiguous（曖昧さ）とUncertainty（不確実性）の恐怖」**

の片りんを味わっていただけたでしょうか？

今回はここまで。それではまた！