2012年5月14日月曜日

Unicodeで天下統一


Unicodeの話その1。

昔は国や地域毎に文字コードがあるのが当たり前だった。基本となる英語圏のASCIIコードに対して、日本人は独自に日本語の文字付け足し、中国人は独自に中国語の文字を付け足し…。そんな互換性も何もない状況を打破しようと、全世界共通の文字コードを作ろうとして生み出されたのがUnicode。世界共通とは言ってもエスペラント語のような崇高かつ無謀な話ではなく、単純に世界中のあらゆる文字に文字コードを与えるだけの話。…だったのだが、初期の判断ミスでやや面倒なことになっている。

当初は単純に16ビットの文字コード(U+0000~U+FFFF)を割り振ろうと考えていたらしい。2バイトの固定長なら、容量は食うけれど扱いは簡単だ。しかし、65536文字ではとても世界中の文字を網羅できなかったので、後に21ビット(~U+10FFFF)に拡張するはめになった。この歴史的経緯が思いっきり現れているのがUTF-16。

UTF-16は、Unicodeの1文字を16ビット固定長の値で表そうとしたもの。が、前述の通り16ビットでは足りなかったので、結局Unicodeの1文字を16ビットまたは32ビットで表すものとなった。

UnicodeUTF-16
U+0000~U+D7FF0000h~D7FFh
U+D800~U+DFFFなし
U+E000~U+FFFFE000h~FFFFh
U+10000~U+10FFFFサロゲートペア

U+0000~U+D7FFとU+E000~U+FFFFはそのままなのでいいとして、問題は17ビット以上のU+10000~U+10FFFFの部分。この範囲の文字は、D800h~DFFFhの16ビットデータを2つ組み合わせて表現する。この2ワードの組み合わせは、サロゲートペアと呼ばれている。U+10000~U+10FFFFからサロゲートペアへの変換をRubyでクドく書くとこんな感じ。
def to_surrogate_pair(u)
  raise “out of range” if u < 0x10000 or u > 0x10ffff
  upper_5bits = u >> 16
  middle_6bits = (u >> 10) & 0x3f
  lower_10bits = u & 0x3ff
  upper_4bits = upper_5bits - 1
  first_word = (0b110110 << 10) | (upper_4bits << 6) | middle_6bits
  second_word = (0b110111 << 10) | lower_10bits
  [first_word, second_word]
end
上位5ビットから1を引いて4ビットにしているが、上位5ビットは最大でも10000bなので、1を引けば必ず4ビットに収まる。

ワード長が多バイトになれば無視できないのがエンディアンの問題。読み手と書き手であらかじめ意思統一できていれば問題ないが、文字列の先頭にBOM(Byte Order Mark)という特殊な文字を入れて、エンディアンを明示する方法もある。BOMの正体はU+FEFF。そしてU+FFFEは未定義なので、存在しないはず。にも関わらず先頭にU+FFFEがあるように見えたなら、それはエンディアンが逆だと分かるというわけ。
UTF-16と言っても、

  • あらかじめビッグエンディアンと約束しておく
  • あらかじめリトルッグエンディアンと約束しておく
  • エンディアンの約束はせず、BOMを付けてビッグエンディアン
  • エンディアンの約束はせず、BOMを付けてリトルエンディアン
  • エンディアンの約束はせず、BOMを付けずにビッグエンディアン

の5パターンがあるので注意。

UTF-16の文字列をバイト列として見ると、いたるところに00hが入りうる。したがって、既存のコードをUTF-16対応させる場合、C言語のstr系関数は使えなくなる。

0 件のコメント:

コメントを投稿