Slide 1

Slide 1 text

エンジニアが知っておくべき文字コード 問題のあれこれ Laravel 勉強会 in 奈良 Yasuyuki Higa @yh65743

Slide 2

Slide 2 text

自己紹介 名前: 比嘉 康至(ひが やすゆき) X(旧Twitter): @yh65743 勤務先: 株式会社ことば研究所 居住地: 奈良(フルリモートで勤務しています) ❏ PHP ❏ React ❏ TypeScript 普段はこのあたりを使ってWebアプリケーション開発/保守 やってます

Slide 3

Slide 3 text

文字コードってよくわかんなくないですか? ● ASCII や Unicode の概念は何となくわかるけど。。。 ● 「あ」(U+3042) を UTF-8 のコード値にすると e3 81 82 ○ -> なんで? Unicode のコードポイントがそのままコード値になるんじゃないの ? ● 「Windows ではバックスラッシュが円記号になります」 -> あれなんでなん? ● MySQL の Character set を utf8 から utf8mb4 にしないと寿司がビールになります ○ -> ??????? ● こういうのをちゃんと理解したい

Slide 4

Slide 4 text

矢野 啓介 『プログラマのための文字コード技術入門 』 技術評論社 (2018/12/22) 本日の発表はだいたいこの本からの引用となっており ます

Slide 5

Slide 5 text

円記号問題 ● 「Windows ではバックスラッシュ(\) が円記号(¥) になる」という有名な話 ● Shift-JIS エンコーディングのシステム上で入力された円記号が他のエンコーディン グのシステム上ではバックスラッシュに見える(逆も然り)という現象のこと ● 記号の表示が変わるだけの問題に思えるが……?

Slide 6

Slide 6 text

そもそも文字コードとは ● コンピューターはデータを0と1(ビット)の羅列で扱う -> そのままでは「文字」を扱え ない ● 文字を扱うにはビット列と文字の対応表が必要 A = 001 B = 010 C = 011 … ● この文字とビット列の対応表を文字コードという

Slide 7

Slide 7 text

ASCII ● American Standard Code for Information Interchange ● 1960年代制定の規格 ● 7ビット = 2^7 = 128 種類の文字を表現 ● 0x00 から 0x1F までの位置は制御文字 ● 英語を表現するだけならこれで十分 上位3ビット 下 位 4 ビ ッ ト 制御文字

Slide 8

Slide 8 text

ISO/IEC 646 ● ASCII では英語圏以外困ることになるので ASCII をベースに した国際規格が作られた。それが ISO/IEC 646 ● ★の部分は各国の都合で変えて良い ● # と £(ポンド)、¤(国際通貨記号)と $ はどちらかを選択する 必要がある 上位3ビット 下 位 4 ビ ッ ト 制御文字

Slide 9

Slide 9 text

JIS X 0201 ● ISO/IEC 646 の日本版規格 ● ASCII とほぼ同じコード表だが2箇所だけ違う ○ 0x5C が ASCII では \ (バックスラッシュ) JIS X 0201 では ¥(円記号) ○ 0x7E が ASCII では ~(チルダ) JIS X 0201 では ¯(オーバーライン) → そもそもの円記号問題の始まり。 とはいえこれはあくまで規格に則った動作なのだが。。。 ● C言語のエスケープシーケンスで \ が使われるように ● さらに MS-DOS がパス区切り文字として \ を使ってしまう ● なんで国によって変わるような文字を使っちゃうの。。。

Slide 10

Slide 10 text

ISO/IEC 2022 ● ISO/IEC 646 の各国版だけだと複数の言語を混在させられな い ● そこで複数の文字コードを同時に使える規格として ISO/IEC 2022 が考案された ● 第8ビットを導入し、ISO/IEC 646 準拠の文字コードを混在で きる(第8ビットが0なら ASCII, 1なら JIS X 0201 みたいにでき る) ● エスケープ(0x1B) を使うことで文字コードの切り替えもできる ● さらに漢字など 7ビットで収まりきらない文字のために複数バ イトで文字を表現できるようになった 第8ビットが0 第8ビットが1

Slide 11

Slide 11 text

