noocyte のプログラミング研究室 〜プログラムは楽しげに走らねばならない♪〜

文字コードに関する覚え書きと実験

公開:2007/05/06(日)
最終更新:2011/02/11(金)

文字コードについて調べたことや実験したこと, テストプログラム,データファイルなどを随時掲載する予定です. ただし筆者の理解不足や誤解により誤りがあるかもしれませんので, ご利用は自己責任で.

このページの主な更新は Blog でお知らせします.

表示確認ブラウザ:FireFox 3.6,IE8.


0.目次

  1. シフト JIS
    1. Shift_JIS と Windows-31J (CP932) の違い
    2. シフト JIS 2バイト文字の判定
  2. Win32 API の MultiByteToWideChar() は CP932 (Microsoft 版シフト JIS) をどのように変換するか? (書きかけ)
  3. 日本語文字コードの自動判別 (実験準備中)
  4. UTF-16 形式 (UTF 16-bit)
    1. サロゲート
  5. UTF-8 形式 (UTF 8-bit)
    1. 現行の UTF-8 (RFC3629)
    2. 旧 UTF-8 (RFC2279(廃))
  6. Unicode 関数・マクロ集 (C言語)
    1. UTF-16 符号単位がサロゲートか否かを判定する.
    2. サロゲート・ペア ⇔ Unicode スカラ値変換
    3. UTF-16 文字列関数
    4. UTF-8/CESU-8 文字列関数・マクロ
  7. 文字コード雑記
    1. 終端文字について
    2. ASCII の NUL と DEL の本来の意味
  8. 文字コード関連データファイル
  9. サイト内関連ページ
  10. 外部へのリンク
  11. 参考図書
  12. 更新履歴

1.シフト JIS

1.1 Shift_JIS と Windows-31J (CP932) の違い

標準的なシフト JIS (以下 Shift_JIS) が扱う2バイト文字は,JIS X 0208 の 1〜94 区であるが,ベンダ独自拡張版のシフト JIS (Windows-31J:Microsoft のコードページ 932 (以下 CP932) など) では,JIS X 0208 には存在しない 95〜120 区も扱う.

シフト JIS において,2バイト文字の第1バイト (LeadByte), 第2バイト (TrailByte),区番号,点番号の範囲は次のとおり.


IANA登録名
(別名)
Shift_JIS
(MS_Kanji,csShiftJIS)
Windows-31J
(csWindows31J)
別名 (非IANA) SJIS CP932
位置づけ 標準版 ベンダ独自拡張版
2バイト文字 文字集合 JIS X 0208 JIS X 0208
ベンダ拡張文字
ユーザ定義外字
区番号 1〜94 1〜120
点番号 1〜94
LeadByte 0x81〜0x9F,0xE0〜0xEF 0x81〜0x9F,0xE0〜0xFC
TrailByte 0x40〜0x7E,0x80〜0xFC
1バイト文字
(JIS X 0201)
制御文字 (ASCII 互換):0x00〜0x1F,0x7F
ラテン文字用図形文字 (ASCII とほぼ互換):0x20〜0x7E
片仮名用図形文字 (いわゆる半角カナ):0xA1〜0xDF
予約:0x80,0xA0,0xFD〜0xFF
未定義:0xF0〜0xFC (Shift_JIS の場合)

TrailByte が取りうる値は 188 (=94×2) 通りで, これは点番号の範囲の丁度2倍になるように設計されたものである. つまり一つの LeadByte の値に対して,2区分のコードポイント範囲が対応する. したがって LeadByte の取りうる値の範囲は,区番号の範囲の半分 (Shift_JIS では 94/2=47 通り,Windows-31J では 120/2=60 通り) である.

シフト JIS の2バイト文字と区点番号は次のようにして対応付けられる.

  1. 両者のコードポイントをそれぞれ小さい順に並べる.
  2. 同じ順位にあるコードポイント同士を対応させる.

なお JIS X 0208 の文字については,区番号および点番号それぞれに 0x20 を加算すると JIS コード,0xA0 を加算すると EUC-JP になる.

参考:シフトJIS / JIS X 0208 文字コード表 (現在の表示環境におけるシフトJISの2バイト文字集合を表示)


シフト JIS の2バイト文字と区点番号の対応表
[画像:シフトJISの2バイト文字と区点番号の対応表]

1.2 シフト JIS 2バイト文字の判定

/*─────────────────────────────────────
入力  :byte:0x00〜0xFF.
戻り値:byte がシフト JIS 2バイト文字の第1バイトのときそのときに限り真.
2007/10/28(日) Shift_JIS (1〜94区) 限定版を追加.
2008/06/24(火) 改定
2010/03/18(木) 高速版の不等号の右辺の意味がわかりやすいように定数の表記変更.
─────────────────────────────────────*/
// 定義どおりの素直な判定方法.
// (わかりやすいが,範囲の判定方法に無駄があるのであまり賢くない.)
// Shift_JIS (1〜94区) 限定版
#define IsSjisLeadByte(byte) \
  (ValueInRange(0x81U, (unsigned)(byte), 0x9FU) || \
   ValueInRange(0xE0U, (unsigned)(byte), 0xEFU))
// ベンダ拡張版 (1〜120区) 用
#define IsSjisLeadByteX(byte) \
  (ValueInRange(0x81U, (unsigned)(byte), 0x9FU) || \
   ValueInRange(0xE0U, (unsigned)(byte), 0xFCU))

// ┌ここで見つけた巧妙な判定方法.
// ↓(上の方法に比べ,条件分岐が2〜4回から1回に減るので少し高速化できそう.)
// http://www.st.rim.or.jp/~phinloda/cqa/cqa15.html#Q4
// Shift_JIS (1〜94区) 限定版
#define IsSjisLeadByte(byte) \
  ((((unsigned)(byte) ^ 0x20U) - 0xA1U) < (unsigned)(94/2))
// ベンダ拡張版 (1〜120区) 用
#define IsSjisLeadByteX(byte) \
  ((((unsigned)(byte) ^ 0x20U) - 0xA1U) < (unsigned)(120/2))

