へっぽこびんぼう野郎のnewbie日記

けろけーろ(´・ω・`)! #vZkt8fc6J

ㇹ゚ン゚'ㇳ̃ヴ゙ニ゙コ゚ヮヰ文̂字̠コ゚−ト゚ノ゙ㇵナ゚ㇱ(現在に至るまでの文字コードの軌跡と簡単な使い方について)

はじめに

社内の勉強会で発表した文字コードの話の焼き直しです。ところどころ適当なので話半分に読んでもらえると助かります。

これ以上闇の深さを知りたくないと思って、深淵に辿り着く前に文字コードの勉強を打ち切っています。文字コードの専門家でもないので雑です。 調査が甘いので間違ってることも多々あるかもしれません。その場合はコメントください。修正します。

自信のないところは「らしい」とか「ようです」などのように伝聞調で書いています。あらかじめご了承ください。

また、前提知識として2進数と16進数の基礎的な知識を要求しています。

16進数の表現には特に断りがないかぎり 0xFFFF のような表現を使います。

2進数を使う場合には必ず断り書きを入れます。それ以外は10進数です。

本筋には関係のない、重要ではない情報は脚注にあります。気になったところだけご覧ください。

アジェンダ

  1. 今回の話に関係ある用語
  2. 現代の文字コード事情(Unicodeとその他)
  3. ASCIIから現代まで
  4. UnicodeUTF-16
  5. UnicodeUTF-8
  6. まとめ

1. 今回の話に関係ある用語

文字コードの規格に関する用語として次のものがあるので、軽く説明します。

ISO

International Organization for Standardization (国際標準化機構)

道路標識とかマネジメント規格とか長さの単位とか、国際標準のものならなんでもとりあえず決めている組織

規格が決まっていないと、連携するときにいちいちサイズなりやり方なりを合わせたりしないといけないので、国際的に決めて便利にしておこう的組織。

IEC

International Electrotechnical Commission (国際電気標準会議)

電気関係の標準を決める組織。水車とか同軸ケーブルとかの規格を決めてる。

文字コードの国際規格では、ISOとIECでどちらが標準を決めるのか縄張り争い的な感じになったらしく、結果的にISOとIECが両方絡むことになった(イイネ!!)

JIS

Japanese Industrial Standards (日本工業規格)

JSA(日本規格協会)が決めている日本の標準規格

トイレットペーパーのサイズとか畳のサイズとか決めてる

文字コードの件でも色々絡む

2. 現代の文字コード事情(Unicodeとその他)

ひとまず、現代の文字コード事情がどうなっているのかという、ぼくが勉強する前にはじめに思った疑問と闇をざっくりと祓っていきます。

そもそも文字コードとは?

一般的に使われている「文字コード」という言葉は、

文字やバイト列を入れると、それに対応したバイト列や文字が返ってくるやつ!

という概念を示したものだと思います。*1

なぜなら、さまざまなアプリケーションで「好きな文字コードを選んでね♡」というふうに書いてあって、

その選択肢として「Unicode(UTF-16)UTF-8Shift JIS」などが提示されるからです。

そのため「Unicode文字コード」「UTF-8文字コード」「UnicodeUTF-16は同じ」というような理解が世間的に了解されていると思います。

この概念はすごく便利なので、あまり詳しくなくても「文字を入れるとなんかバイト列になる」という知識で、文字をコンピュータで扱えるので重宝されています。

しかし「文字コード」と一言で言っても、一概に全部が全部同じような「文字コード」というわけではありません。

それぞれの文字コードには、歴史的な経緯があり、人間的な努力があり、ドラマがあり、互換性のためのテクニックが存在しているため、外面は同じように見えて、中身の実装が違うからです。

よく聞く文字コードたち

現代でもよく使う、かなり知られている文字コードですが、やっぱり「UnicodeUTF-16の関係は?」とか「結局Unicodeってなんだよ?」「Shift JISのShiftってなんだよ?」と聞かれると、ぼくはパニックになっていました。

まずはとりあえず、Unicodeとその他の文字コードの関係を説明します。

Unicodeとは?

Unicodeとは、Unicodeコンソーシアムで作られている文字コードです。コンソーシアムというのは共同事業体という意味です。日本の企業も参加しています。

Unicode Consortium

Unicodeという言葉は、色んな文脈によって使われ方があるのですが、文字コードとしてのUnicodeではなくて、そもそものUnicodeを説明したいと思います。

その場合、Unicodeとはざっくり言うと、文字と、文字に対する番号(16進数)の表です。

たとえば、下のように「あ」に対して「3042(16進数)」番が振られています。

f:id:haruharu1:20180428144400p:plain

ここですべてを見ることができます→https://unicode-table.com/en/#hiragana

Unicodeのこの表には、ラテン文字(英語で使うアルファベット)やキリル文字、ひらがなカタカナはもちろん、ハングルもあり、ヒエログリフ線文字B、そして絵文字など、かなりの文字が含まれています。ただ当然、全世界のすべての文字が含まれているわけではないことにご注意ください。

Unicodeでの文字に対する位置を表すときは、16進数の前に U+ をつけて表すことが多いです。

U+3042U+1F439🐹を表します。

このUnicodeでの文字に対する位置【例: U+3042】 を、CodePoint(コードの位置という意味)と呼びます。

たとえば、のCodePointはU+3042です。

Unicodeの実際の使われ方の例

プログラミング言語Pythonの、3.x系では、文字列型str(文字型charはありません)を、Unicodeで表現しているので、次のようなことができます。

$ python
Python 3.6.5 (default, Mar 30 2018, 06:41:53)
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.39.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> chr(0x3042)
'あ'
>>> chr(0x1F439)
'🐹'
>>> '\U00003042'
'あ'
>>> '\U0001F439'
'🐹'

その他の文字コード

その他の文字コードは、Unicodeの子分のような存在です。他の文字コードの値は、UnicodeのCodePointに変換されます。その逆もします。

たとえばUTF-8の値、E3 81 82(16進数)が与えられたときに、U+3042を吐き出します。

逆も同じで、UnicodeのCodePointU+3042が与えられたとき、これをE3 81 82(16進数)に変換します。

そんなかんじで、 E3 81 82 を変換したら U+3042 になったから、Unicodeの表でいうと だなと変換されて、ディスプレイに表示されます。

そのほかの文字コードもそうです。(以下の数字は16進数です)

CP932 は、Microsoft実装のShift JISのことです。 *2

[UTF-8]
E3 81 82 -> U+3042

U+3042 -> E3 81 82

U+1F439 -> F0 9F 90 B9
[UTF-16]
30 42 -> U+3042

U+3042 -> 30 42

U+1F439 -> D8 3D DC 39
[CP932]
82 A0 -> U+3042

U+3042 -> 82 A0

U+1F439 -> 無理

例として、python3.xでUTF-8エンコードすると、このようになります。*3

この は、Unicode で、つまりpython内部では U+3042として取り扱われています。

>>> 'あ'.encode('utf-8')
b'\xe3\x81\x82'
>>> '🐹'.encode('utf-8')
b'\xf0\x9f\x90\xb9'

クイズ

もう1つ例として、HTMLを使ってみます。

以下は、UTF-8 で書かれた htmlファイル です。 shift_jis で表示しろと書いてありますが、この場合、ブラウザではどのように表示されるでしょうか?

f:id:haruharu1:20180428152605p:plain

こたえ

f:id:haruharu1:20180428152707p:plain

まずテキストファイルは、UTF-8エンコードされたものになっています。

それをブラウザがShift JISだと解釈してデコードします。

最後に、&#x〜〜〜(Numeric Character Reference【NCR】)などをデータとして受け取ったブラウザは、これをUnicode表と対応させて、文字を表示します。

python3.xで実際にやってみると↓のようになります。

>>> '🐹'.encode('utf-8').decode('cp932')
'🐹'
>>> '🐹'.encode('utf-8').decode('cp932')
'\ue05e聖'

クロスサイトスクリプティングを防ぐために、<script></script>&gt;を使ったものに変える必要があるのは、このためです。 *4

Unicodeとその他の文字コードとの関係

だいじなのは、Unicodeとその他の文字コードの関係で、とりあえず「なんでもかんでもUnicodeのCodePointにするのか」という感じで了解してもらいたいです。

でも、もともと文字コードが誕生したときから世界がこういう思想で動いていたわけではなくて、さまざまな人達がその時代時代で最善の選択肢を取ってきたことによって、今の現状があるわけなので、その経緯をざくっと説明したいと思います。

3. ASCIIから現代まで

有名な文字コード関係の規格たち

これらをすべて同じ「文字コード」とするのは無理があるのですが、ひとまずこれらについて説明していきます。

かなり大まかなものなので、「厳密に言うと全然違う」みたいな話もありますが、ご了承ください。

日本で有名なものだけ抜粋したので、もちろん他にもいっぱいあります。

ASCII

  • American Standard Code for Information Interchange(情報交換のためのアメリカ標準符号という意味)
  • 1960年〜
  • 7bitでアルファベットと少しの記号を送るための規格

超絶有名な規格で、 2進数で100 0001(0x41)と送ると、「 A をコンピュータ上で表示する」というような極めてシンプルな規格です。

f:id:haruharu1:20180428160550p:plain

ISO/IEC 646

  • ASCIIっぽいものを、アメリカ以外でも当然自分たちの言語として使いたいという欲求が当然出てくるので、各国語版として存在させるために作られた規格
  • ASCIIの一部の文字を、各国自由に使っていいよと定めた
  • ASCIIと、各国語版を切り替えて使うことができるように、7bitのときは制御文字を用い、8bitのときは最上位ビットが0だったらASCII、最上位ビットが1だったら各国語版を使うのような使い方をしていた
  • 1962年〜

↓ここの緑色になっている部分を自由に各国で決めることができました。

f:id:haruharu1:20180428161021p:plain

JIS X 0201

  • ISO/IEC 646の日本語版。7bitか8bitで使う
  • 1969年〜
  • コウイウカンジデニホンゴヲヒョウジシテイタ
  • ASCIIと違うのは、\~ (今もなおWindowsで残る問題)

ひらがなではなく、カタカナを用いたのは当時のグラフィック能力によるものだと思われます(ひらがなだと文字がドットで潰れる)

f:id:haruharu1:20180428161337p:plain

JIS X 0201 らへんの日本の風潮

  • コンピュータでまともに表示できない言語など欠陥品 (ナンデカタカナデシカツカエナイノ!? アメリカでは何の問題もない)
  • 新聞もコンピュータで印刷できない(活字拾い大活躍。まだまだ全然活版印刷の時代)
  • 日本語は漢字を廃止してローマ字化すべき (アルファベットなら文字が少なくて済む)
  • 国が出している漢字表も、「常用漢字表」ではなく「当用漢字表」(さしあたってとりあえず使う漢字)だった
  • 戦後なので、とりあえず日本語disしておけばいい状態

こういう時代だったので、ひらがなと漢字が表示できるコンピュータを作るか、日本語やめるかという状況だったみたいです。

↓40年前のこの時代に使われていた和文タイプライターがこんな感じで、下に見える1つ1つのハンコっぽいやつが漢字です。

f:id:haruharu1:20180428162336p:plain

アメリカではタイプライターで文字探しなんかしなくていいし、コンピュータをガンガン使えるのに、日本では文字探しをしなければいけないしコンピュータ使えないという時代だったようです。

これで仕事しなければいけないとか言われたら、そりゃ文豪も怒りが有頂天になって日本語廃止したくなりますわっていう納得感を感じます。

JIS X 0208

  • 1文字に2バイト使ってもコンピュータが問題なく動く、メモリが潤沢な時代が到来
  • 94×94の8836文字使える(94という数字はASCIIから制御文字を取り除いたときの数)
  • ある程度まともに日本語が使えるようになって日本語廃止論者がいなくなる
  • ここで追加されたのが第1水準、第2水準漢字
  • この時代では、互換性のために必要に応じてJIS X 0201JIS X 0208を切り替えて使っていた(重要!!)
  • なので、JIS X 0208単体でも使えるように、JIS X 0208にもカタカナや英数字があった。
  • 1978年〜
  • 78年にできたので、78JISと言われる。

常用漢字表制定

1981年、常用漢字表制定。

日本語廃止論が収束して、漢字使うぜっていう流れになったので「いつも使う漢字たち♡」という意味の、常用漢字表が作成されました。

そして……

JIS X 0208 (1983年改訂)

常用漢字表の改訂に合わせて、既存の文字を変えることになりました。

たとえば飛驒の「驒」が「騨」になり、
これによって83JISを実装したコンピュータでは「飛騨」と表示されるようになりました。

そのため、正式名は「飛驒」なのにもかかわらず、この改訂によって全国的に「飛騨」という漢字で知られるようになります……

f:id:haruharu1:20180428163143p:plain

今後は、この失敗を省みて、既存の文字を変更するということは日本では行われていないようです。

Shift JIS

  • 1982年〜
  • アスキー社やMicrosoftなどが共同でつくった
  • それまでシステムに応じてJIS X 0201JIS X 0208を切り替えて使っていたので、切り替えをせずに、JIS X 0201JIS X 0208を同時に使えるようにしたかった(めっちゃ便利だしMicrosoftが実装したので市場を席巻した)
  • JIS X 0201の未定義部分を拡張していっぱい文字入れれるようにした(文字通り、JISをシフトした)
  • JIS X 0201の英数字やカタカナを「半角英数字・半角カナ」と呼び、JIS X 0208の英数字やカタカナを「全角英数字・全角カナ」と呼ぶことにした

JIS X 0212

  • 1990年〜
  • JIS X 0208だけじゃ漢字足りないし、人名・地名を中心にいっぱい追加した
  • Shift JISはこの規格に準拠してない(だってShiftしたし)
  • ISO-2022-JPとかeuc-jpとかがこの規格

ISO/IEC-8859-1 (a.k.a ISO-8859-1, Latin-1)

  • 1992年〜
  • 8bitでかなりの数が表現できる画期的な文字コード
  • 各国で自由に使っていたものをすべて右側(最上位ビットが1の場合)に統合した
  • 現在も欧米では使われる
  • 世界中の色んな国ではこれでほぼ問題ない

f:id:haruharu1:20180428164325p:plain

UnicodeISO/IEC 10646

  • 世界の文字をすべて取り入れた文字コードを作れば、もう世界は混乱しないという方針のもと作られた
  • 16bit(65536文字)で世界中の文字を表現するようにした

もともとは、ISO/IEC 10646という規格と、Unicodeという規格は別々のものでした。

ISO/IEC 10646は、ISOとIECで作られていたものでしたが、Unicodeは前述のUnicodeコンソーシアムで作られていました。

しかし同じような規格が乱立するのは好ましくないとされて、合流することになりました。

なので、ISO/IEC 10646側の用語と、Unicode側の用語があります。

最初の方のUnicode

  • 1991年〜
  • 欧米勢「16bitで、世界の文字を表現しよう」
  • 日本・中国・韓国「16bitだと最大65536通りだぞ。無理に決まってんだろ!2バイトやめろ!俺たちだけで超えるわ!殺す!」
  • 欧米勢「2バイトで十分!漢字は場所取りすぎ!まず同じ漢字はまとめてからクレームは言ってね!!!」→CJK統合漢字(Chinese-Japanese-Korea)
  • アラビア語圏「落ち着けお前ら、俺達はまず文字は、右から左に書きたい!」
  • ベンガル語圏「文字と文字がバラバラだけど、どうやって文字くっつけるんだ。こんなの俺たちの文字じゃねぇ」
  • 韓国「なんでハングルを他の言語の文字と文字の隙間に入れるんだ!移動しろ!」→ハングル大移動
  • モンゴル「うちは基本、文字は縦書きなんだけど!」
  • 誰か「文字は文字だけ入れろ!構造を示す文字ってそんなのいらねえだろ!」
  • 誰か「ルビ文字とか入れたい!絶対役に立つ!(役に立たなかった)」

カオス。当初の崇高な目的は理想論すぎました。

最初ぼくは「16bitで世界の文字を表現とか、明らかに無理だろわろすw」と思っていましたが、国際標準の場に立つとそんな「当たり前のこと」が当たり前ではなくなってしまうことがよくあるらしいということを知りました。

自分の要求を押し通すには、他の国の要求も飲まなければいけないわけです。

たとえば日本勢は「右から左に書くとか仕様が複雑になるだけでしょ」って感覚だったとしても、アラビア勢にとっては死活問題で、それと同じように、日本勢が「漢字いっぱい入れろ」っていうのを押し通すためには、他の国々の言い分を聞き入れて、味方につける必要がありました。めちゃくちゃ大変そう……

JIS X 0213

Unicodeが落ち着きはじめた20世紀後半、日本ではまだまだ戦いが続いていました。まだまだ使えない漢字があったので、新しい規格がつくられました。

それがJIS X 2013です。ただ、だんだんUnicodeが隆盛していきます。

  • 2000年〜
  • JIS X 0208の上位互換。JIS X 0212との互換性は無し
  • あとあとUnicodeに文字が取り入れられた
  • Shift JISでは使えない
  • 第三水準・第四水準漢字はここで追加された

どうでもいいことですが、このあたりで、携帯ベンダが絵文字を発明し、文明がより豊かになり、あたらしい地獄が始まりました。

歴史まとめ

だいたいこんな流れだと思っています。

ASCII → JIS X 0201

JIS X 0208(78JIS) -> JIS X 0208(83JIS)

JIS X 0201 + JIS X 0208 -> Shift JIS -> 色んなベンダによるShift JIS実装

JIS X 0212 -> euc-jp, ISO-2022-JP-1

JIS X 0213

16bitですべてを表現するUnicode -> 世界各国の文字コードを吸収する方針へ(現代のUnicode)

4. UnicodeUTF-16

UTF-16とは

最初、Unicodeは16bitですべての文字を表現しようとしていました。

でもやっぱり、16bitは、U+0000U+FFFF までの65536文字しか表現できない……

そこで U+10000 よりも後の文字を表現するために16bitを2つくっつけて、0xD800 0xDC00のようにし、この場合は U+10000 である!ということにしました。

こういった方式を UTF-16 といいます。

また、0xD800 0xDC00 のような組み合わせを、サロゲートペアと呼びます。

サロゲートは「代理」という意味で、つまりUnicodeの実際のCodePointを表すための代理の値の組がサロゲートペアだということです。

は、U+3042なので、0x3042と表すけど、 🐹は、U+1F439だから、これを表すために、0xD83D 0xDC39 のような表現をするということです。

このサロゲートペアという仕組みによって、UnicodeU+10FFFF までの文字が表現可能になりました。(苦肉の策的な感じですが……)

だから、 Unicode(UTF-16) みたいな書き方をたまに見かけることがあります。この場合は、 単にUTF-16 という意味だと思えばいいと思います。

サロゲートペアとUnicodeの実際のCodePointについての計算式は、以下を参照してください。

JavaScript’s internal character encoding: UCS-2 or UTF-16? · Mathias Bynens

RFC 2781 - UTF-16, an encoding of ISO 10646

ちなみに、本当に16bitだけのUnicodeのことを UCS-2 とかって言っていたみたいです。 *5

JavaScriptでの例

JavaScriptでは、UTF-16をStringとして使っているので、次のような感じになっています。🐹の文字の長さが2になってしまうのもUTF-16だからです。

f:id:haruharu1:20180428171502p:plain

なので、どうしてもUnicodeの文字として文字数をカウントしたい場合は、サロゲートペアなどを考慮して実装する必要があります。

これらサロゲートペアを用いた、U+10000 以上の文字は、俗に 4バイト文字 などと呼ばれますが、あまり本質を表していないので、こう呼称するのはやめたほうがいいと思います。

たとえば、 ト゚ は、サロゲートペアを用いてないですが、UTF-16では4バイトで、UTF-8では6バイトになります。 ト゚゚ は、UTF-16では6バイトですが、UTF-8では9バイトです。

5. UnicodeUTF-8

UTF-8は、ASCII互換の文字コードで、これもUnicodeのCodePointに対応するように作られました。

ASCII互換になっている例として、UTF-16では、たとえば A0x0041 と表現されますが、UTF-8では0x41です。

↓こんな感じです。python3では、バイナリは基本的にasciiで表現され、asciiじゃなければ \xNN で表現されます。 utf-16bebe は、ビッグエンディアンです。これらの説明は面倒なので省略します。

>>> 'a'.encode('utf-16be')
b'\x00a'
>>> 'a'.encode('utf-8')
b'a'

UTF-8では、ASCIIじゃないものはすべてUnicodeの表に帰着するように計算されます。

↓のような感じでUnicodeの数字を表現しようとしています。

f:id:haruharu1:20180428173204p:plain

このへんで僕は文字コードから興味を逸しているので、UTF-8の詳細な実装については他のサイトを御覧いただきたいです。

6. まとめ

  • 現在のUnicodeは最強
  • Shift JISは、使えない文字がたくさんあるので、もう使うべきではない。CSV出力?UTF-16でやってね!!
  • UTF-16UTF-8は怖くない
  • 文字コードの闇は、コンピュータの発展と言語そのものの複雑さと、政治的なゲームに起因するもの
  • 昔の人ががんばってくれたおかげで、文字コードがこれぐらいの混乱で済んでいることに感謝する
  • とりあえずこのあたりがわかると文字コードの勉強が開始できる

リファレンス

ASCII + ISO/IEC 646 + JIS X 0201

ASCII - Wikipedia

2002年12月号 特集 第1章

2002年12月号 特集 第1章

JIS X 0208

小形克宏の「文字の海、ビットの舟」

「飛騨」と「飛驒」? どちらも正解ですが公文書は旧字体(旧漢字)です – 村坂克之 小又接骨院のブログ 飛騨・高山・下呂

Shift JIS

ftp://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP932.TXT

Encodings of Japanese

Code page 932 (Microsoft Windows) - Wikipedia

ISO/IEC 8859-1

ISO 8859-1 Charset

ISO/IEC 8859-1 - Wikipedia

タイプライター・活版印刷

和文タイプライター 日本語タイプライター

活版印刷職人・加藤隆男さんの「活字拾い、組版、印刷から裁断」まで - YouTube

UTF-16

UCS-2とUTF-8

JavaScript’s internal character encoding: UCS-2 or UTF-16? · Mathias Bynens

http://unicode.org/versions/Unicode3.0.0/ch03.pdf

Let’s talk about Javascript string encoding | Kevin Burke

UTF-8

UTF-8からSJISに文字化けすると糸偏の漢字がよく出てくる - Qiita

文字化けパターンサンプル - instant tools

文字コード考え方から理解するUnicodeとUTF-8の違い | ギークを目指して

パソコンは日本語をどう変えたか―日本語処理の技術史 (ブルーバックス)

ユニコード戦記 ─文字符号の国際標準化バトル

いま日本語が危ない―文字コードの誤った国際化

プログラマのための文字コード技術入門 (WEB+DB PRESS plus) (WEB+DB PRESS plusシリーズ)

その他いろいろ

*1:もちろん厳密な定義ではありません

*2:Shift JIS内でも闇があるのですが、現代っ子のぼくは「自分に関係ないし辛そう」と切り捨てて全く調べてません。

*3:UTF-16で試す場合はBOMなどに、また、出力されるバイナリはasciiで表現されることに注意してください。'a'.encode('utf-8')が'a'になるのはこのためです。python2.x系ではバイナリがstr型だったため、こういう風になっているのだと思います(勘)

*4:ちなみにCSSでもできるので、xCSSで修飾することもできます。

*5: UCS-4とかUTF-32とかもあります。