JIS X 0208 ● ISO/IEC 2022 に準拠した日本の2バイトコード規格 ● いわゆる「JIS 第1・第2水準漢字」を定めているのがこれ ● ASCII の制御文字以外の部分94箇所を1バイト目、2バイト目に使用し、 94*94 = 8,836 文字 収録可能 ● ASCII と同じアルファベットや数字を含んでいるが、2バイトコードであり ASCII や JIS X 0201 と互換性はない -> Shift_JIS や EUC-JP といった 1バイトコードと と JIS X 0208 を併用するための文字符号化方式が生まれる

Slide 12

Slide 12 text

EUC-JP と Shift_JIS ● どちらも1バイトコード と JIS X 0208 を併用するための文字符号化方式 ● EUC-JP は UNIX系OSで、Shift_JIS は MS-DOS のWindows で使われた ● EUC-JP は 1バイトコードとして ASCII を、Shift_JIS は JIS X 0201 を使用 ○ どちらも1バイトコード部分は互換性があるので ASCII/JIS X 0201 の文字コー ドはそのまま使える -> EUC-JP ⇔ Shift_JIS 間で文字コード変換したら1バイト 部分は変換されずにそのままになってる ○ 大部分はそれで問題ないけど ASCII と JIS X 0201 で同じコードで違う記号に なっている \ と ¥ が文字化けすることに

Slide 13

Slide 13 text

Unicode で円記号が問題になる理由 ● 世界中の文字を1つの文字コードで表現するために考案されたのが Unicode ● Unicode は先頭の128符号位置が ASCII と一致するようになっており、次の 128符 号位置は ISO/IEC 8859-1 というヨーロッパの文字コードと一致するように作られて いる。そして ISO/IEC 8859-1 には バックスラッシュとは別に円記号の符号位置が 定められている(U+00A5)。 ● Shift-JIS -> Unicode に変換したときに 0x5C は U+00A5 の円記号に変換されるこ とに。バックスラッシュとしての特別な意味が失われてしまう。 ● 逆に Unicode の円記号を Shift-JIS に変換したとき勝手にエスケープシーケンスに なってしまう。

Slide 14

Slide 14 text

円記号問題の対処法 ● 「全角」の円記号(JIS X 0201 ではなくJIS X 0208 の円記号)を使うようにする ○ Unicode から Shift_JIS に変換したときに勝手にエスケープシーケンスとして扱われることを避けら れる ○ PHP では 8.1.8 から mb_convert_encoding で UTF-8 の円記号を Shift_JIS に変換すると全角円 記号(818F)に変換されるようになったようです ● とはいえ Shift_JIS の半角円記号がどっちの意味で使われてるか機械的に判別す ることは困難なので、どう扱うかは事前に決めておく必要がある。

Slide 15

Slide 15 text

寿司ビール問題 ● MySQL で CHARACTER SET が utf8 になっていると 4バイト文字が扱えない ○ utf8mb4 を指定することで 4バイト文字が格納できるようになる ● また、Collation(照合順序)が utf8mb4_general_ci だと絵文字の区別ができず、 where句で値が 🍣 のレコードを指定したのに 🍺 のレコードもヒットしてしまった、 みたいな現象が起こる。これが寿司ビール問題 ● そもそも UTF-8 と Unicode の関係って何? 何で3バイトの文字と4バイトの文字が あるの? (1バイト文字、2バイト文字もあります)みたいなところがわからないとこの 問題の理解がふわっとしてしまう

Slide 16

Slide 16 text

Unicode の歴史 ● 当初、全世界の文字を収めた文字コードの規格として ISO/IEC 10646 が考案され ていた。 ● 当初 ISO/IEC 10646 は4バイトで1文字を表す予定だった。 ● ところが同時期にコンピューター関連企業が同様の目的を持った規格を開発し始 めた。この規格が Unicode ● 同じ目的の国際文字コードが並立するのは好ましくないので2つの規格を統合する ことに。 ● ところが Unicode は2バイトコードだった。そこで4バイトの下位2バイトだけを使用し 上位2バイトは0000で埋めるだけに。 ○ -> つまり当初 Unicode は実質2バイトコードだった

Slide 17

Slide 17 text