// 2010/03/05(金) 追記
//   この方法は LeadByte を0から始まる連続領域に移動 (「逆」シフト) させるので,
//   SJIS ⇒ 区点/JIS/EUC 変換に応用すれば判定と変換 (の一部) をまとめて行える.

/*─────────────────────────────────────
入力  :byte:0x00〜0xFF.
戻り値:byte がシフト JIS 2バイト文字の第2バイトのときそのときに限り真.
2008/06/24(火) 改定
2010/03/03(水) 条件分岐を1回削減したバージョンを追加.
2010/03/18(木) 高速版の不等号の右辺の意味がわかりやすいように定数の表記変更.
─────────────────────────────────────*/
// 定義どおりの (以下同文)
#define IsSjisTrailByte(byte) \
  (ValueInRange(0x40U, (unsigned)(byte), 0xFCU) && ((unsigned)(byte) != 0x7FU))

// 上の巧妙な方法に倣って,条件分岐を1回削減したバージョン.
#define IsSjisTrailByte(byte) \
  ((((unsigned)(byte) - 0x40U) < (unsigned)(94 * 2 + 1)) && \
   ((unsigned)(byte) != 0x7FU))

2.Win32 API の MultiByteToWideChar() は CP932
  (Microsoft 版シフト JIS) をどのように変換するか?

書きかけです.

テスト方法

準備中

1バイト文字の変換

dwFlags=MB_ERR_INALID_CHARS の場合

dwFlags=0 の場合

2バイト文字の変換

全般的なまとめについては準備中. 個別の2バイト文字の変換結果については, 「Windows-31J (CP932) 文字コード表」 (Windows-31J のすべての2バイト文字を MultiByteToWideChar() で変換したファイル (UTF-8/16BE)) を参照.

関連リンク


3.日本語文字コードの自動判別

実験準備中.

対象文字コード:

とりあえず関連リンク.


4.UTF-16 形式 (UTF 16-bit, RFC2781,2000年2月)

Unicode 1文字を16ビットで表す. ただし第1〜16面 (U+010000 〜 U+10FFFF) の文字にはサロゲート・ペアを使用する.

4.1 サロゲート

サロゲート・ペアは本来 UTF-16 のものであり,それ以外では (UTF-16 をベースにした) UTF-7,そして残念ながら混乱の元となる CESU-8 でのみ用いられる (はず).

サロゲート・ペア内での UTF-16 符号単位の順序は (上位,下位) の順.


5.UTF-8 形式 (UTF 8-bit)

5.1 現行の UTF-8 (RFC3629,2003年11月)


Unicode スカラ値 第1バイト 第2バイト 第3バイト 第4バイト
UTF8-1
0面0区0〜127点 (ASCII)
U+0000 〜 U+007F
(0000 0000 0xxx xxxx)
0xxx xxxx
(0x00〜0x7F)
- - -
UTF8-2
0面0区128点〜7区
U+0080 〜 U+07FF
(0000 0xxx xxyy yyyy)
110x xxxx
(0xC2〜0xDF)
10yy yyyy
(0x80〜0xBF)
- -
UTF8-3
0面8〜255区
U+0800 〜 U+D7FF
U+E000 〜 U+FFFF
(xxxx yyyy yyzz zzzz)

Surrogate 領域
(U+D800 〜 U+DFFF)
は使用禁止
1110 xxxx
(0xE0)
10yy yyyy
(0xA0〜0xBF)
10zz zzzz
(0x80〜0xBF)
-
1110 xxxx
(0xE1〜0xEC)
10yy yyyy
(0x80〜0xBF)
1110 xxxx
(0xED)
10yy yyyy
(0x80〜0x9F)
1110 xxxx
(0xEE〜0xEF)
10yy yyyy
(0x80〜0xBF)
UTF8-4
1〜16面
U+010000 〜 U+10FFFF
(x xxyy yyyy zzzz zzww wwww)
1111 0xxx
(0xF0)
10yy yyyy
(0x90〜0xBF)
10zz zzzz
(0x80〜0xBF)
10ww wwww
(0x80〜0xBF)
1111 0xxx
(0xF1〜0xF3)
10yy yyyy
(0x80〜0xBF)
1111 0xxx
(0xF4)
10yy yyyy
(0x80〜0x8F)

バイト値による判別方法


5.2 旧 UTF-8 (RFC2279(廃),1998年1月)

RFC2279 は廃止されたが,次の理由でここに掲載する.


UCS-4 第1バイト 第2バイト 第3バイト 第4バイト 第5バイト 第6バイト
0群0面0区0〜127点 (ASCII)
0000 0000 0xxx xxxx
(U+0000 〜 U+007F)
0xxx xxxx
(0x00〜0x7F)
- - - - -
0群0面0区128点〜7区
0000 0xxx xxyy yyyy
(U+0080 〜 U+07FF)
110x xxxx
(0xC0〜0xDF)
10yy yyyy
(0x80〜0xBF)
- - - -
0群0面8〜255区
xxxx yyyy yyzz zzzz
(U+0800 〜 U+FFFF)
1110 xxxx
(0xE0〜0xEF)
10yy yyyy
(0x80〜0xBF)
10zz zzzz
(0x80〜0xBF)
- - -
0群1〜31面
0000 0000 000x xxyy
yyyy zzzz zzww wwww
1111 0xxx
(0xF0〜0xF7)
10yy yyyy
(0x80〜0xBF)
10zz zzzz
(0x80〜0xBF)
10ww wwww
(0x80〜0xBF)
- -
0群32面〜3群
0000 00xx yyyy yyzz
zzzz wwww wwvv vvvv
1111 10xx
(0xF8〜0xFB)
10yy yyyy
(0x80〜0xBF)
10zz zzzz
(0x80〜0xBF)
10ww wwww
(0x80〜0xBF)
10vv vvvv
(0x80〜0xBF)
-
4群〜127群
0xyy yyyy zzzz zzww
wwww vvvv vvuu uuuu
1111 110x
(0xFC〜0xFD)
10yy yyyy
(0x80〜0xBF)
10zz zzzz
(0x80〜0xBF)
10ww wwww
(0x80〜0xBF)
10vv vvvv
(0x80〜0xBF)
10uu uuuu
(0x80〜0xBF)

バイト値による判別方法


