Upgrade to Pro — share decks privately, control downloads, hide ads and more …

エンジニアが知っておくべき文字コード問題のあれこれ

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

 エンジニアが知っておくべき文字コード問題のあれこれ

Avatar for Yasuyuki Higa

Yasuyuki Higa

May 25, 2024
Tweet

More Decks by Yasuyuki Higa

Other Decks in Programming

Transcript

  1. 自己紹介 名前: 比嘉 康至(ひが やすゆき) X(旧Twitter): @yh65743 勤務先: 株式会社ことば研究所 居住地:

    奈良(フルリモートで勤務しています) ❏ PHP ❏ React ❏ TypeScript 普段はこのあたりを使ってWebアプリケーション開発/保守 やってます
  2. 文字コードってよくわかんなくないですか? • ASCII や Unicode の概念は何となくわかるけど。。。 • 「あ」(U+3042) を UTF-8

    のコード値にすると e3 81 82 ◦ -> なんで? Unicode のコードポイントがそのままコード値になるんじゃないの ? • 「Windows ではバックスラッシュが円記号になります」 -> あれなんでなん? • MySQL の Character set を utf8 から utf8mb4 にしないと寿司がビールになります ◦ -> ??????? • こういうのをちゃんと理解したい
  3. ASCII • American Standard Code for Information Interchange • 1960年代制定の規格

    • 7ビット = 2^7 = 128 種類の文字を表現 • 0x00 から 0x1F までの位置は制御文字 • 英語を表現するだけならこれで十分 上位3ビット 下 位 4 ビ ッ ト 制御文字
  4. ISO/IEC 646 • ASCII では英語圏以外困ることになるので ASCII をベースに した国際規格が作られた。それが ISO/IEC 646

    • ★の部分は各国の都合で変えて良い • # と £(ポンド)、¤(国際通貨記号)と $ はどちらかを選択する 必要がある 上位3ビット 下 位 4 ビ ッ ト 制御文字
  5. JIS X 0201 • ISO/IEC 646 の日本版規格 • ASCII とほぼ同じコード表だが2箇所だけ違う

    ◦ 0x5C が ASCII では \ (バックスラッシュ) JIS X 0201 では ¥(円記号) ◦ 0x7E が ASCII では ~(チルダ) JIS X 0201 では ¯(オーバーライン) → そもそもの円記号問題の始まり。 とはいえこれはあくまで規格に則った動作なのだが。。。 • C言語のエスケープシーケンスで \ が使われるように • さらに MS-DOS がパス区切り文字として \ を使ってしまう • なんで国によって変わるような文字を使っちゃうの。。。
  6. 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
  7. 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 を併用するための文字符号化方式が生まれる
  8. 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 で同じコードで違う記号に なっている \ と ¥ が文字化けすることに
  9. 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 に変換したとき勝手にエスケープシーケンスに なってしまう。
  10. 円記号問題の対処法 • 「全角」の円記号(JIS X 0201 ではなくJIS X 0208 の円記号)を使うようにする ◦

    Unicode から Shift_JIS に変換したときに勝手にエスケープシーケンスとして扱われることを避けら れる ◦ PHP では 8.1.8 から mb_convert_encoding で UTF-8 の円記号を Shift_JIS に変換すると全角円 記号(818F)に変換されるようになったようです • とはいえ Shift_JIS の半角円記号がどっちの意味で使われてるか機械的に判別す ることは困難なので、どう扱うかは事前に決めておく必要がある。
  11. 寿司ビール問題 • MySQL で CHARACTER SET が utf8 になっていると 4バイト文字が扱えない

    ◦ utf8mb4 を指定することで 4バイト文字が格納できるようになる • また、Collation(照合順序)が utf8mb4_general_ci だと絵文字の区別ができず、 where句で値が 🍣 のレコードを指定したのに 🍺 のレコードもヒットしてしまった、 みたいな現象が起こる。これが寿司ビール問題 • そもそも UTF-8 と Unicode の関係って何? 何で3バイトの文字と4バイトの文字が あるの? (1バイト文字、2バイト文字もあります)みたいなところがわからないとこの 問題の理解がふわっとしてしまう
  12. Unicode の歴史 • 当初、全世界の文字を収めた文字コードの規格として ISO/IEC 10646 が考案され ていた。 • 当初

    ISO/IEC 10646 は4バイトで1文字を表す予定だった。 • ところが同時期にコンピューター関連企業が同様の目的を持った規格を開発し始 めた。この規格が Unicode • 同じ目的の国際文字コードが並立するのは好ましくないので2つの規格を統合する ことに。 • ところが Unicode は2バイトコードだった。そこで4バイトの下位2バイトだけを使用し 上位2バイトは0000で埋めるだけに。 ◦ -> つまり当初 Unicode は実質2バイトコードだった
  13. Unicode の歴史 • 事実上2バイトコードとして制定された Unicode だったが世界中の文字を収録する には2バイトでは足りないことが明らかに。 • この始まりの2バイトコードの範囲を基本多言語面(Basic Multilingual

    Plan 略して BMP)と呼び、ここには普段よく使われている文字が収録されている • もともと2バイト=16ビット固定長の文字コードだった Unicode で BMP 以外の範囲 を扱う方法として UTF-16 という文字符号化方式が考案された
  14. 符号化文字集合と文字符号化方式 符号化文字集合: 文字と数値の対応表のこと 文字符号化方式: 符号化文字集合によって割り出された文字に対応する数値をコン ピュータシステムが利用できるバイト列に変換する方式のこと ASCII/JIS X 0201, JIS

    X 0208, Unicode : 符号化文字集合 EUC-JP, Shift_JIS, UTF-16, UTF-8 : 文字符号化方式 複数の符号化文字集合を組み合わせたする場合は単に文字のコード値を並べるだけで は無理 -> 文字符号化方式が必要、ということ
  15. 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
  16. 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バイト文字に関してはそうではな いということ
  17. 波ダッシュ問題 • JIS X 0208 の 〜(波ダッシュ)が Unicode に変換された際に U+301C(WAVE

    DASH)に なったり U+FF5E (FULLWIDTH TILDE)になったりする問題のこと。
  18. 波ダッシュ問題 • ある Shift_JIS のシステム上で作られた波ダッシュを含むデータを Unicode に変換 するとする。 • 変換したデータを別の

    Shift_JIS 実装で動いているシステムで使うために再度文字 コードを変換したとき、最初のシステムが波ダッシュを U+301 に紐づける実装で、 もう一つのシステムが波ダッシュを U+FF5E に紐づける実装だと再変換で文字化 けする可能性がある。
  19. 波ダッシュ問題の原因 • JIS X 0208 の 「〜」(1区33点)と Unicode の「〜」(U+301C)は共に “WAVE

    DASH” という文字名が与えられており Unicode への変換において妥当なのは U+301C への変換だと思われる • しかし Unicode は以前 WAVE DASH の例示字形として右肩上がりの波線を提示し ており、そのせいで誤解を生むことになってしまった。 普通の波ダッシュ Unicode の例示字形
  20. 波ダッシュ問題の対処法 • 変換方法を揃えるか、Unicode に変換後 文字自体をU+301C か U+FF5E のどちら かに強制的に変換して揃えてしまう。 •

    波ダッシュ以外にも、ǁ, −, ¢, £, ¬ のような記号で同様の問題が起こり得るので注 意が必要