Unicode の歴史 ● 事実上2バイトコードとして制定された Unicode だったが世界中の文字を収録する には2バイトでは足りないことが明らかに。 ● この始まりの2バイトコードの範囲を基本多言語面(Basic Multilingual Plan 略して BMP)と呼び、ここには普段よく使われている文字が収録されている ● もともと2バイト=16ビット固定長の文字コードだった Unicode で BMP 以外の範囲 を扱う方法として UTF-16 という文字符号化方式が考案された

Slide 18

Slide 18 text

符号化文字集合と文字符号化方式 符号化文字集合: 文字と数値の対応表のこと 文字符号化方式: 符号化文字集合によって割り出された文字に対応する数値をコン ピュータシステムが利用できるバイト列に変換する方式のこと ASCII/JIS X 0201, JIS X 0208, Unicode : 符号化文字集合 EUC-JP, Shift_JIS, UTF-16, UTF-8 : 文字符号化方式 複数の符号化文字集合を組み合わせたする場合は単に文字のコード値を並べるだけで は無理 -> 文字符号化方式が必要、ということ

Slide 19

Slide 19 text

UTF-16 ● 本来の Unicode が意図していた符号化方式 ● BMP の文字は2バイト(16ビット)で符号をそのまま並べ、BMP以外の文字に関し てはサロゲートペアと呼ばれる特殊な計算で求めたビット列を4バイト並べて表現す る ● 全て4バイト固定長で表現する UTF-32 という符号化方式もある

Slide 20

Slide 20 text

UTF-8 ● 1文字を1バイトから4バイトまでの可変長で符号化する方式。 ● 以下の表に基づいて符号位置のビット列を x にあてはめる Unicodeの符号位置 UTF-8のバイト列 00000000 ~ 0000007F 0xxxxxxx 00000000 ~ 000007FF 110xxxxx 10xxxxxx 00000800 ~ 0000FFFF 1110xxxx 10xxxxxx 10xxxxxx 00010000 ~ 0010FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

Slide 21

Slide 21 text

UTF-8 ● 試しに「あ」を符号化してみる ○ Unicode で 「あ」は U+3042 -> 二進数で 00110000 01000010 ○ 3042 は表で3行目にあたるので 1110xxxx 10xxxxxx 10xxxxxx に上記ビットをあてはめる ○ すると 11100011 10000001 10000010 -> 16進数にすると E3 81 82 これが 「あ」の UTF-8によって エンコードされた値 ● 表の1行目は ASCII まんまなので互換性がある ● 表の3行目までは BMP の範囲内 -> 「3バイトのUTF-8」はよく使われる基礎的な文 字なので多くのソフトウェアで実装されているが、4バイト文字に関してはそうではな いということ

Slide 22

Slide 22 text

波ダッシュ問題 ● JIS X 0208 の 〜(波ダッシュ)が Unicode に変換された際に U+301C(WAVE DASH)に なったり U+FF5E (FULLWIDTH TILDE)になったりする問題のこと。

Slide 23

Slide 23 text

波ダッシュ問題 ● ある Shift_JIS のシステム上で作られた波ダッシュを含むデータを Unicode に変換 するとする。 ● 変換したデータを別の Shift_JIS 実装で動いているシステムで使うために再度文字 コードを変換したとき、最初のシステムが波ダッシュを U+301 に紐づける実装で、 もう一つのシステムが波ダッシュを U+FF5E に紐づける実装だと再変換で文字化 けする可能性がある。

Slide 24

Slide 24 text

波ダッシュ問題の原因 ● JIS X 0208 の 「〜」(1区33点)と Unicode の「〜」(U+301C)は共に “WAVE DASH” という文字名が与えられており Unicode への変換において妥当なのは U+301C への変換だと思われる ● しかし Unicode は以前 WAVE DASH の例示字形として右肩上がりの波線を提示し ており、そのせいで誤解を生むことになってしまった。 普通の波ダッシュ Unicode の例示字形

Slide 25

Slide 25 text

波ダッシュ問題の対処法 ● 変換方法を揃えるか、Unicode に変換後 文字自体をU+301C か U+FF5E のどちら かに強制的に変換して揃えてしまう。 ● 波ダッシュ以外にも、ǁ, −, ¢, £, ¬ のような記号で同様の問題が起こり得るので注 意が必要