6.Unicode 関数・マクロ集 (C言語)

実験中なので,仕様は予告なく変更する可能性があります.

6.1 UTF-16 符号単位がサロゲートか否かを判定する.

#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
// C99 以後
#include <stdint.h>
#elif defined(__unix__)
#include <sys/types.h>
#else
// 処理系に応じて変更すること.
typedef unsigned char  uint8_t;     // 8ビット無符号整数
typedef unsigned short uint16_t;    // 16ビット無符号整数
typedef unsigned int   uint32_t;    // 32ビット無符号整数
#endif

typedef uint8_t  utf8_t;    // UTF-8  の符号単位
typedef uint16_t utf16_t;   // UTF-16 の符号単位
typedef uint32_t utf32_t;   // UTF-32 の符号単位
typedef uint32_t unicode_t; // Unicode スカラ値
typedef uint32_t ucs4_t;    // UCS-4 の1文字

typedef int Bool;

#define OK   0
#define FAIL (-1)

// Unicode スカラ値の範囲
#define UNICODE_MIN     ((unicode_t)0x000000)
#define UNICODE_MAX     ((unicode_t)0x10FFFF)

// 無効な Unicode スカラ値
// (注意:規格とは無関係に,このページで独自に定義した値.)
#define UNICODE_EOF     (~(unicode_t)0)

// 1面の最初のコードポイント
#define UNICODE_PLANE1_MIN   ((unicode_t)0x010000)

// UTF-16 符号単位の範囲
#define UTF16_MIN       ((utf16_t)UNICODE_MIN)
#define UTF16_MAX       ((utf16_t)0xFFFF)

// UCS-4 の範囲
#define UCS4_MIN        ((ucs4_t)0x00000000)
#define UCS4_MAX        ((ucs4_t)0x7FFFFFFF)

/*─────────────────────────────────────
説明  :サロゲート領域の定義.
2007/08/12(日) 作成
2007/10/20(土) SURROGATE_BITS,{HIGH,LOW,BOTH}_SURROGATE_MASK を追加.
─────────────────────────────────────*/
#define HIGH_SURROGATE_MIN ((utf16_t)0xD800) // 上位サロゲート領域開始位置
#define HIGH_SURROGATE_MAX ((utf16_t)0xDBFF) // 上位サロゲート領域終了位置
#define LOW_SURROGATE_MIN  ((utf16_t)0xDC00) // 下位サロゲート領域開始位置
#define LOW_SURROGATE_MAX  ((utf16_t)0xDFFF) // 下位サロゲート領域終了位置
#define SURROGATE_MIN       HIGH_SURROGATE_MIN  // サロゲート領域開始位置
#define SURROGATE_MAX       LOW_SURROGATE_MAX   // サロゲート領域終了位置

// サロゲート可変部分のビット数 (上位,下位共通)
#define SURROGATE_BITS     10

// サロゲート符号単位の可変部分を抽出するためのマスク
#define HIGH_SURROGATE_MASK (((utf16_t)1 << SURROGATE_BITS) - 1)       // 上位用
#define LOW_SURROGATE_MASK  (((utf16_t)1 << SURROGATE_BITS) - 1)       // 下位用
#define BOTH_SURROGATE_MASK (((utf16_t)1 << (SURROGATE_BITS + 1)) - 1) // 上下用

/*─────────────────────────────────────
機能  :有効なスカラ値か否かを判定する.
入力  :unicode:スカラ値.
戻り値:unicode が有効なスカラ値のときそのときに限り真.
2009/01/23(金) 作成
─────────────────────────────────────*/
#define IsValidUnicode(unicode) \
  (((unicode) <= UNICODE_MAX) && !IsSurrogate(unicode))

/*─────────────────────────────────────
機能  :UTF-16 の符号単位が上位サロゲートか否かを判定する.
入力  :utf16:UTF-16 の符号単位.
戻り値:utf16 が上位サロゲートのときそのときに限り真.
2007/08/12(日) 作成
2007/10/20(土) 改定
─────────────────────────────────────*/
#define IsHighSurrogate(utf16) \
  (((unicode_t)(utf16) & ~(unicode_t)HIGH_SURROGATE_MASK) \
   == (unicode_t)HIGH_SURROGATE_MIN)

/*─────────────────────────────────────
機能  :UTF-16 の符号単位が下位サロゲートか否かを判定する.
入力  :utf16:UTF-16 の符号単位.
戻り値:utf16 が下位サロゲートのときそのときに限り真.
2007/08/12(日) 作成
2007/10/20(土) 改定
─────────────────────────────────────*/
#define IsLowSurrogate(utf16) \
  (((unicode_t)(utf16) & ~(unicode_t)LOW_SURROGATE_MASK) \
   == (unicode_t)LOW_SURROGATE_MIN)

/*─────────────────────────────────────
機能  :UTF-16 の符号単位が上位または下位サロゲートか否かを判定する.
入力  :utf16:UTF-16 の符号単位.
戻り値:utf16 が上位または下位サロゲートのときそのときに限り真.
2007/08/12(日) 作成
2007/10/20(土) 改定
─────────────────────────────────────*/
#define IsSurrogate(utf16) \
  (((unicode_t)(utf16) & ~(unicode_t)BOTH_SURROGATE_MASK) \
   == (unicode_t)SURROGATE_MIN)

/*─────────────────────────────────────
機能  :UTF-16 の2つの符号単位の組 (first,second) がサロゲート・ペアか否か
        を判定する.
入力  :first,second:UTF-16 の符号単位.first が先,second が後.
戻り値:(first,second) がサロゲート・ペアのときそのときに限り真.
2007/08/12(日) 作成
─────────────────────────────────────*/
#define IsSurrogatePair(first, second) \
  (IsHighSurrogate(first) && IsLowSurrogate(second))

