Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
文字数の話 〜Unicodeの楽しい話〜
Search
しゅん🌙
July 02, 2023
Programming
0
210
文字数の話 〜Unicodeの楽しい話〜
プログラミングをする際に間違えがちな文字数のカウント方法について,Unicodeの仕組みを紐解きながら解説しています.
しゅん🌙
July 02, 2023
Tweet
Share
More Decks by しゅん🌙
See All by しゅん🌙
Rustで対戦型Tetrisを作った話
shunshobon
0
280
文字数の話の続き 〜Unicodeの楽しくない話〜
shunshobon
0
130
Haskellの並列・並行処理
shunshobon
1
280
Other Decks in Programming
See All in Programming
range over funcの使い道と非同期N+1リゾルバーの夢 / about a range over func
mackee
0
110
20年もののレガシープロダクトに 0からPHPStanを入れるまで / phpcon2024
hirobe1999
0
440
fs2-io を試してたらバグを見つけて直した話
chencmd
0
230
KMP와 kotlinx.rpc로 서버와 클라이언트 동기화
kwakeuijin
0
140
アクターシステムに頼らずEvent Sourcingする方法について
j5ik2o
4
250
モバイルアプリにおける自動テストの導入戦略
ostk0069
0
110
Итераторы в Go 1.23: зачем они нужны, как использовать, и насколько они быстрые?
lamodatech
0
720
Mermaid x AST x 生成AI = コードとドキュメントの完全同期への道
shibuyamizuho
0
160
Beyond ORM
77web
3
460
Stackless и stackful? Корутины и асинхронность в Go
lamodatech
0
710
ドメインイベント増えすぎ問題
h0r15h0
1
250
RWC 2024 DICOM & ISO/IEC 2022
m_seki
0
210
Featured
See All Featured
Making Projects Easy
brettharned
116
5.9k
The Illustrated Children's Guide to Kubernetes
chrisshort
48
48k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
159
15k
The World Runs on Bad Software
bkeepers
PRO
65
11k
The Language of Interfaces
destraynor
154
24k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
29
2k
Testing 201, or: Great Expectations
jmmastey
40
7.1k
Side Projects
sachag
452
42k
Building Applications with DynamoDB
mza
91
6.1k
Rebuilding a faster, lazier Slack
samanthasiow
79
8.7k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
26
1.9k
Transcript
文字数の話 Unicodeの楽しい話
自己紹介 名前: しゅん Twitter: @shun_shobon / GitHub: @shun-shobo1
学校: 長野高専 電子情報工学科 5 得意: Web Frontend / Web Frontend Ops / A11y なI 研究: ホログラフィ・ヒューマンインタフェースなI 趣味: PCゲーム・自作キーボー 一言: コンピュータと人との関わり方を模索しています
突然ですがクイズです
このJSのコードは何を出力する?
答え
このJSのコードは何を出力する?
答え どう見ても11文字しかないのに出力は12......
今回はこの挙動を理解しよう! というコンセプトです
Unicodeの仕組み
Unicodeの目的 Unicodeは全ての文字にユニークなIDを降ること目的としている. このユニークなIDのことをUnicodeでは Code Point(コードポイント) と呼ぶ. チ → U+30C1 ゃ
→ U+3083 𠮟 → U+20B9F ! → U+FF01
Code Pointの符号化方法 このCode Pointを相手に送る際には,特定の方式によってバイナリにすることで送信される. これにはいくつかの方式がある. UTF-32 … どのCode Pointも4Byteで表r
UTF-16 ... 小さなCode Pointは2Byte,大きいのは4Byt UTF-8 ... なるべく小さなByteになるように1〜4Byteで表す ※ものすごい雑な解説なので詳しくは調べてください
符号化の例① 試しに「チ(U+30C1)」をそれぞれの形式で符号化すると... ※UTF-32・UTF-16はビッグエンディアンでの場合 g UTF-32 … 0x00, 0x00, 0x30, 0xCb
g UTF-16 ... 0x30, 0xCb g UTF-8 ... 0xE3, 0x83, 0x81
符号化の例② 試しに「𠮟(U+20B9F)」をそれぞれの形式で符号化すると... I UTF-32 … 0x00, 0x02, 0x0B, 0x9V I
UTF-16 ... 0xD8, 0x42, 0xDF, 0x9V I UTF-8 ... 0xF0, 0xA0, 0xAE, 0x9F ※UTF-32・UTF-16はビッグエンディアンでの場合
先程の挙動の解説
JavaScriptでの内部表現はUTF-16 JavaScriptでは仕様として文字列データの内部表現をUTF-16と定めている. このようなコードを実行した際,メモリ上に保存されるデータはCode Pointがそのまま保存 されるわけではなく,それをUTF-16で符号化した「0xD8, 0x42, 0xDF, 0x9F」が保存され る. →
つまり1要素2byteの配列で管理される
.lengthの挙動 .lengthはこの2byte配列の要素数を数えているだけ 4byteで表現される「𠮟」があるため,11文字だけど要素数が12なので.lengthは12を返す.
解決策
StringのIteratorを使う Stringがネイティブに実装しているIteratorの処理はCode Point単位で処理される. IteratorベースのSpread演算子を使えばCode Point単位に文字列を分割できる!
よし!これで文字数のカウ ントはバッチリだ!
...本当に?
1Code Point = 1文字 ではない
異体字セレクタという存在 Unicodeには漢字や絵文字のバリエーションを表す異体字セレクタというものがある. 例えば「葛」と「葛󠄀」の違いや,「 」と「 」の違いなど. つまりCode Pointの数と直感的な文字数が一致しない場合がある. これらは基本となる文字にCode Pointを定義して,その文字の後に異体字セレクタという別 のCode
Pointをつけることによって表現される.
結合文字という存在 意味的にはこの2つは等しいため,検索などでこの2つを異なる扱いをしてしまうと,直感に 反する可能性がある. Unicodeには結合文字という,複数のCode Pointを使って1文字を表現することがある. 例えば「が」は,「U+304C」と「U+304B, U+3099」の2通りの表し方がある.
絵文字の合字 合字とは「f」を2回重ねたときに「ff」のように2つがくっついた状態で表示されること. 一部の絵文字はこの合字を利用して複雑な絵文字を表現している場合がある. それぞれの絵文字の間にZero Point Joiner(U+200D)という不可視の文字を入れることに よってその絵文字は合成されているということを表す. ※Zero Point Joinerを使わないで合成される場合もあり(一部の国旗の絵文字など)
こんなのいちいち判別するプ ログラムなんて書けるか!
書けます
書記素クラスタと JSの便利なAPI
自然な区切りを表す書記素クラスタ これまで見てきたように,Code Point単位で見ても異体字セレクタや結合文字,合字などの 概念によってUnicodeにおける「1文字」というのはかなり表現するのが難しい. それに「1文字」という表現は非常に曖昧で,Byte単位なのか,Code Point単位なのか, 「いわゆる直感的な1文字」なのかが分かりづらい. そこで,「いわゆる直感的な1文字」をUnicodeでは書記素クラスタと呼んでいる. 書記素クラスタでの分割アルゴリズムはUnicodeの仕様として標準化されており,この仕様 に従えば誰でも直感的な1文字で文字列を分割することができる(とはいえアルゴリズムは非
常に複雑).
JSの便利なAPI,Intl.Segmenter() 書記素クラスタへの分割をJavaScriptでやる場合,ECMAScript標準APIである Intl.Segmenter()が使える. これを使うと文字列をロケールに応じて書記素,単語,文に分割することができる.
これでようやく正しく 「文字数」を 数えることができる
まとめ
まとめ n Unicodeにおける文字一つ一つに割り当てられるユニークなIDを Code Point と言g n JSでは文字列の内部表現がUTF-16で統一されていh n .lengthはCode
Point単位の処理ではないので,直感と異なる値を返す事があh n Code Point単位で得たい場合はIteratorを使g n Unicodeでは「1Code Point = 直感的な1文字」とは限らな6 n 異体字セレク n 結合文 n 絵文字の合 n et n 直感的な1文字のことをUnicodeでは 書記素クラスタ と呼Æ n JSでは Intl.Segmenter() を使うと簡単に書記素クラスタ単位に分割できる