ただの技術メモ

個人備忘録

16進数の計算とUnicodeとUTF-8

Goで文字列を1文字ずつ処理するときの仕組みが気になって深入りした内容をまとめておく。

16進数とバイトの関係

4ビットが表す数値は0から15(24-1)までである。 これは16進数で表現できる「0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F」と対応させて考えることができる。

つまり4ビットを16進数で表現すれば、1文字で表すことができる。

逆に言うと、16進数の1文字は4ビット、16進数の2文字は1バイト(8ビット)と考えることができる。

2進数から16進数への変換

「100110110110」という2進数を考える。 16進数では、10進数と区別するために数値の先頭に「0x」を付ける。

①2進数を4桁(4ビット)ごとに分割する。

4ビットごとに分割すると「1001」「1011」「0110」となる。

②2進数を10進数に変換する。

「20×1+21×0+22×0+23×1」「20×1+21×1+22×0+23×1」「20×0+21×1+22×1+23×0」となり「9」「11」「6」となる。

③10進数を16進数に変換して結合する。

16進数にすると「0x9」「0xB」「0x6」となり、最終的な16進数は「0x9B6」となる。

16進数から2進数への変換

「0x9B6」という16進数を考える。先程と逆の手順を踏む。

①1文字ずつに分解して10進数に変換する。

「0x9」「0xB」「0x6」を10進数に変換すると、「9」「11」「6」となる。

②10進数を2進数に変換する。

「9」「11」「6」を2進数に変換すると、「1001」「1011」「0110」となる。

③各2進数を結合する。

「100110110110」となる。

文字コードと符号化

コンピューター上では全てがバイナリで管理されている。 このバイナリを人間が読める文字に変換したり、人間が読める文字からバイナリに変換するためには文字とバイナリを対応させる必要がある。

このように、文字1つ1つに文字コードを割り当てた対応表のことを文字集合と言う。

文字集合には、ASCIIやUnicode、JIS系など様々な種類がある。

Unicodeについて

ASCIIはアルファベットなど限られた文字しか対応できず、国や地域を超えて文字コードをまとめた国際的な統一文字コードUnicodeである。 例えばUnicodeでは「あ」という文字は「3042」という16進数で対応づけられる。

Unicodeではこの文字コードのことをコードポイントと言い、コードポイントの数値で文字を表す際、「U+16進数」(「U+3042」)のように表記する。

Unicodeの登場以前までは、最初に標準化された文字集合であるASCIIがHTTPなど主要なプロトコルで採用されていた。(HTTP/2ではバイナリやUTF-8を使うよう置き換えられている)

ASCIIは16進数で「0x0」から「0x7F」までの1バイトで文字を表現する。

符号化方式について

文字列をファイルに保存したり、ネットワークで通信したりする場合は、1バイトずつに分解して順番に書き込んだり、送信したりする必要がある。

Unicodeのコードポイントを単純に1バイトずつに分割してファイルに保存すると、コードポイントが被ることがある。

例えば「《」という文字は「U+300A」というコードポイントで表される。これを1バイトずつに単純に分けると、「U+0030」と「U+000A」となり、これらは「0」と改行文字のコードポイントであるため意図した文字列を保存することができない。

unicode-table.com

unicode-table.com

unicode-table.com

このようなことが起こらないように、一定のルールでバイト列に変換する方式を符号化方式と言い、UTF-8UTF-16など様々な種類がある。

UTF-8による符号化

UTF-8はASCII互換の符号化方式で、1バイトから4バイトの可変長で変換し、8ビット(16進数で2文字)単位で表す。Go言語でも符号化方式はUTF-8が採用されている。

UTF-8では、コードポイントの範囲によって何バイトの値で表すかが以下のように決まっている。

コードポイント(16進数) UTF-8のバイト列(2進)
00000000 ~ 0000007F 0xxxxxxx
00000080 ~ 000007FF 110xxxxx 10xxxxxx
00000800 ~ 0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
00010000 ~ 0010FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

⚠️ ASCII文字(コードポインタが00000000 ~ 0000007Fの範囲)は1バイトで表現されるので、ASCII互換で文字を表現できる

例えば「あ」という文字のコードポイント「U+3042」をUTF-8で符号化する。

①コードポイントを4バイトに変換する。

「0x3042」を4バイトに変換すると「00003042」となり、表の上から3行目の範囲に該当し、3バイトで表現されることが分かる。

②4バイトに変換したコードポイント(16進数)の各桁を10進数に変換する。

「3」「0」「4」「2」は10進数に変換しても「3」「0」「4」「2」となる。

③10進数に変換したコードポイントを2進数に変換する。

これを2進数に変換すると「0011」「0000」「0100」「0010」となる。

UTF-8の符号化方式に当てはめて1バイト単位で2進数を得る。

これを表の3行目のバイト列に当てはめると、「11100011」「10000001」「10000010」となる。

⑤2進数を16進数に変換する。

16進数に変換すると、「0xE3」「0x81」「0x82」となり、UTF-8では「あ」という文字が「E3 81 82」というバイト列で表現される。