/*─────────────────────────────────────
機能  :IsHighSurrogate(),IsLowSurrogate(),IsSurrogate() のテスト.
        すべての UTF-16 符号単位についてこれらの述語の判定結果が正しいことを
        確認するとともに,真理値表を out に出力する.
2007/08/12(日) 作成
2007/08/16(木) 改名 (SurrogateTest() ⇒ IsSurrogateTest())
2007/10/20(土) 改定
─────────────────────────────────────*/
void IsSurrogateTest(FILE *out)
{
  unicode_t c;
  Bool isHighSurrogate, isLowSurrogate, isSurrogate;

  fprintf(out, "UTF-16\tHigh\tLow\tSurrogate\n");
  for(c = UTF16_MIN;  c <= UTF16_MAX;  c++) {
    // Is{High,Low,}Surrogate() と別の方法で判定する.
    isHighSurrogate = ValueInRange(HIGH_SURROGATE_MIN, c, HIGH_SURROGATE_MAX);
    isLowSurrogate = ValueInRange(LOW_SURROGATE_MIN, c, LOW_SURROGATE_MAX);
    isSurrogate = ValueInRange(SURROGATE_MIN, c, SURROGATE_MAX);

    // 異なる判定方法の結果が一致することを確認する.
    assert(IsHighSurrogate(c) == isHighSurrogate);
    assert(IsLowSurrogate(c) == isLowSurrogate);
    assert(IsSurrogate(c) == isSurrogate);

    // 判定結果 (真理値表) を出力する.
    fprintf(out, "%04X\t%s\t%s\t%s\n", c,
            isHighSurrogate ? "*" : "",
            isLowSurrogate ? "*" : "",
            isSurrogate ? "*" : "");
  }
}

/*─────────────────────────────────────
テスト用
─────────────────────────────────────*/
int main(void)
{
  IsSurrogateTest(stdout);
  return EXIT_SUCCESS;
}

6.2 サロゲート・ペア ⇔ Unicode スカラ値変換

/*─────────────────────────────────────
機能  :サロゲート・ペアを解読 (Unicode スカラ値に変換) する.
入力  :(1) high:上位サロゲート符号単位 (0xD800 〜 0xDBFF).
        (2) low :下位サロゲート符号単位 (0xDC00 〜 0xDFFF).
戻り値:サロゲート・ペアに対応する Unicode スカラ値 (U+010000 〜 U+10FFFF).
2007/08/12(日) 作成
2007/10/20(土) 改定
2008/06/24(火) 改定
─────────────────────────────────────*/
unicode_t DecodeSurrogatePair(unicode_t high, unicode_t low)
{
  return ((high & (unicode_t)HIGH_SURROGATE_MASK) << SURROGATE_BITS) +
         (low & (unicode_t)LOW_SURROGATE_MASK) + UNICODE_PLANE1_MIN;
}

/*─────────────────────────────────────
機能  :Unicode スカラ値 (1〜16面) を上位サロゲートに変換する.
入力  :unicode:Unicode スカラ値.1〜16面 (U+010000 〜 U+10FFFF) でなければ
        ならない.
戻り値:unicode に対応する上位サロゲート.
2007/08/16(木) 作成
2007/10/20(土) 改定
2008/06/24(火) 改定
─────────────────────────────────────*/
#define Unicode_ToHighSurrogate(unicode) \
  ((utf16_t)(((((unicode) - UNICODE_PLANE1_MIN) >> SURROGATE_BITS) \
              & HIGH_SURROGATE_MASK) | HIGH_SURROGATE_MIN))

/*─────────────────────────────────────
機能  :Unicode スカラ値 (1〜16面) を下位サロゲートに変換する.
入力  :unicode:Unicode スカラ値.1〜16面 (U+010000 〜 U+10FFFF) でなければ
        ならない.
戻り値:unicode に対応する下位サロゲート.
2007/08/16(木) 作成
2007/08/28(火) 改定 (無駄を省いて簡略化)
2007/10/20(土) 改定
─────────────────────────────────────*/
#define Unicode_ToLowSurrogate(unicode) \
  ((utf16_t)((unicode) & LOW_SURROGATE_MASK | LOW_SURROGATE_MIN))

/*─────────────────────────────────────
機能  :Unicode_ToHighSurrogate(),Unicode_ToLowSurrogate(),
        DecodeSurrogatePair() のテスト.
        ・すべてのサロゲート・ペアについて,DecodeSurrogatePair(),
          Unicode_To{High,Low}Surrogate() の結果が正しいことを確認する.
        ・サロゲート・ペアと Unicode スカラ値の対応表を out に出力する.
2007/08/12(日) 作成 (DecodeSurrogatePair() のテスト.)
2007/08/16(木) Unicode_To{High,Low}Surrogate() のテストを追加.
2007/08/18(土) 改名 (DecodeSurrogatePairTest() ⇒ SurrogatePairConvTest())
2007/10/20(土) 改定
2008/06/24(火) 改定
─────────────────────────────────────*/
void SurrogatePairConvTest(FILE *out)
{
  unicode_t high, low;         // サロゲート・ペア
  unicode_t high2, low2;       // Unicode_To{High,Low}Surrogate() の結果.
  unicode_t value;             // DecodeSurrogatePair() の変換結果
  // 第1〜16面のコードポイントを順番に.
  unicode_t refval = UNICODE_PLANE1_MIN;

  fprintf(out, "High Low  Unicode\n"
               "---- ---- --------\n");
  for(high = HIGH_SURROGATE_MIN;  high <= HIGH_SURROGATE_MAX;  high++) {
    for(low = LOW_SURROGATE_MIN;  low <= LOW_SURROGATE_MAX;  low++, refval++) {
      assert(IsSurrogatePair(high, low));
      value = DecodeSurrogatePair(high, low);
      high2 = Unicode_ToHighSurrogate(value);
      low2 = Unicode_ToLowSurrogate(value);
      fprintf(out, "%04X %04X U+%06lX\n", high, low, value);
      assert(value == refval);
      assert(high2 == high);
      assert(low2 == low);
    }
  }
  assert(refval == UNICODE_MAX + 1);
}

/*─────────────────────────────────────
テスト用
─────────────────────────────────────*/
int main(void)
{
  SurrogatePairConvTest(stdout);
  return EXIT_SUCCESS;
}

6.3 UTF-16 文字列関数

