技術関係

1文字に見えるけどカウントするとおかしくなる絵文字「👩‍👩‍👧‍👧」の正体を解き明かす

文字カウントするときに厄介な絵文字が存在します。例えば「👩‍👩‍👧‍👧」という絵文字です。1文字に見えますが7文字換算されます。

これをカウントすると文字数がおかしくなりますが、果たしてどういう仕組みになってるのでしょうか?見てみましょう。

スポンサーリンク
スポンサーリンク

おさらい

MySQLのようなデータベースの型定義の何気なく指定しているVARCHAR(X)は何文字入るの?

例えばデータベースの文字コードがUTF-8(utf8mb4)だと括弧内の数字は文字数を表し1文字4バイトとして計算する。

つまりVARCHAR(50)は

  • 50文字
  • 200バイト

を許容し、どちらもオーバーすることは許されない。

環境

mysql: 8.0.31
Collation: utf8mb4_unicode_ci

まずは普通の文字をカウントする

まずは試しでテーブルを作ってみる。

create table user (
  user_message VARCHAR(50) not null
);

NG: カタカナ51文字(3×51 = 153バイト)

カタカナを挿入してみよう。日本語文字は3バイト。

カタカナ51文字(3×51 = 153バイト)を挿入するinsert文を用意して実行してみる。

insert into user(
user_message
)
values(
'カナヘイカナヘイカナヘイカナヘイカナヘイカナヘイカナヘイカナヘイカナヘイカナヘイカナヘイカナヘイカナヘ'
);

結果

Data too long for column 'user_message' at row 1

バイト数は余裕があるが文字数がオーバーしてるので当然エラーが出る。

絵文字をカウントする

NG: 絵文字「🥺」51文字(4×51 = 201バイト)

絵文字「🥺」を挿入してみよう。

1文字4バイトの絵文字を51文字(204バイト)のINSERT文を用意してみた。

insert into user(
user_message
)
values(
'🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺'
);

Data too long for column 'user_message' at row 1

同様にエラーが出る。バイト数も文字数もオーバーしているので当然。

NG: 1文字に見える絵文字8個(56文字 200バイト)

では1文字に見える絵文字「👩‍👩‍👧‍👧」はどうなるか。

insert into user(
user_message
)
values(
'👩‍👩‍👧‍👧👩‍👩‍👧‍👧👩‍👩‍👧‍👧👩‍👩‍👧‍👧👩‍👩‍👧‍👧👩‍👩‍👧‍👧👩‍👩‍👧‍👧👩‍👩‍👧‍👧'
);

Data too long for column 'user_message' at row 1

これでエラーが出る。8個に見えるがUTF-8だと「👩‍👩‍👧‍👧」が1個で7文字換算で56文字なので文字数オーバーしている。

文字数と聞くと惑わされてしまいそうだ。この絵文字はなんで7文字として計算されるのか?

1文字に見える絵文字「👩‍👩‍👧‍👧」 の文字数をカウントする

カウントがおかしくなる絵文字

今回のデータベースはutf8mb4なので文字数はUTF-8で数えることになる。

人間が識別する単位の数え方ではないことに注意。

まずはじめに「👩‍👩‍👧‍👧」は「woman + woman + girl + girl」が結合した絵文字である。

つまり「👩 + 👩 + 👧 + 👧」ということ。

 

なるほど、じゃあ絵文字4個だから4文字?・・・・とはならない。

ゼロ幅接合子 での結合されている絵文字の正体

最初に説明した「結合した絵文字」であることを考える。

どういうことか、というと結合するのも「文字」で行っていて「ゼロ幅接合子」というもので行っている。

これはUTF-8(16進数)で表現すると「e2808d」になる。

つまり、「👩‍👩‍👧‍👧」という絵文字は

「👩 e2808d 👩 e2808d 👧 e2808d 👧」

という感じで絵文字の結合がおこなれている。

絵文字とゼロ幅接合子の数をカウントすると7個となる。だから7文字になる。

絵文字をUTF-8で分解してみる

最後に絵文字もUTF-8(16進数)で分解してみよう

  • 👩 -> f09f91a9
  • e2808d
  • 👩 -> f09f91a9
  • e2808d
  • 👧 -> f09f91a7
  • e2808d
  • 👧 -> f09f91a7

UTF-8で文字数をカウントするということは上記のような形で区切って数えることになる。

全部まとめると「f09f91a9e2808f09f91a9e2808df09f91a7e2808df09f91a7」こうなる。

 

なんでこの英数字の羅列と絵文字が対応しているの?というとUnicodeコードポイントで定義されているからである。

絵文字「👩‍👩‍👧‍👧」をUnicodeコードポイントで表してみると?

「👩‍👩‍👧‍👧」はUnicodeコードポイントで表すと

  1. U+1F469
  2. U+200D
  3. U+1F469
  4. U+200D
  5. U+1F467
  6. U+200D
  7. U+1F467

の7個の集まりである。

絵文字「👩‍👩‍👧‍👧」をUnicodeエスケースプシーケンスで表してみると?

これを扱いやすくするためにUnicodeエスケースプシーケンスにすると

  1. \ud83d\udc69
  2. \u200d
  3. \ud83d\udc69
  4. \u200d
  5. \ud83d\udc67
  6. \u200d
  7. \ud83d\udc67

こうなったりする

絵文字「👩‍👩‍👧‍👧」をUTF-8(16進数)で表してみると?

これは上記で書いたのと同じだがUTF-8(16進数)

  1. f09f91a9
  2. e2808d
  3. f09f91a9
  4. e2808d
  5. f09f91a7
  6. e2808d
  7. f09f91a7

UTF-8は「Unicode Transformation Format – 8-bit」の略であるようにUnicodeを符号化したものである。

つまりそれぞれ使いやすいように変換してるだけ。

ここらへんは他の記事でも書いたので興味があればどうぞ

絵文字を使うと文字数カウントがおかしくなる問題に対応してみる
入力フォームを作る時にこいつの文字数の扱いに苦しみませんか?そう「絵文字」です。 絵文字の文字数をどうやって「正確に」数えてアプリケーションを作るか、という問題...

まとめ

人間が見てる1文字はシステムの世界では1文字でないかもしれない。

「UTF-8で文字数を数える」というのはUnicodeのコードポイント単位で数えること。

コメント

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