Old revision
Revision as of 2021-05-07 07:26
<Header>
<Parent> ../Programming
<Title> C++で学ぶUnicodeの基本とUTF-8, UTF-16, UTF-32の相互変換アルゴリズム
<CreatedAt> 2020-11-26
<Tags> Unicode, UTF-8, UTF-16, UTF-32, Cpp
<Summary>
広く使われているUnicode規格を理解し,
エンコーディングの手法, UTF-8, UTF-16, UTF-32, を理解して,
多言語の文字コードを扱えるようになることを目指します.
具体的に, UTF-8, UTF-16, UTF-32間の変換方法をC++で実装し,
手法はできるだけ速い方法を用います.
</Summary>
</Header>
# はじめに
コンピュータで文字を処理するにあたり, Unicode規格が多く使われています^[w3techs-usage-statistics].
2021年4月でのW3Techsの調査によると, Webサイトで用いられる文字エンコーディングで, Unicode規格であるUTF-8が全体の96.7%を占めています^[w3techs-usage-statistics].
Unicodeは情報技術規格で, 世界のほとんどの文字体系で表現されるテキストを一貫して符号化,
表現, 処理するために用いられています^[wikipedia-unicode].
この規格は, Unicode Consortiumによって管理されており,
2020年3月現在, 143,859文字を有しています^[wikipedia-unicode].
多言語に対応した文字コードの規格を標準に組み込まれたプログラミング言語は多くあります.
例えば, Pythonは文字列をUTF-8で扱っています^[python-string].
C#は, 文字列をUTF-16で扱っています^[c#-string].
標準で多言語に対応した文字コードをプログラミング言語に組み入れられることで,
プログラマは簡単に多言語の文字を扱うアプリケーション(ゲーム等)を作成できます.
一部のプログラミング言語は, 標準で多言語の文字コードに対応していません.
例えば, C言語は文字を基本的に`char`型の1バイトで表現します.
C++では, 一部utf-8を取り入れる動きはあるものの, 完全に整備されていません^[cpp-utf-8].
これらの言語で多言語を扱いたいとき, 多言語の文字コードに対応したプログラムを自身で作成する必要があります.
本稿では, 広く使われているUnicode規格を理解し,
エンコーディングの手法, UTF-8, UTF-16, UTF-32, を理解して,
多言語の文字コードを扱えるようになることを目指します.
具体的に, UTF-8, UTF-16, UTF-32間の変換方法をC++で実装し,
手法にできるだけ速い方法を用います.
# 用語
Unicode規格で使われる用語についてまとめます.
Unicode規格書(Ver13)を参考にしています.
* ["The Unicode standard"](https://www.unicode.org/versions/Unicode13.0.0/ch03.pdf#G2212) \
Unicode Inc. accessed at 2021-03-31
細かな用語説明に入る前に, 全体像を説明しますと,
Unicode規格は, 全世界の文字ごとに数値(コードポイント)を割り当て,
コンピュータが文字を扱えるようにしています(コードポイントの集合をコードスペースと呼びます).
また, Unicode規格は, 各コードポイントをコンピュータのメモリ, ストレージに符号化するための, 符号化方法を定め,
これをUTFと呼びます. UTFには種類があり, 代表的なものに, UTF-8, UTF-16, UTF-32があります.
以上をまとめたのを, Fig.1. に示します.
![[[fig-1]]Fig.1. Unicode規格全体像](CURRENT_DIR/images/unicode-codespace-utf.jpg)
Unicode codespace (コードスペース):
0x0000 ~ 0x10FFFFの整数の集合^[unicode-standard].
Code point (コードポイント):
Codespace に属する各値^[unicode-standard].
code position とも呼ばれています^[unicode-standard].
各Code point は, U+0000からU+10FFFFと表記されています^[wikipedia-unicode].
("U+"にcode point の値を16進数で加え, 必要に応じて先頭にゼロを付けて, 最小4桁になるようにしています.)
Plane (面):
Planeは, 連続した65,536(0x10000)個のCode pointのグループです^[unicode-glossary].
Plane は, 0~16の番号が割り当てられています.
|[Unicode planeとcode point の範囲]
| Planes || Code point 範囲 | 名称 |
|--------||-----------------|------|
| Plane 0 \
|| U+0000 ~ U+FFFF \
| Basic Multilingual Plane (BMP) |
| Plane 1 \
|| U+10000 ~ U+1FFFF \
| Supplementary Multilingual Plane (SMP) |
| Plane 2 \
|| U+20000 ~ U+2FFFF \
| Supplementary Ideographic Plane (SIP) |
| Plane 3 \
|| U+30000 ~ U+3FFFF \
| Tertiary Ideographic Plane (TIP) |
| Plane 4-13 \
|| U+40000 ~ U+DFFFF \
| - |
| Plane 14 \
|| U+E0000 ~ U+EFFFF \
| Supplementary Special-purpose Plane (SSP) |
| Plane 15-16 \
|| U+F0000 ~ U+10FFFF \
| Supplementary Private Use Area planes (SPUA-A/B) |
Basic Multilingual Plane (BMP):
BMP には, 現代で用いられているほとんどの言語の文字と記号が含まれています.
Code unit (コードユニット):
処理のために符号化されたテキストの単位を表すことができる最小のビットの組み合わせ^[unicode-standard].
Code unit は, コンピュータのストレージ単位になります.
Unicode Transformation Format (UTF):
Unicode Transformation Format (UTF)は, すべてのUnicode code point (サロゲート Code point を除く) から
一意のバイト列へマッピングします^[unicode-inc-utf]^[weblio-utf].
UTF-8:
Code point を8ビット(1バイト)単位(Code unit と呼ぶ)の1バイトから4バイトの可変長バイト列に変換します.
UTF-16:
Code point を1つもしくは2つの16ビット Code unit へ変換します.
ちょうど2バイトを超えるU+10000からCode spaceの終わりU+10FFFFまで, 2 code units で表記されます.
2 code units の上位 code unit を //High-Surrogate Code Unit//, 下位 code unit を //Low-Surrogate Code Unit// と呼びます^[unicode-glossary].
この二つを合わせて, //Surrogate Pair (サロゲートペア)// と呼びます^[unicode-glossary].
High-Surrogate Code Unit, Low-Surrogate Code Unitでとりえる値を持つCode point をそれぞれ,
//High-Surrogate Code Point//, //Low-Surrogate Code Point// と呼び, この領域には文字が割り当てられていません.
Surrogate Code Unit と Surrogate Code Point の関係を, Fig.2. に示します.
![Fig.2. Surrogate Code Unit と Surrogate Code Point の関係](CURRENT_DIR/images/unicode-surrogate.jpg)
UTF-32:
Code point を1つの固定長32ビット Code unit へ変換します.
|[UTFの比較]
| Name | UTF-8 | UTF-16 | UTF-32 |
|----------------------------|-------|--------|--------|
| 下限 Code point | U+0000 | U+0000 | U+0000 |
| 上限 Code point | U+10FFFF | U+10FFFF | U+10FFFF |
| Code unit 長 | 8bits | 16bits | 32bits |
| 一文字当たりの最小バイト数 | 1 | 2 | 4 |
| 一文字当たりの最大バイト数 | 4 | 4 | 4 |
# 符号化方法
# UTF-8
UTF-8は, Code pointを1~4bytesの可変長で変換します.
Code point の範囲に応じて, バイト数が変わります.
次の表に, 符号化の構造を示します. `x`文字は, Code point のビットに置き換えられます.
|[Code point < - > UTF-8変換]
| Code point | Byte 1 | Byte 2 | Byte 3 | Byte 4 |
|--------------------|-----------|-----------|-----------|-----------|
| U+0000 ~ U+007F | 0xxx xxxx | - | - | - |
| U+0080 ~ U+07FF | 110x xxxx | 10xx xxxx | - | - |
| U+0800 ~ U+FFFF | 1110 xxxx | 10xx xxxx | 10xx xxxx | - |
| U+10000 ~ U+10FFFF | 1111 0xxx | 10xx xxxx | 10xx xxxx | 10xx xxxx |
# 例
`あ`という文字をUTF-8で変換してみます. 手順を以下に示します.
1. `あ`のCode pointは, U+3042です^[wiki-a-unicode].
2. U+3042は, U+0800 ~ U+FFFF間にあるので, 3バイトで表現します.
3. U+3042をビット表記すると, `0011 0000 0100 0010`になります. \
先頭の`00`が追加されたのは, 3バイト表記には, 16個のビットが必要になるからです.
4. 変換長は3バイトになるので, `1110`ではじまります.
5. 残りの4ビットをU+3042の先頭4ビットからとり, 1バイト目に`1110 0011`を得ます.
6. 2バイト目は, `10`から始まり, 残り6ビットをU+3042の5ビット目からとります. よって, `1000 0001`を得ます.
7. 3バイト目も, `10`から始まり, 残り6ビットをU+3042の10ビット目からとります. よって, `1000 0010`を得ます.
|[UTF-8変換例]
| 文字 | バイナリ Code point | バイナリ UTF-8 |
|------|---------------------|----------------|
| `あ` U+3042 \
| <span style="color: orange">0011</span> <span style="color: green">0000 01</span><span style="color: red">00 0010</span> \
| 1110 <span style="color: orange">0011</span> 10<span style="color: green">00 0001</span> 10<span style="color: red">00 0010</span> |
# UTF-16
UTF-16は, Code pointを1つのcode unit (2バイト)または, 2つのcode unit (4バイト)に変換します.
変換方法は次の3つに分けられ, それぞれの方法ごとにCode pointの範囲があります.
# U+0000 ~ U+D7FF, U+E000 ~ U+FFFF
UTF-16は, この範囲のCode point を対応するCode pointと数値として等しい単一の16ビットCode unitとして変換します.
この範囲のCode pointは, Plane 0(BMP) に属するCode point から, Surrogate Code Point を除いたものになります.
# U+10000 ~ U+10FFFF
UTF-16は, この範囲のCode point を2つの16bit Code unit へと変換します.
この2つのCode unit をSurrogate pair と呼びます.
変換の手順は以下の通りです.
1. 0x10000 を 変換対象のCode point (U)から引き, 20 bit の数値(U')を得ます.
2. U'の上位10bitに0xD800(1101 1000 0000 0000)を加算し, 16bitのHigh-Surrogate Code Unit (W1)を得ます.
3. U'の下位10bitに0xDC00(1101 1100 0000 0000)を加算し, 16bitのLow-Surrogate Code Unit (W2)を得ます.
U', W1, W2 の計算流れを視覚的に表現したものを以下に示します.
```
U' = yyyy yyyy yyxx xxxx xxxx // U - 0x10000
W1 = 1101 10yy yyyy yyyy yyyy // 0xD800 + yyyyyyyyyy
W2 = 1101 11xx xxxx xxxx xxxx // 0xDC00 + xxxxxxxxxx
```
# U+D800 ~ U+DFFF
この範囲のCode point をSurrogate Code Point と呼び, 文字が割り当てられることはありません.
よって, この範囲を変換する必要はありません.
# 例
`あ`という文字をUTF-16で変換してみます. 手順を以下に示します.
1. `あ`のCode pointは, U+3042です^[wiki-a-unicode].
2. U+3042は, U+0000 ~ U+D7FF, U+E000 ~ U+FFFFの範囲にあるので, \
そのまま16bitのCode unitへと変換し, `0011 0000 0100 0010`を得ます.
`🍣`という文字をUTF-16で変換してみます. 手順を以下に示します.
1. `🍣`のCode pointは, U+1F363です^[wiki-sushi-unicode].
2. U+1F363は, U+10000 ~ U+10FFFFの範囲にあるので, \
2つのCode unit (Surrogate pair) で表記します.
3. 0x1F363 から 0x10000を引き, 0xF363 (`0000 1111 0011 0110 0011`) を得ます.
4. 0xF363の上位10bit に0xD800を加算し, 16bitのHigh-Surrogate Code Unit `1101 1000 0011 1100`を得ます.
5. 0xF363の下位10bit に0xDC00を加算し, 16bitのLow-Surrogate Code Unit `1101 1111 0110 0011`を得ます.
|[UTF-16変換例]
| 文字 | バイナリCode point | バイナリUTF-16 |
|------|--------------------|----------------|
| `あ` U+3042 \
| 0011 0000 0100 0010 \
| 0011 0000 0100 0010 |
| `🍣` U+1F363 \
| 0001 1111 0011 0110 0011 <br>\
(0x1F363 - 0x10000 = <span style="color: red">0000 1111 00</span><span style="color: green">11 0110 0011</span> ) \
| 1101 10<span style="color: red">00 0011 1100</span> 1101 11<span style="color: green">11 0110 0011</span> |
# UTF-32
UTF-32は, 一つのCode point につき, ちょうど32ビット(4バイト)の固定長で変換します.
UTF-32の各32ビット値は, 1つのCode pointを表し,
そのCode pointの数値と正確に一致します.
# フォーマット間の変換方法
フォーマット間の変換をC++で実装します.
変換のアルゴリズムには, Github上で筆者が速そうだと思ったものを選択しました.
今回参考にしたGithubリポジトリは以下の通りです.
2016年でのICU, libiconv, UTFPPの性能評価によると, UTFPPが約2倍速いことが示されていました^[github-utfpp].
* ["Alexhuszagh/UTFPP"](https://github.com/Alexhuszagh/UTFPP). Github. accessed at 2021-04-24.
UTFPPを参考にして, 文字のイテレータを追加したものを作成いたしました.
作成した`cpp`ファイル, `hpp`ファイルを以下にあげます.
本稿では, こちらのスクリプト^[注.nodec]にそって説明します.
* ["ContentsViewer/nodec/unicode.cpp"](https://github.com/ContentsViewer/nodec/blob/main/nodec/src/nodec/unicode.cpp). Github. accessed at 2021-04-24.
* ["ContentsViewer/nodec/unicode.hpp"](https://github.com/ContentsViewer/nodec/blob/main/nodec/include/nodec/unicode.hpp). Github. accessed at 2021-04-24.
# UTF-8 と UTF-32間の変換方法
# UTF-8 => UTF-32
# UTF-8のCode unit数を取得する
utf-8の先頭Code unit から, Code unit 数を調べます.
Code unit 値から直接Code unit 数へ変換するハッシュを作成します.
Code unit 長は1byteで, とりえる値は0~255なので, サイズ256の配列を用意し,
配列の値を0がunit数1になるように順番に割り振ります.
```cpp
const std::array<uint8_t, 256> UTF8_BYTES ={
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0000 0000 ~
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0010 0000 ~
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0100 0000 ~
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0110 0000 ~
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 1000 0000 ~
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 1010 0000 ~
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 1100 0000 ~
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // 1110 0000 ~
3,3,3,3,3,3,3,3, // 1111 0000 ~
4,4,4,4, // 1111 1000 ~
5,5,5,5 // 1111 1100 ~ 1111 1111
};
```
# 各Code unitを連結する
UTF-8の各Code unitのCode point 部分を連結し, 直接Code pointになるUTF-32のCode unit値を取得します.
今, Code unit数 $N$ のUTF-8それぞれのCode unit $C_{i} (i=1,...,N)$があるとします.
$C_{i}$の先頭ビット列値(110x xxxx の場合 1100 0000)を$C_{i, H}$とし,
Code point 部分の値(110x xxxx の場合 000x xxxx)を$C_{i, C}$と置きます.
このとき, $C_{i} = C_{i, H} + C_{i, C}$になります. ($N=3$の場合の説明図をFig.3.に示します.)
![Fig.3. Code unit数が3の時の数式文字の定義](CURRENT_DIR/images/unicode-utf-8-code-unit-math-definition.jpg)
求めたいUTF-32のCode unit値 $C_{32}$は, $C_{i, C}$を順番に6bit左シフトして足し合わせたものになるので, 以下の式になります.
```math
\begin{align}
C_{32} &= \sum_{i}^{N} C_{i, C} << 6(N - i)
\end{align}
```
さらに式を変形すると,
```math
\begin{align}
C_{32} &= \sum_{i}^{N} C_{i, C} << 6(N - i) \nonumber \\
&= \sum_{i}^{N} (C_{i, H} + C_{i, C}) << 6(N - i) - \sum_{i}^{N} C_{i, H} << 6(N - i) \nonumber \\
&= \sum_{i}^{N} C_{i} << 6(N - i) - \sum_{i}^{N} C_{i, H} << 6(N - i) \quad (C_{i} = C_{i, H} + C_{i, C} より)\nonumber \\
&= \sum_{i}^{N} C_{i} << 6(N - i) - Offset \quad (Offset = \sum_{i}^{N} C_{i, H} << 6(N - i) とおく) \label{eq:utf8-utf32}
\end{align}
```
式(\ref{eq:utf8-utf32})から,
UTF-32のCode unit値$C_{32}$は, UTF-8の各Code Unit値 $C_{i}$を順番に6bit左シフトして足し合わせたものから,
$Offset (= \sum_{i}^{N} C_{i, H} << 6(N - i))$を引いたものだとわかります.
各$N$ごとの$Offset$値の配列を定めます.
```cpp
const std::array<uint32_t, 6> UTF8_OFFSETS ={
0x00000000UL, // 0: 0000 0000 0000 0000 0000 0000 0000 0000
0x00003080UL, // 1: 0000 0000 0000 0000 0011 0000 1000 0000
0x000E2080UL, // 2: 0000 0000 0000 1110 0010 0000 1000 0000
0x03C82080UL, // 3: 0000 0011 1100 1000 0010 0000 1000 0000
0xFA082080UL, // 4: 1111 1010 0000 1000 0010 0000 1000 0000
0x82082080UL // 5: 1000 0010 0000 1000 0010 0000 1000 0000
};
```
# コード全体
以上を踏まえて, 実装コードは以下のようになります.
```cpp
template <typename Iter8>
uint32_t code_point_utf8to32(Iter8& begin, Iter8 end, bool strict)
{
uint32_t c = 0;
uint8_t bytes = UTF8_BYTES[*begin];
// check range
if (begin + bytes >= end)
{
// source buffer, check whether or not we have space to replace
throw IllegalCharacterException(__FILE__, __LINE__);
}
// get our UTF-32 character
switch (bytes)
{
case 5:
c = check_strict(strict);
c <<= 6;
// No break. Continue to fall down.
case 4:
c = check_strict(strict);
c <<= 6;
case 3:
c += *begin++; // uint32_t = uint32_t + uint8_t
c <<= 6;
case 2:
c += *begin++;
c <<= 6;
case 1:
c += *begin++;
c <<= 6;
case 0:
c += *begin++;
}
c -= UTF8_OFFSETS[bytes];
return c;
}
```
# UTF-32 => UTF-8
# UTF-8のCode unit数を求める
UTF-32のCode unit値からUTF-8のCode unit数を求めます.
Code pointの範囲によって Code unit数が決まるので, 以下のようにCode unit数を求めます.
`c`はUTF-32のCode unit値, `bytes`はCode unit数です.
```cpp
// caliculate bytes to write
short bytes;
if (c < 0x80) // 1000 0000
{
bytes = 1;
}
else if (c < 0x800) // 1000 0000 0000
{
bytes = 2;
}
else if (c < 0x10000) // 0001 0000 0000 0000 0000
{
bytes = 3;
}
else if (c <= max_utf32)
{
bytes = 4;
}
else
{
bytes = 3;
c = check_strict(strict);
}
```
# Code point を各Code unit へ分割する
Code pointを, UTF-8の各Code unitへ分割します.
Code pointの下位6ビットずつ処理をして,
先頭フラグ(10xx xxxxなど)を立て, マスクします.
Code unit数$N$が2の時の変換例をFig.4.に示します.
![Fig.4. Code unit 数が2の時の, UTF-32からUTF-8への変換例](CURRENT_DIR/images/unicode-utf32-utf8.jpg)
Code unit 数に応じた先頭Code unitのフラグはあらかじめ定義しておきます.
```cpp
const std::array<uint8_t, 7> FIRST_BYTE_MARK ={
0x00, // 0: 0000 0000
0x00, // 1: 0000 0000
0xC0, // 2: 1100 0000
0xE0, // 3: 1110 0000
0xF0, // 4: 1111 0000
0xF8, // 5: 1111 1000
0xFC // 6: 1111 1100
};
```
# コード全体
以上を踏まえて, 実装コードは以下のようになります.
```cpp
template <typename Iter8>
void code_point_utf32to8(uint32_t c, Iter8& begin, Iter8 end, bool strict)
{
// limits
constexpr uint32_t max_utf32 = 0x0010FFFF;
constexpr uint32_t byte_mark = 0x80; //! 1000 0000
constexpr uint32_t byte_mask = 0xBF; //! 1011 1111
// caliculate bytes to write
short bytes;
if (c < 0x80) // 1000 0000
{
bytes = 1;
}
else if (c < 0x800) // 1000 0000 0000
{
bytes = 2;
}
else if (c < 0x10000) // 0001 0000 0000 0000 0000
{
bytes = 3;
}
else if (c <= max_utf32)
{
bytes = 4;
}
else
{
bytes = 3;
c = check_strict(strict);
}
// check range
if (begin + bytes > end)
{
throw BufferRangeException(__FILE__, __LINE__);
}
// write to buffer
begin += bytes;
switch (bytes)
{
case 4:
*--begin = static_cast<uint8_t>((c | byte_mark) & byte_mask);
c >>= 6;
case 3:
*--begin = static_cast<uint8_t>((c | byte_mark) & byte_mask);
c >>= 6;
case 2:
*--begin = static_cast<uint8_t>((c | byte_mark) & byte_mask);
c >>= 6;
case 1:
*--begin = static_cast<uint8_t>(c | FIRST_BYTE_MARK[bytes]);
}
begin += bytes;
}
```
# UTF-16 と UTF-32間の変換方法
# UTF-16 => UTF-32
# Surrogate Pairであるか確認する
先頭Code unit値が, High-Surrogate Code Point であり,
次のCode unit値が, Low-Surrogate Code Point である場合,
文字は, Surrogate Pair で表現されています.
コードで表すと次の通りです.
`begin`はCode unit へのポインタです.
```cpp
constexpr uint32_t high_begin = 0xD800;
constexpr uint32_t high_end = 0xDBFF;
constexpr uint32_t low_begin = 0xDC00;
constexpr uint32_t low_end = 0xDFFF;
const uint32_t c1 = *begin++;
if (c1 >= high_begin && c1 <= high_end)
{
const uint32_t c2 = *begin++;
if (c2 >= low_begin && c2 <= low_end)
{
// surrogate pair
}
}
```
# Code unitを連結する
Surrogate Pairではない場合, Code unit値がCode pointになります.
Surrogate Pairの場合, High-Surrogate Code Unit から0xD800を引いたのち10bit左シフトし,
Low-Surrogate Code Unit から0xDC00をひたものを足し合わせ, 0x10000足したものが,
求めたいCode pointになります(Surrogate Pair の変換手順は, 上記セクション"U+10000 ~ U+10FFFFのUTF-16符号化方法"を参照してください).
コードで表すと次の通りです.
```cpp
constexpr int shift = 10;
constexpr uint32_t base = 0x0010000UL;
((c1 - high_begin) << shift) + (c2 - low_begin) + base;
```
# コード全体
以上を踏まえて, 実装コードは以下のようになります.
```cpp
template<typename Iter16>
uint32_t code_point_utf16to32(Iter16& begin, Iter16 end, bool strict)
{
// limits
constexpr uint32_t high_begin = 0xD800;
constexpr uint32_t high_end = 0xDBFF;
constexpr uint32_t low_begin = 0xDC00;
constexpr uint32_t low_end = 0xDFFF;
constexpr int shift = 10;
constexpr uint32_t base = 0x0010000UL;
const uint32_t c1 = *begin++;
if (c1 >= high_begin && c1 <= high_end)
{
// surrogate pair
const uint32_t c2 = *begin++;
if (c2 >= low_begin && c2 <= low_end)
{
return ((c1 - high_begin) << shift) + (c2 - low_begin) + base;
}
else
{
return check_strict(strict);
}
}
else if (c1 >= low_begin && c1 <= low_end)
{
return check_strict(strict);
}
else
{
return c1;
}
}
```
# UTF-32 => UTF-16
# Code point が, Surrogate Pair で表すものか確認する
U+FFFFより後半のCode point (BMP以降のPlaneに属するCode point) は,
Surrogate Pairで表現されます. BMP内のCode pointは, Surrogate Pair を用いません.
# Code unit に分割する.
BMP内のCode pointは, Surrogate Pair を使わないため, Code pointがそのままCode unit の値になります.
Surrogate Pair を用いるCode point は, 上記セクション"U+10000 ~ U+10FFFFのUTF-16符号化方法"に従って,
二つのCode unit へ変換します.
# コード全体
以上を踏まえて, 実装コードは以下のようになります.
```cpp
template <typename Iter16>
void code_point_utf32to16(uint32_t c, Iter16& begin, Iter16& end, bool strict)
{
// limits
constexpr uint32_t max_utf32 = 0x0010FFFF; //! max unicode code space size
constexpr uint32_t high_begin = 0xD800; //! 1101 1000 0000 0000
constexpr uint32_t low_begin = 0xDC00; //! 1101 1100 0000 0000
constexpr uint32_t max_bmp = 0x0000FFFF; //! Basic Multilingual Plane (BMP)
constexpr int shift = 10;
constexpr uint32_t base = 0x0010000UL; //! 0001 0000 0000 0000 0000
constexpr uint32_t mask = 0x3FFUL; //! 0000 0011 1111 1111
// variables
if (c <= max_bmp)
{
if (c >= high_begin && c <= low_begin)
{
*begin++ = check_strict(strict);
}
else
{
*begin++ = static_cast<uint16_t>(c);
}
}
else if (c > max_utf32)
{
*begin++ = check_strict(strict);
}
else
{
if (begin + 1 > end)
{
throw BufferRangeException(__FILE__, __LINE__);
}
c -= base;
*begin++ = static_cast<uint16_t>((c >> shift) + high_begin);
*begin++ = static_cast<uint16_t>((c & mask) + low_begin);
}
}
```
# UTF-8 と UTF-16間の変換方法
# UTF-8 => UTF-16
UTF-8をUTF-32に変換して, UTF-32をUTF-16へと変換します.
# UTF-16 => UTF-8
UTF-16をUTF-32に変換して, UTF-32をUTF-8へと変換します.
# おわりに
以上, Unicode規格の基本を学び, UTF-8, UTF-16, UTF-32間の変換方法をC++を使って実装しました.
本手法に基づいて, C++でUnicode規格に沿った文字列ライブラリ([/Library/Cpp/Unicode/Unicode])を作成しましたので参考にしてみてください.
# 注釈
[注.nodec]: こちらのスクリプトは, nodec プロジェクト([/nodec/nodec])に属します. \
nodecプロジェクトは, 筆者が自作でゲームエンジンを作成することを目指したプロジェクトで, \
作成過程で得られるプログラミング技術の習得が主な目的です.
# 参考文献
[w3techs-usage-statistics]: ["Usage statistics of character encodings for websites"](https://w3techs.com/technologies/overview/character_encoding). \
W3Techs. accessed at 2021-04-05
[wikipedia-unicode]: ["Unicode"](https://en.wikipedia.org/wiki/Unicode). \
Wikipedia. accessed at 2021-03-30
[unicode-glossary]: ["Glossary of Unicode Terms"](https://unicode.org/glossary/). \
Unicode Inc. accessed at 2021-03-30.
[unicode-standard]: ["The Unicode standard"](https://www.unicode.org/versions/Unicode13.0.0/ch03.pdf#G2212). \
Unicode Inc. accessed at 2021-03-31.
[weblio-utf]: ["UTF"](https://www.weblio.jp/content/Unicode+Transformation+Format). \
Weblio. accessed at 2021-04-05.
[unicode-inc-utf]: ["UTF-8, UTF-16, UTF-32 & BOM"](https://unicode.org/faq/utf_bom.html). \
Unicode Inc. accessed at 2021-04-05.
[python-string]: ["Unicode HOWTO"](https://docs.python.org/3/howto/unicode.html). \
Python. accessed at 2021-04-21.
[c#-string]: [".NET での文字エンコード"](https://docs.microsoft.com/ja-jp/dotnet/standard/base-types/character-encoding-introduction). \
Microsoft. accessed at 2021-04-21.
[cpp-utf-8]: ["UTF-8エンコーディングされた文字の型としてchar8_tを追加"](https://cpprefjp.github.io/lang/cpp20/char8_t.html). \
cpprefjp. accessed at 2021-04-21.
[wiki-a-unicode]: ["Unicode一覧 3000-3FFF"](https://ja.wikipedia.org/wiki/Unicode%E4%B8%80%E8%A6%A7_3000-3FFF). \
Wikipedia. accessed at 2021-04-22.
[github-utfpp]: ["Alexhuszagh/UTFPP"](https://github.com/Alexhuszagh/UTFPP). Github. accessed at 2021-04-24.
[wiki-sushi-unicode]: ["UnicodeのEmojiの一覧"](https://ja.wikipedia.org/wiki/Unicode%E3%81%AEEmoji%E3%81%AE%E4%B8%80%E8%A6%A7). \
Wikipedia. accessed at 2021-04-22.