/*─────────────────────────────────────
機能  :Unicode スカラ値 ⇒ UTF-16 変換 (UTF-16 文字列バッファに1文字追記).
        Unicode スカラ値 unicode を UTF-16 文字列バッファに追記する.
入出力:*pWritePtr:UTF-16 文字列バッファの,次の書き込み位置を指すポインタ.
        書き込み成功ならば,次の書き込み位置を指すように更新される.
入力  :(1) bufEnd:UTF-16 文字列バッファの直後のアドレス.つまり UTF-16 バ
            ッファ がN要素の配列 buf[N] ならば &buf[N].ただし UTF-16 文字
            列を必ず NUL 終端したい場合は &buf[N-1] とする (NUL は自分で書き
            込むこと).
        (2) unicode:Unicode スカラ値.
戻り値:UTF-16 バッファに書き込まれた UTF-16 符号単位の数 (1〜2).
        バッファの空き容量不足で書き込めなかった場合には0.
        unicode が範囲外 (>U+10FFFF) の場合は -1.
2007/08/16(木) 作成
2007/10/20(土) 改定
─────────────────────────────────────*/
int UTF16_PutChar(utf16_t **pWritePtr, utf16_t *bufEnd, unicode_t unicode)
{
  utf16_t *dest = *pWritePtr;   // 書き込み位置

  if(unicode <= UTF16_MAX) {
    // unicode が0面の場合:そのまま書き込む.
    if(dest >= bufEnd) goto BufferFull;
    *dest = (utf16_t)unicode;
    *pWritePtr = dest + 1; // 次の書き込み位置
    return 1;
  } else if(unicode <= UNICODE_MAX) {
    // unicode が1〜16面の場合:サロゲート・ペアに変換して書き込む.
    if(dest + 1 >= bufEnd) goto BufferFull;
    dest[0] = Unicode_ToHighSurrogate(unicode);
    dest[1] = Unicode_ToLowSurrogate(unicode);
    *pWritePtr = dest + 2;  // 次の書き込み位置
    return 2;
  } else {
    // unicode が17面以上の場合:エラー.
    return -1;
  }
BufferFull: return 0;
}

/*─────────────────────────────────────
機能  :UTF-16 ⇒ Unicode スカラ値変換
        UTF-16 文字列から1文字読み取り,Unicode スカラ値に変換する.
        文字列は NUL 終端でも,そうでなくてもよい.
入力  :stringEnd:UTF-16 文字列の終端を指定する.
        (1) 非 NUL 終端文字列の場合:文字列の直後 (最後の符号単位の次のアド
            レス) を指す.
        (2) NUL 終端文字列の場合:NULL.
入出力:*pReadPtr:UTF-16 文字列の読み出し位置を指すポインタ.1文字を読み
        出した後,次の読み出し位置に更新される.ただし文字列が NUL 終端
        (stringEnd=NULL) で,既に NUL を指している場合には更新されない.
戻り値:*pReadPtr が指す1文字.ただし,
        ・NUL 終端文字列で,*pReadPtr が終端 NUL を指している場合は NUL を
          返し,読み出し位置は更新しない.
        ・非 NUL 終端文字列 (stringEnd≠NULL) で,既に終端に達している場合
          (*pReadPtr≧stringEnd) は UNICODE_EOF を返し,読み出し位置は更新
          しない.
        ・**pReadPtr が孤立した ((正しい) ペアでない) サロゲートの場合は,
          それをそのまま返す.読み出し位置は次の符号単位に進む.
2007/08/17(金) 作成
─────────────────────────────────────*/
unicode_t UTF16_GetChar(const utf16_t **pReadPtr, const utf16_t *stringEnd)
{
  const utf16_t *src = *pReadPtr;
  unicode_t uc, uc2;

  if(stringEnd == NULL) {
    // NUL 終端文字列の場合
    if((uc = *src++) != 0) {
      if(IsHighSurrogate(uc)) {
        uc2 = *src;
        if(IsLowSurrogate(uc2)) {
          uc = DecodeSurrogatePair(uc, uc2);
          src++;
        }
      }
      *pReadPtr = src;
    }
  } else {
    // 非 NUL 終端文字列の場合
    if(src >= stringEnd) {
      uc = UNICODE_EOF; // 文字列終端
    } else {
      uc = *src++;
      if(IsHighSurrogate(uc) && (src < stringEnd)) {
        uc2 = *src;
        if(IsLowSurrogate(uc2)) {
          uc = DecodeSurrogatePair(uc, uc2);
          src++;
        }
      }
      *pReadPtr = src;
    }
  }
  return uc;
}

/*─────────────────────────────────────
機能  :UTF-16 文字列の長さ (文字数および符号単位数) を取得する.
入力  :(1) string:UTF-16 文字列.NUL 終端でも,そうでなくてもよい.
        (2) stringEnd:string の終端.
            ・非 NUL 終端文字列の場合:string の最後の符号単位の次のアドレス.
            ・NUL 終端文字列の場合:NULL.
出力  :*pNUnits:pNUnits≠NULL ならば,string の UTF-16 符号単位の個数を返す.
        終端 NUL は含まない.
戻り値:string に含まれる文字数.ペアをなさないサロゲートも1文字と数える.
        (UTF16_GetChar() が返す文字 (終端 NUL を除く) の個数と一致.)
2007/08/18(土) 作成
─────────────────────────────────────*/
size_t UTF16_Length(const utf16_t *string, const utf16_t *stringEnd, size_t *pNUnits)
{
  const utf16_t *p = string;
  size_t nChars = 0;
  unicode_t uc;

  if(stringEnd == NULL) {
    // NUL 終端文字列の場合
    while((uc = *p) != 0) {
      p++;
      nChars++;
      if(IsHighSurrogate(uc) && IsLowSurrogate(*p)) p++;
    }
  } else {
    // 非 NUL 終端文字列の場合
    while(p < stringEnd) {
      nChars++;
      uc = *p++;
      if(IsHighSurrogate(uc) && (p < stringEnd) && IsLowSurrogate(*p)) p++;
    }
  }
  if(pNUnits != NULL) *pNUnits = (size_t)(p - string);
  return nChars;
}

