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ビットで表すものとなった。
Unicode | UTF-16 |
---|---|
U+0000~U+D7FF | 0000h~D7FFh |
U+D800~U+DFFF | なし |
U+E000~U+FFFF | E000h~FFFFh |
U+10000~U+10FFFF | サロゲートペア |
def to_surrogate_pair(u)上位5ビットから1を引いて4ビットにしているが、上位5ビットは最大でも10000bなので、1を引けば必ず4ビットに収まる。
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
ワード長が多バイトになれば無視できないのがエンディアンの問題。読み手と書き手であらかじめ意思統一できていれば問題ないが、文字列の先頭に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 件のコメント:
コメントを投稿