こんにちは!面白プロデュース事業部のおばらです。
本記事はJS体操第3問「Zalgo Text の生成」の問題のヒントにもなるかもしれないシリーズ第2弾。第1弾は「Zalgo Text のできるまで」でした。
今回は「コードポイント」、そして「コードポイント」と似ているけれどちょっと違う概念の「コードユニット」についてざっくりおさらいしてみます。
目次
「コードポイント」と「コードユニット」の説明の前に、まずは「Unicode」について。
「Unicode(The Unicode Standard)」とは「文字コード(Character Encoding)」の業界標準の「規格(Standard)」です。
「文字コード(Character Encoding)」は(ざっくり言うと)「文字」に「数字」を紐づけるルール&その方法のことです。
文脈によっては「文字に紐づく数字」のことを「文字コード(Character Code)」という場合もあり、日本語で説明するとややこしいですね。「Character Encoding」と「Character Code」を明確に区別するため、「文字エンコーディング(Character Encoding)」という日本語訳が使われる場合もあるようです。また「文字集合(Character Set)」という概念もあり、もうわけがわかりません。
日本語に翻訳された2次情報よりも英語の1次情報を読むほうが混乱しないかもしれませんが、Wikipedia と MDN の「Unicode」の説明を比較しても若干のニュアンスの違いを感じます。
Unicode, formally The Unicode Standard, is a text encoding standard maintained by the Unicode Consortium designed to support the use of text in all of the world's writing systems that can be digitized.
Unicode is a standard character set that numbers and defines characters from the world's different languages, writing systems, and symbols.
本記事では
という前提で話を進めますが、もし間違いがあればご指摘ください。
「Unicode」で「文字」と「数字」がどう紐づいているか、いくつか例を見てみましょう。
12354
12355
12356
65
66
67
「あ」の次は「い」ではなく(捨て仮名、小書き文字の)「ぃ」なのですね。なお、「文字コード(Character Encoding)」(の規格)が違えば、この「文字」と「数字」の対応が異なる場合があります。
「Unicode」についてもっと詳しく知りたい!という方は以下をご覧ください。
「コードポイント(Code Point)」とは「文字に割り当てられた数字」のことです。「符号位置 」や「符号点」、また文脈によっては単に「点」と呼ぶこともあるようです。
「点(Point)」という単語が登場するのは、「空間(Codespace)」、「面(Code Plane)」などの概念と併せて使われるからですが、それら用語を使うととても難解になるのでここでは割愛します。
先程の(Unicode の)「文字」と「数字」の対応の例を「コードポイント」という言葉を使って書き直すと以下です。
12354
12355
12356
65
66
67
ちなみに、Unicode の「コードポイント」の範囲、難しく言うと Unicode の「文字集合の符号空間」は、0
〜1114111
、16進数で表すと0x000000
〜0x10FFFF
と仕様で決まっています。ということはつまり、 Unicode の「コードポイント」すべてを表すには最低 21 ビット必要であることに注目しましょう。1114111
を 2進数に直すと0b100001111111111111111
で、21桁あるからです。
0x10FFFF.toString(2)// => '100001111111111111111'0x10FFFF.toString(2).length// => 21
Unicode の「コードポイント」をすべて表すのに最低21ビット必要であることは、後で「コードポイント」と「コードユニット」の違いを意識するのに役立ちます。
なお、以降本記事で「コードポイント」と書く場合「Unicode のコードポイント」を指すものとします。
コードポイントについて詳しく知りたい!という方は以下をご覧ください。
JavaScript で、ある「文字(列)」の「コードポイント」の数値を取得するにはString
クラスのcodePointAt()
インスタンスメソッドを使うのが便利です。
使い方はこんな感じ。
'あ'.codePointAt(0)// => 12354'A'.codePointAt(0)// => 65
引数の数値は、文字列が複数のコードポイントからなる場合に「何個目のコードポイントを取得するかのインデックス」です。よって、こんな使い方もできます。
'あA'.codePointAt(0)// => 12354'あA'.codePointAt(1)// => 65
0個目(一番最初)のコードポイントを取得したい場合、分かりづらくなるのでオススメはしませんが0
は省略できます。
'あ'.codePointAt()// => 12354'あA'.codePointAt()// => 12354
逆に「コードポイント」の数値を「文字(列)」に変換するにはString
クラスのfromCodePoint()
クラスメソッドを使うのが便利です。
String.fromCodePoint(12354)// => 'あ'String.fromCodePoint(65)// => 'A'
コンマ区切りで複数のコードポイントを渡すこともできます。
String.fromCodePoint(12354,65)// => 'あA'
ちなみに整数でないとしっかりと怒られます。
String.fromCodePoint(12354.678)// => RangeError: Invalid code point 12354.678
Unicode のコードポイントの範囲を超える数値(0x10FFFF
を超える数値)を渡しても怒られます。
String.fromCodePoint(0x10FFFF)// => '�'String.fromCodePoint(0x10FFFF+1)// => RangeError: Invalid code point 1114112
「コードポイント」と似た言葉で「コードユニット」があります。が、「コードユニット」を説明する前に「UTF-8」「UTF-16」「UTF-32」を説明します。
「Unicode」の「文字符号化形式(Character Encoding Form)」には以下の3つがあります。
ざっくり言い換えると、「Unicode」という「規格(Standard)」を採用した/に準拠した「文字コード(Character Encoding)」には以下の3つがあります。
UTF-8 はウェブで用いられるテキストファイル(hoge.html、piyo.css、fuga.js など)で一般的に用いられる「文字コード(Character Encoding)」です。
UTF-16 は JavaScript が内部で文字列を保持するのに使われる「文字コード(Character Encoding)」です。
UTF-32 はウェブではあまり使わない気がするので本記事では割愛します。
ja.wikipedia.orgja.wikipedia.orgja.wikipedia.org
この情報を格納する「単位(Unit)」のことを「コードユニット(Code Unit)」もしくは「コード単位」、「符号単位」などと呼びます。
以降本記事では「コードユニット」は「UTF-16 のコードユニット」を指すものとします。
ある「文字(列)」を「(UTF-16 の)コードユニット」の数値に変換するにはString
クラスのcharCodeAt()
インスタンスメソッドを使うのが便利です。
codeUnitAt()
ではなくcharCodeAt()
なので注意しましょう。
「コードポイント」を取得するインスタンスメソッドはcodePointAt()
なので、
「コードユニット」を取得するインスタンスメソッドはcodeUnitAt()
でしょ!と思いきやそうではありません。
charCodeAt()
は Char Code、つまり「文字コード(Character Code)」を取得するメソッドです。
日本語で「文字コード」と言うと「文字コード(Character Encoding)」のことなのか「文字コード(Character Code)」なのか曖昧になりますが、ここでは「Character Code」のことです。また、「Character Code」も、「コードポイント(Code Point)」のことなのか「コードユニット(Code Unit)」 のことなのかが曖昧でカオスですね。この場合は「コードユニット(Code Unit)」 を指します。
歴史的にはcharCodeAt()
は元々(ECMAScript 1 から)あり、あとからcodePointAt()
がES6 (ECMAScript 2015) で追加されました。
あのときメソッド名をcharCodeAt()
ではなくcodeUnitAt()
にすれば良かったと世界のどこかで誰かが後悔しているかもしれませんが、後方互換性を考えるとcharCodeAt()
のままにするしか無かったのかもしれません。
とにかく、charCodeAt()
の使い方はこんな感じです。
'あ'.charCodeAt(0)// => 12354'A'.charCodeAt(0)// => 65
引数の数値は、文字列が複数のコードユニットからなる場合に「何個目のコードユニットを取得するかのインデックス」です。よって、こんな使い方もできます。
'あA'.charCodeAt(0)// => 12354'あA'.charCodeAt(1)// => 65
0個目(一番最初)のコードユニットを取得したい場合、分かりづらくなるのでオススメはしませんが0
は省略できます。
'あ'.charCodeAt()// => 12354'あA'.charCodeAt()// => 12354
codePointAt()
のときと使い方も結果も(今のところ)同じですね。
文字(列)によっては、codePointAt()
とcharCodeAt()
で違いがでてくるのですが、それはまた後ほど。
逆に「コードユニット」の数値を「文字(列)」に変換するにはString
クラスのfromCharCode()
クラスメソッドを使うのが便利です。
こちらもfromCodeUnit()
ではなくfromCharCode()
ですので注意してください。
String.fromCharCode(12354)// => 'あ'String.fromCharCode(65)// => 'A'
コンマ区切りで複数の「コードユニット」の数値を渡すこともできます。
String.fromCharCode(12354,65)// => 'あA'
ちなみに(String.fromCodePoint()
とは違い)整数でなくても怒られません。小数点以下を切り捨ててくれます。やさしい。というか緩い。
String.fromCharCode(12354.678)// => 'あ'
1つの「コードユニット」に収まりきらない数値(0xFFFF
を超える数値)を渡すと、その下位16ビットのみが使われます。これまた緩い。
String.fromCharCode(0x1234)===String.fromCharCode(0x00001234|0xFFFF0000)// => true
「コードユニット」について詳しく知りたい!という方は以下をご覧ください。developer.mozilla.org
これまでの内容を踏まえて、「コードポイント」と「コードユニット」の違いを簡単にまとめると以下です。
より具体的に UTF-16 の場合を考えると、
と言えます。
Unicode の「コードポイント」の範囲は0x000000
〜0x10FFFF
であること、それを表すには最低21ビット必要であることを思い出してみましょう。
「(UTF-16 の)コードユニット」ひとつ分、つまり16ビットでは、「(Unicode の)コードポイント」すべてを表すことができません(ビットが足りない)。「(UTF-16 の)コードユニット」ひとつ分、つまり16ビットに格納できる(非負整数の)最大値は0xFFFF
です。
よって、「コードポイント」が0xFFFF
を超えると、その「コードポイント」をデータとして格納するには複数(UTF-16 では最大2つの)の「コードユニット」が必要になります。
const n=0xFFFF;String.fromCharCode(n)===String.fromCodePoint(n);// => true
const m=0xFFFF+1;String.fromCharCode(m)===String.fromCodePoint(m);// => false
なお、1つの「コードポイント」を複数の「コードユニット」に格納しなければならない場合、1つめの「コードユニット」に格納しきれない分が単純に2つめの「コードユニット」に格納されるわけではありません。
0xFFFF + 1
、つまり65536
という(1つのコードユニットには収まりきらない)「コードポイント」は、55296
と56320
、16進数に直すと0xD800
と0xDC00
という2つの数値にあるルールで変換され、それぞれ1つずつのコードユニット、合計2つのコードユニットに格納されます。
const m=0xFFFF+1;// => 65536const char=String.fromCodePoint(m);char.charCodeAt(0);// => 55296char.charCodeAt(1);// => 56320
String.fromCharCode(55296,56320)===String.fromCodePoint(0xFFFF+1);// => true
この「(UTF-16 の)コードユニット」1つでは Unicode の「コードポイント」すべてを格納できないという制限を回避するための仕組みが「サロゲートペア」ですが、その説明は本記事では割愛します。
とにかく、JavaScript を書くうえでは
ことに注意してください。
良い機会なので「文字数」についても軽く触れておきます。
『JS体操』では「文字列のlength
プロパティの値」を「文字数」と定義しています。実は「文字列のlength
プロパティの値」は「UTF-16 のコードユニットの数」です。実際の、見た目上の文字数とは異なります。
例えば、以下は人間の目では15文字に見えますが、length
の値はなんと27
です。
つまり「UTF-16 のコードユニットの数」が27
です。
'みんな👨👩👧👦で𩸽のお寿司食べたい🍣'.length // => 27
※ 上記サンプルコードのシンタックスハイライトの言語指定をjavascript
にしたら家族「👨👩👧👦」がバラバラになってしまったので、あえて言語指定をしていません
※「𩸽(ほっけ)」はサロゲートペア界隈ではとても有名な魚です🐟
それなら「コードポイントの数」を数えればいいのでは!となりそうですが、そうともいきません。見た目上の文字数を数えるには「書記素」という概念が必要になります。でもその説明は長くなるのでまたの機会に。きちんと説明するには前述の「サロゲートペア」や「ゼロ幅接合子」、JS体操第3問「Zalgo Textの生成」のテーマでもある「結合文字」などの特殊な文字について触れる必要もでてきます。
もし『JS体操』の文字数を「文字列のlength
の値」ではなく「コードポイントの数」や「書記素クラスタの数」とすると、またいろんなチートが可能になりそうですね。
最後まで読んでいただき、ありがとうございます。
「コードポイント」と「コードユニット」についてざっくり解説してみましたが、名前も似ているし、日本語訳の問題も相まってとてもややこしいですね。
本記事の内容は、もしかしたら現在開催中のJS体操第3問「Zalgo Textの生成」のヒントにもなるかもしれません。
『JS体操』とは面白法人カヤックが主催する JavaScript のコードゴルフ大会。
その第3問は任意の文字列を「Zalgo Text」に変換する JavaScript の長〜いコードをできるだけ短くする!というお題です。元々2222文字もあるコードを、なんと115文字以下まで減らせます。
みなさまのご参加をお待ちしております!
引用をストックしました
引用するにはまずログインしてください
引用をストックできませんでした。再度お試しください
限定公開記事のため引用できません。