/*─────────────────────────────────────
機能  :UTF16_GetChar(),UTF16_PutChar(),UTF16_Length() のテスト.
        エラー処理は手抜き.
        (1) UTF-16 文字列 string を NUL 終端文字列として buf[] にコピーする
            (Unicode スカラ値経由) とともに,文字数および UTF-16 符号単位数
            を数える.
        (2) UTF-16 文字列 string を非 NUL 終端文字列として buf[] にコピーす
            る (Unicode スカラ値経由) とともに,文字数および UTF-16 符号単位
            数を数える.また UTF-32 ファイル UTF-32.txt に書き出す.
        (3) buf[] を UTF-16 ファイル UTF-16.txt に書き出す.
2007/08/18(土) 作成
─────────────────────────────────────*/
void UTF16_Test(void)
{
  // Windows では wchar_t=UTF-16 なので OK だが,他の OS ではダメかも.
  static const utf16_t string[] =
    L"\xFEFF"   // BOM
    L"\x24B6牛丼一筋80年♪ \xD842\xDFB7野家\n" // サロゲート・ペア ([土]+[口])
    L"\x24B7森\x9DD7外\n"   // Windows-31J に存在しない文字 // [區鳥]
    L"\x24B8草\x5F45剛\n"   // Windows-31J 機種依存文字 // [弓剪]
    L"\x24B9\x9AD9知新聞\n" // Windows-31J 機種依存文字 // (はしご高)
    L"\x24BA虹は♂,\x873Aは♀.\n" // Windows-31J に存在しない文字 // [虫兒]
    L"\x2707\x21D6\x21D7\x21D8\x21D9\n"; // Unicode 記号
  utf16_t buf[64];
  const utf16_t *src = string;
  const utf16_t * const stringEnd = string + wcslen(string);
  utf16_t *dest = buf;
  utf16_t * const bufEnd = ArrayEnd(buf);
  unicode_t uc;
  size_t size, nChars, nUnits;
  int result;
  FILE *out;

  // NUL 終端文字列としてコピーしてみる.
  for(nChars = 0;  ;  nChars++) {
    uc = UTF16_GetChar(&src, NULL);
    result = UTF16_PutChar(&dest, bufEnd, uc);
    printf("%06X %d\n", uc, result);
    if((uc == 0) || (result <= 0)) break;
  }
  if(result > 0) {
    assert(wcscmp(string, buf) == 0);
    assert(nChars == UTF16_Length(string, NULL, &nUnits));
    assert(nUnits == wcslen(string));
    printf("%lu characters, %lu units\n",
           (unsigned long)nChars, (unsigned long)nUnits);
  }

  printf("\n");

  // 非 NUL 終端文字列としてコピーしてみる.
  // UTF-32 のファイルも作ってみる.
  out = fopen("UTF-32.txt", "wb");
  assert(out != NULL);
  src = string;
  dest = buf;
  nChars = 0;
  while((uc = UTF16_GetChar(&src, stringEnd)) != UNICODE_EOF) {
    result = UTF16_PutChar(&dest, bufEnd, uc);
    printf("%06X %d\n", uc, result);
    if(result <= 0) break;
    size = fwrite(&uc, 1, sizeof(uc), out);
    assert(size == sizeof(uc));
    nChars++;
  }
  fclose(out);
  if(result > 0) {
    assert(memcmp(string, buf, sizeof(utf16_t) * (size_t)(dest - buf)) == 0);
    assert(nChars == UTF16_Length(string, stringEnd, &nUnits));
    assert(nUnits == (size_t)(src - string));
    printf("%lu characters, %lu units\n",
           (unsigned long)nChars, (unsigned long)nUnits);
  }

  // UTF-16 のファイルも作ってみる.
  out = fopen("UTF-16.txt", "wb");
  assert(out != NULL);
  size = fwrite(buf, sizeof(buf[0]), (size_t)(dest - buf), out);
  assert(size == (size_t)(dest - buf));
  fclose(out);
}

/*─────────────────────────────────────
テスト用.
─────────────────────────────────────*/
int main(void)
{
  UTF16_Test();
  return EXIT_SUCCESS;
}

6.4 UTF-8/CESU-8 文字列関数・マクロ

// UTF-8/CESU-8 1文字の最大バイト数
#define UTF8_RFC3629_MAXBYTES   4
#define UTF8_RFC2279_MAXBYTES   6
#define UTF8_MAXBYTES            UTF8_RFC3629_MAXBYTES
#define CESU8_MAXBYTES           6

/*─────────────────────────────────────
入力  :n:1 〜 UTF8_RFC2279_MAXBYTES.
戻り値:UTF-8 のnバイト文字に含まれるスカラ値のビット数.
2008/12/28(日) 作成
─────────────────────────────────────*/
#define UTF8_BITS(n)    (((n) == 1) ? 7 : (n) * 5 + 1)

/*─────────────────────────────────────
入力  :n:1 〜 UTF8_RFC2279_MAXBYTES.
戻り値:UTF-8 でnバイト文字として表現可能な最大の Unicode スカラ値.
注意  :n=RFC3629_MAXBYTES の場合,0x10FFFF (RFC3629 での最大値) ではなく,
        0x1FFFFF (RFC2279 での最大値) を返す.
用途  :UTF-8 非最短形式の効率的判定.
2008/12/28(日) 作成
─────────────────────────────────────*/
#define UTF8_MAX(n)     ((unicode_t)(((unicode_t)1 << UTF8_BITS(n)) - 1U))

/*─────────────────────────────────────
入力  :byte:UTF-8 文字列内の1バイト (無符号整数型).
戻り値:byte が UTF-8 のnバイト文字 (UTF8-n) の先頭バイトのときそのときに
        限り,IS_UTF8_<n>(byte) は真を返す.
注意  :非最短形式の先頭バイト値 (0xC0〜0xC1,0xF5〜0xF7) を排除していない.
余談  :IS_UTF8_[2-6]() については,ValueInRange(0xC0, (byte), 0xDF) などと
        するのに比べて条件分岐が1回少ないのでちょっと速い(はず).
2009/02/10(火) 作成
─────────────────────────────────────*/
#define IS_UTF8_1(byte)      ((byte) <= 0x7FU)
#define IS_UTF8_2(byte)      (((byte) & 0xE0U) == 0xC0U)
#define IS_UTF8_3(byte)      (((byte) & 0xF0U) == 0xE0U)
#define IS_UTF8_4(byte)      (((byte) & 0xF8U) == 0xF0U)
#define IS_UTF8_5(byte)      (((byte) & 0xFCU) == 0xF8U) // RFC2279 のみ
#define IS_UTF8_6(byte)      (((byte) & 0xFEU) == 0xFCU) // RFC2279 のみ

