[JavaScript] サロゲートペアの扱い方をわかりやすくメモっておく

[JavaScript] サロゲートペアの扱い方をわかりやすくメモっておく

先日、JavaScriptでサロゲートペアに対応した文字列関数を書いたりしてみたが、これだけだとサロゲートペアの扱い方を思い出すにはわかりづらいので、わかりやすくまとめ直してメモっておこうと思う。

前提知識

  • サロゲートペアを使って表現できるコードポイントの範囲はU+10000-U+10FFFFだけ (だから、UnicodeはU+10FFFFまでしかコードポイントを割り当てない仕様になってる!)
  • サロゲートコードポイントはU+D800-U+DFFFの2048個 (11ビット)
  • 2048個を上位サロゲート (U+D800-U+DBFF) と下位サロゲート (U+DC00-U+DFFF) に分けて、上位サロゲートと下位サロゲートの組み合わせ (1024*1024) でU+10000-U+10FFFFの範囲を表現する

コードポイント → サロゲートペアにエンコード

コードポイントをサロゲートペアにエンコードする方法は、↓のサイトによくまとまっている。

サロゲートペア入門:CodeZine

  1. 文字コードから0x10000を引いて1番左の桁を"2"から"1"にする。これをXとする。
  2. Xを0x400で割ってその商を0xD800に足す。これを「上位サロゲート」とする。
  3. Xを0x400で割ってその剰余を0xDC00に足す。これを「下位サロゲート」とする。
  4. 上位サロゲート、下位サロゲートの順番で出力する。

この通りに素直に実装すれば問題なくエンコードできるのだが、割り算を多用していてあまり効率がよくない。0x400で割った商と剰余をとっているのは、Xの上位10ビットと下位10ビットをとるためなので、ビット演算を使ってやった方が効率が良いだろう。

ということで、ビット演算を使った方法を↓にまとめてみた。

  1. コードポイントから0x10000を減算する

    コードポイントの最大値が0x10FFFFなので、0x10000を減算した時の最大値は0xFFFFF (= (11111111111111111111)2 = 20ビット整数の最大値) になる。

    var code = 0x2A6B2;  // U+2A6B2 = "𪚲"
    code -= 0x10000;     // code = 0x1A6B2
    console.assert(code < 0x100000, "20ビット以下のはず");
  2. 減算した値から上位10ビットと下位10ビットを取り出す

    var hi = code >> 10;    // hi = 0x69,  上位10ビットが残る
    var lo = code & 0x3FF;  // lo = 0x2B2, 0x3FF = (1111111111)2
    console.assert(hi < 0x400, "10ビット以下のはず");
    console.assert(lo < 0x400, "10ビット以下のはず");
  3. 上位10ビットと0xD800を、下位10ビットと0xDC00をOR演算する

    0xD800も0xDC00も下位10ビットは全てゼロなので、10ビットの数値との和をとるためにはOR演算をすればよい。

    hi |= 0xD800;  // hi = 0xD869, 0xD800 = (1101100000000000)2
    lo |= 0xDC00;  // lo = 0xDEB2, 0xDC00 = (1101110000000000)2
    console.assert(0xD800 < hi && hi < 0xDBFF, "上位サロゲートの範囲に収まるはず");
    console.assert(0xDC00 < lo && lo < 0xDFFF, "下位サロゲートの範囲に収まるはず");
  4. 上位10ビット、下位10ビットの順に出力する

    window.alert(String.fromCharCode(hi, lo));  // "𪚲"

英語版WikipediaのUTF-16の頁の方がビット演算らしさが出ていてわかりやすいかもしれない。

サロゲートペア → コードポイントにデコード

エンコードの仕組みがわかればデコードも簡単。単純にエンコードの逆の手順をこなしていくだけ。

  1. 上位サロゲートと下位サロゲートをとりだす

    var char = "𪚲";                // "𪚲" = U+2A6B2
    var hi   = char.charCodeAt(0);  //  hi  = 0xD869
    var lo   = char.charCodeAt(1);  //  lo  = 0xDEB2
    console.assert(0xD800 < hi && hi < 0xDBFF, "上位サロゲートの範囲に収まるはず");
    console.assert(0xDC00 < lo && lo < 0xDFFF, "下位サロゲートの範囲に収まるはず");
  2. それぞれのサロゲートの下位10ビットをとりだす

    エンコード時に加えた0xD800と0xDC00を除くためには、AND演算で上位ビットを無視するだけでよい。

    hi &= 0x3FF;  // hi = 0x69
    lo &= 0x3FF;  // lo = 0x2B2
    console.assert(hi < 0x400, "10ビット以下のはず");
    console.assert(lo < 0x400, "10ビット以下のはず");
  3. 上位10ビットと下位10ビットから20ビットのコードポイントを復元する

    上位10ビットを左に10ビット移動して、下位10ビットとOR演算をする。

    var code = (hi << 10) | lo;  // code = 0x1A6B2
    console.assert(code < 0x100000, "20ビット以下のはず");
  4. 0x10000を加える

    code += 0x10000;
    window.alert(code.toString(16));  // "2a6b2"

まとめ

サロゲートペアって良くできた仕組みだなー

スポンサーサイト



関連記事

トラックバック URL

http://liosk.blog103.fc2.com/tb.php/164-a6b44e56

トラックバック

コメント

コメントの投稿

お名前
コメント
編集キー