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

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

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

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 のどちら かに強制的に変換して揃えてしまう。 •

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