/*─────────────────────────────────────
入力  :byte:UTF-8 文字列内の1バイト (無符号整数型).
戻り値:byte が UTF-8 の複数バイト文字の第2バイト以後 (UTF8-tail) のとき
        そのときに限り真.
2009/02/10(火) 作成
─────────────────────────────────────*/
#define IS_UTF8_TAIL(byte)   (((byte) & 0xC0U) == 0x80U)

/*─────────────────────────────────────
入力  :byte:UTF-8 文字列内の1バイト (無符号整数型).
戻り値:byte が UTF-8 の先頭バイトのときそのときに限り真.
注意  :非最短形式の先頭バイト値 (0xC0〜0xC1,0xF5〜0xF7) を排除していない.
2009/02/10(火) 作成
2010/03/18(木) 高速化のため判定順序を入れ替え.
─────────────────────────────────────*/
#define IS_UTF8_RFC3629_HEAD(byte) \
  (!IS_UTF8_TAIL(byte) && ((byte) <= 0xF7U))

#define IS_UTF8_RFC2279_HEAD(byte) \
  (!IS_UTF8_TAIL(byte) && ((byte) <= 0xFDU))

#define IS_UTF8_HEAD(byte)            IS_UTF8_RFC3629_HEAD(byte)

7.文字コード雑記

7.1 終端文字について

時々「UTF-16 終端文字」,「UTF-8 終端文字」,「SJIS 終端文字」などで検索して来る人がいるが, 文字コードの規格で終端文字というものが決まっているわけではない 「終端文字として何を使うか」ではなく, 「終端文字というものを使う」ことさえ規定されていない. テキストファイルに終端文字なんてないでしょ? えっ,それも知らないの?  そういう人は, 時々テキストファイルをバイナリエディタで開いて見る習慣をつけると文字コードや文字列の正体が理解しやすくなると思うよ.
(例外:大昔 (1980年代) の CP/M や DOS 時代のテキストファイルの一部は終端文字として EOF 文字 (CTRL-Z=0x1A) を使っていたけど, これは文字コードではなく OS (CP/M) の制約に由来する仕様.)

0 (NUL) をメモリ内の文字列の終端文字として使用するのはC言語 (およびその派生言語) の仕様. 正確に言うと,C言語であっても,すべての文字列が NUL 終端とは限らない. 文字列の終端方法は API やライブラリの仕様なので, それぞれのマニュアルでちゃんと確認すること. 例えば Windows の文字コード変換 API である MultiByteToWideChar() や WideCharToMultiByte() などは,文字列は NUL 終端でもそうでなくても使用できる.一般論で「(エンコーディング名) 終端文字」なんて検索しても正解が見つかるわけがない.


■注意


■余談

■参考

2009/12/13(日) 作成
2010/01/10(日) 改定
2010/03/03(水) 改定
2010/03/05(金) 改定
2010/08/08(日) 追記
2011/01/19(水) 追記
2011/02/11(金) 改定

7.2 ASCII の NUL と DEL の本来の意味

ASCII の NUL のコードポイントは 0x00,DEL は 0x7F であるが, そうなっている理由には元々物理的必然性があった.

ASCII が制定された当時,デジタル記録媒体として紙テープが使われていた. 紙テープは,穴が空いている状態が '1' を,穴が空いていない状態が '0' を表す. したがって紙テープの未使用の (まだ文字が書き込まれていない) 部分は 0x00 である.これを空文字 (NUL) と定義した.

紙テープ上に記録された文字を削除したい場合には, 専用の器具を使ってすべてのビットに穴を空ける. つまり 0x7F であり,これを削除済 (DEL) と定義した.

NUL も DEL も, 紙テープ読み込み時には無視 (読み飛ば) すべき文字コードだったのである.

■参考

2007/08/03(金) 作成

8.文字コード関連データファイル

文字コード処理に役立つ (と思われる) データファイルを随時掲載します. ただし,筆者の理解不足や誤解により誤ったデータがあるかもしれませんので, ご利用は自己責任で.

データ形式は主として CSV と,Cのプログラムですぐ利用できる (構造体) 配列のソースにする予定.

8.1 シフトJIS / JIS X 0208 文字コード表
   (現在の表示環境におけるシフトJISの2バイト文字集合を表示)

「Shift_JIS (または CP932,Windows-31J) コード表」などで検索してくる人が多いので追加.

現在の表示環境で表示可能なシフトJISの2バイト文字を区点番号表で示す (1バイト文字については JIS X 0201 を参照). 1〜120区を収録してあるので,Shift_JIS (標準的なシフトJIS) が使用する2バイト文字集合である JIS X 0208 (1〜94区) だけでなく, Shift_JIS のベンダ拡張版である Windows-31J (CP932),MacJapanese 等の文字も表示可能.

このファイル自身,シフトJISコードで記述しているので, JIS X 0208 以外の文字がどう表示されるかは, このファイルの表示環境に依存する.Windows 環境では Windows-31J が,Mac では MacJapanese が,UNIX 環境では (たぶん) JIS X 0208 の文字集合だけが表示されるだろう. (ファイル内の機種依存領域の説明は,Windows-31J および NEC PC-9800 のものである.MacJapanese については Wikipedia を参考に記入.)

「シフトJIS / JIS X 0208 文字コード表」というタイトルだが, シフトJISのコード値ではなく区点番号で表示している.理由は,

JIS第1水準漢字 (16〜47区) は読みごと,第2水準漢字 (48〜83区 (1983年以降の追加分を除く)) は部首ごとに区点番号表を分けている.

注意:このファイルを別の文字コードに変換する場合, JIS X 0208 で定義されている文字以外は変換条件に応じた文字だけが表示されたり, 文字化けしたりする可能性がある.

ShiftJisTable.txt ダウンロード (プレーンテキストファイル (シフトJIS),91KB)

8.2 Windows-31J (CP932) 文字コード表

上記「シフトJIS / JIS X 0208 文字コード表」を, Windows API の MultiByteToWideChar() で Unicode に変換したテキストファイル. Unicode なので Windows-31J の機種依存文字も文字コード上は機種依存ではなくなっているが, 表示環境の Unicode フォントがこれらの字形を持っていなければ表示できない点に注意. また MultiByteToWideChar() は Windows-31J の未定義/予約部分を Unicode の私用領域 (Private Use Area) に変換する (詳細は調査中) ので,その表示についても機種依存となる.

Windows-31J の文字集合を調べるだけでなく,MultiByteToWideChar() が Windows-31J (特に未定義/予約部分) をどのように変換するかを調べるためにも使用できる.

Windows-31J.lzh ダウンロード (UTF-8 + UTF-16(BE),64KB)

9.サイト内関連ページ


10.外部へのリンク

行き当たりばったりにブックマークした,文字コード関連のリンク集 (未整理,敬称略) です.


11.参考図書

文字コード超研究
文字コード超研究
posted with amazlet at 10.04.13
深沢 千尋
ラトルズ
売り上げランキング: 46194
おすすめ度の平均: 4.5
5 隠れた名著
4 まぁまぁ
4 類似所の中では大変読みやすい書籍
5 面白いです。



Unicode標準入門

楽天で買う

価格:3,990円(税込、送料別)

Unicode標準入門
Unicode標準入門
posted with amazlet at 10.04.13
トニー グラハム 関口 正裕
翔泳社
売り上げランキング: 210985
おすすめ度の平均: 5.0
5 ISO/IEC 10646
5 Localization、Internationalizationの虎の巻です



プログラマのための文字コード技術入門

楽天で買う

価格:2,709円(税込、送料別)




Unicode Standard, Version 5.0, The (5th Edition)
The Unicode Consortium
Addison-Wesley Professional
売り上げランキング: 77034



CJKV日中韓越情報処理

楽天で買う

価格:13,440円(税込、送料別)

CJKV日中韓越情報処理
CJKV日中韓越情報処理
posted with amazlet on 07.05.07
ケン ランディ Ken Lunde 小松 章 逆井 克己
オライリージャパン (2002/12)
売り上げランキング: 177876
おすすめ度の平均: 4.0
4 分厚い。



文字符号の歴史(欧米と日本編)

楽天で買う

価格:6,300円(税込、送料別)

文字符号の歴史―欧米と日本編
安岡 孝一 安岡 素子
共立出版 (2006/02)
売り上げランキング: 174787



JISハンドブック(情報基本 2010)

楽天で買う

価格:12,915円(税込、送料別)

JISハンドブック 2010-64

日本規格協会
売り上げランキング: 1029093
おすすめ度の平均: 5.0
5 情報処理技術者試験 必携

文字コード関連規格 (JIS X 0201020202080209021202130221 (Unicode)) や用語集などを含む,情報処理関連 JIS 規格.(目次)




ユニコード漢字情報辞典

三省堂
売り上げランキング: 323656
おすすめ度の平均: 4.0
4 まさに漢字情報辞典

12.更新履歴

このページの主な更新は Blog でお知らせします.

  1. 2007/05/06(日) 公開
  2. 2007/05/13(日) 「シフト JIS の2バイト文字 ⇔ 区点番号/JIS/EUC-JP 変換」を追加.
  3. 2007/05/20(日) 「シフト JIS の2バイト文字 ⇔ 区点番号/JIS/EUC-JP 変換」のソースファイル・ダウンロードを追加.
  4. 2007/06/07(木) 下記を現在追加中.
  5. 2007/08/03(金) 「ASCII の NUL と DEL の本来の意味」を追加.
  6. 2007/08/12(日) 「Unicode 関数・マクロ集 (C言語)」を追加.
  7. 2007/08/16(木) 「シフトJIS / JIS X 0208 文字コード表 (現在の表示環境におけるシフトJISの2バイト文字集合を表示)」を追加.
  8. 2007/08/18(土) 「UTF-16 文字列関数」を追加.
  9. 2007/08/25(土) 「シフト JIS 2バイト文字の判定」を追加. (ネットで第1バイトの巧妙な判定方法を見つけたので.)
  10. 2007/08/26(日) 「Windows-31J (CP932) 文字コード表」を追加.
  11. 2007/08/28(火) Unicode_ToLowSurrogate() を改定.
  12. 2007/10/20(土) SURROGATE_BITS,{HIGH,LOW,BOTH}_SURROGATE_MASK を追加. それに伴い,Unicode 関数・マクロの定義を改定.
  13. 2007/10/28(日) IsSjisLeadByte() に Shift_JIS (1〜94区) 限定版を追加.
  14. 2008/06/24(火) コードをわずかに改定.(実質的な変更はなし.)
  15. 2008/12/28(日) UTF8_BITS()UTF8_MAX() を追加.
  16. 2009/01/23(金) IsValidUnicode() を追加.
  17. 2009/02/18(水) IS_UTF_[1-6]()IS_UTF8_{HEAD,TAIL}() を追加.
  18. 2009/03/03(火) 「Shift_JIS と Windows-31J (CP932) の違い」を「シフト JIS の2バイト文字 ⇔ 区点番号/JIS/EUC-JP 変換」から分離.
  19. 2009/07/06(月) 「シフト JIS の2バイト文字 ⇔ 区点番号/JIS/EUC-JP 変換」を「シフト JIS の2バイト文字と区点番号の対応表」に入れ替え.
  20. 2009/12/13(日)「終端文字について」を追記.
  21. 2010/01/10(日)
  22. 2010/03/03(水)
  23. 2010/08/08(日)「終端文字について」に少し追記.
  24. 2011/01/19(水)「終端文字について」に少し追記.
  25. 2011/02/11(金)「終端文字について」を少し改定.


Copyright © 2007-2011 noocyte, All rights reserved.
E-mail: relipmoced (a) yahoo.co.jp
  (" (a) " を半角のアットマークに書き替えてください.)
リンクはご自由に.
「noocyte のプログラミング研究室」トップページに戻る.


track feed noocyte