Help us understand the problem. What is going on with this article?

UTF-32、UTF-16、UTF-8 の相互変換

More than 1 year has passed since last update.

C++17 では codecvt が非推奨になるようです。
https://cpprefjp.github.io/reference/codecvt.html
UTF-32、UTF-16、UTF-8 の相互変換くらいは外部ライブラリを使用せずに変換したいです。

UTF-32 から UTF-16、UTF-8 への変換をまとめてみました。

Unicode の詳しい解説は、別途 Wikipedia 等を参照してください。
https://ja.wikipedia.org/wiki/Unicode

UTF-8

UTF-8 は UTF-32 のコードを、1~4バイトの可変長で扱います。
以下のような形になっています。

0xxxxxxx                            0 - 127
110yyyyx 10xxxxxx                   128 - 2047
1110yyyy 10yxxxxx 10xxxxxx          2048 - 65535
11110yyy 10yyxxxx 10xxxxxx 10xxxxxx 65536 - 0x10FFFF

先頭のバイトから、何バイト続くか判定できます。

@akinomyoga さんのコメントより、y がすべて 0 になっているものは不正な文字として扱います。

UTF-32 から UTF-8

UTF-32 から UTF-8 への変換は以下のようになります。

bool ConvChU32ToU8(const char32_t u32Ch, std::array<char, 4>& u8Ch) {
    if (u32Ch < 0 || u32Ch > 0x10FFFF) {
        return false;
    }

    if (u32Ch < 128) {
        u8Ch[0] = char(u32Ch);
        u8Ch[1] = 0;
        u8Ch[2] = 0;
        u8Ch[3] = 0;
    } else if (u32Ch < 2048) {
        u8Ch[0] = 0xC0 | char(u32Ch >> 6);
        u8Ch[1] = 0x80 | (char(u32Ch) & 0x3F);
        u8Ch[2] = 0;
        u8Ch[3] = 0;
    } else if (u32Ch < 65536) {
        u8Ch[0] = 0xE0 | char(u32Ch >> 12);
        u8Ch[1] = 0x80 | (char(u32Ch >> 6) & 0x3F);
        u8Ch[2] = 0x80 | (char(u32Ch) & 0x3F);
        u8Ch[3] = 0;
    } else {
        u8Ch[0] = 0xF0 | char(u32Ch >> 18);
        u8Ch[1] = 0x80 | (char(u32Ch >> 12) & 0x3F);
        u8Ch[2] = 0x80 | (char(u32Ch >> 6) & 0x3F);
        u8Ch[3] = 0x80 | (char(u32Ch) & 0x3F);
    }

    return true;
}

UTF-8 から UTF-32

UTF-8 から UTF-32 は以下のようになります。

@akinomyoga さんのプルリクエストより判定にあやまりがあったことが判明しました。
こちら
ありがとうございました。

int GetU8ByteCount(char ch) {
    if (0 <= uint8_t(ch) && uint8_t(ch) < 0x80) {
        return 1;
    }
    if (0xC2 <= uint8_t(ch) && uint8_t(ch) < 0xE0) {
        return 2;
    }
    if (0xE0 <= uint8_t(ch) && uint8_t(ch) < 0xF0) {
        return 3;
    }
    if (0xF0 <= uint8_t(ch) && uint8_t(ch) < 0xF8) {
        return 4;
    }
    return 0;
}

bool IsU8LaterByte(char ch) {
    return 0x80 <= uint8_t(ch) && uint8_t(ch) < 0xC0;
}

bool ConvChU8ToU32(const std::array<char, 4>& u8Ch, char32_t& u32Ch) {
    int numBytes = GetU8ByteCount(u8Ch[0]);
    if (numBytes == 0) {
        return false;
    }
    switch (numBytes) {
        case 1:
            u32Ch = char32_t(uint8_t(u8Ch[0]));
            break;
        case 2:
            if (!IsU8LaterByte(u8Ch[1])) {
                return false;
            }
            if ((uint8_t(u8Ch[0]) & 0x1E) == 0) {
                return false;
            }

            u32Ch = char32_t(u8Ch[0] & 0x1F) << 6;
            u32Ch |= char32_t(u8Ch[1] & 0x3F);
            break;
        case 3:
            if (!IsU8LaterByte(u8Ch[1]) || !IsU8LaterByte(u8Ch[2])) {
                return false;
            }
            if ((uint8_t(u8Ch[0]) & 0x0F) == 0 &&
                (uint8_t(u8Ch[1]) & 0x20) == 0) {
                return false;
            }

            u32Ch = char32_t(u8Ch[0] & 0x0F) << 12;
            u32Ch |= char32_t(u8Ch[1] & 0x3F) << 6;
            u32Ch |= char32_t(u8Ch[2] & 0x3F);
            break;
        case 4:
            if (!IsU8LaterByte(u8Ch[1]) || !IsU8LaterByte(u8Ch[2]) ||
                !IsU8LaterByte(u8Ch[3])) {
                return false;
            }
            if ((uint8_t(u8Ch[0]) & 0x07) == 0 &&
                (uint8_t(u8Ch[1]) & 0x30) == 0) {
                return false;
            }

            u32Ch = char32_t(u8Ch[0] & 0x07) << 18;
            u32Ch |= char32_t(u8Ch[1] & 0x3F) << 12;
            u32Ch |= char32_t(u8Ch[2] & 0x3F) << 6;
            u32Ch |= char32_t(u8Ch[3] & 0x3F);
            break;
        default:
            return false;
    }

    return true;
}

UTF-16

UTF-16 は1文字2バイトとして扱う予定でしたが、2バイトでは収まりきらなくなったため、サロゲートペアという方法を用いて2または4バイトとなりました。
サロゲートペアは前半が 0xD800 ~ 0xDBFF、後半が 0xDC00 ~ 0xDFFF の組み合わせにより、16ビットでは表現できない UTF-32 の文字を表します。

UTF-32 から UTF-16

サロゲートペアの計算方法は以下のようになります。
Wikipediaより

u32Ch  UTF-32 の文字
u16Hi  UTF-16 の前半
u16Low UTF-16 の後半
u16Hi  = (u32Ch - 0x10000) / 0x400 + 0xD800
u16Low = (u32Ch - 0x10000) % 0x400 + 0xDC00

コードにすると以下のようになります。

bool ConvChU32ToU16(const char32_t u32Ch, std::array<char16_t, 2>& u16Ch) {
    if (u32Ch < 0 || u32Ch > 0x10FFFF) {
        return false;
    }

    if (u32Ch < 0x10000) {
        u16Ch[0] = char16_t(u32Ch);
        u16Ch[1] = 0;
    } else {
        u16Ch[0] = char16_t((u32Ch - 0x10000) / 0x400 + 0xD800);
        u16Ch[1] = char16_t((u32Ch - 0x10000) % 0x400 + 0xDC00);
    }

    return true;
}

UTF-16 から UTF-32

サロゲートペアの計算方法は以下のようになります。
Wikipediaより

u32Ch  UTF-32 の文字
u16Hi  UTF-16 の前半
u16Low UTF-16 の後半
u32Ch = 0x10000 + (u16Hi - 0xD800) * 0x400 + (u16Low - 0xDC00)

コードにすると以下のようになります。

bool IsU16HighSurrogate(char16_t ch) { return 0xD800 <= ch && ch < 0xDC00; }

bool IsU16LowSurrogate(char16_t ch) { return 0xDC00 <= ch && ch < 0xE000; }

bool ConvChU16ToU32(const std::array<char16_t, 2>& u16Ch, char32_t& u32Ch) {
    if (IsU16HighSurrogate(u16Ch[0])) {
        if (IsU16LowSurrogate(u16Ch[1])) {
            u32Ch = 0x10000 + (char32_t(u16Ch[0]) - 0xD800) * 0x400 +
                    (char32_t(u16Ch[1]) - 0xDC00);
        } else if (u16Ch[1] == 0) {
            u32Ch = u16Ch[0];
        } else {
            return false;
        }
    } else if (IsU16LowSurrogate(u16Ch[0])) {
        if (u16Ch[1] == 0) {
            u32Ch = u16Ch[0];
        } else {
            return false;
        }
    } else {
        u32Ch = u16Ch[0];
    }

    return true;
}

UTF-8 から UTF-16 または UTF-16 から UTF-8

一度 UTF-32 に変換し、UTF-8、UTF-16 へ変換します。

UTF-8 から UTF-16

bool ConvChU8ToU16(const std::array<char, 4>& u8Ch,
                   std::array<char16_t, 2>& u16Ch) {
    char32_t u32Ch;
    if (!ConvChU8ToU32(u8Ch, u32Ch)) {
        return false;
    }
    if (!ConvChU32ToU16(u32Ch, u16Ch)) {
        return false;
    }
    return true;
}

UTF-16 から UTF-8

bool ConvChU16ToU8(const std::array<char16_t, 2>& u16Ch,
                   std::array<char, 4>& u8Ch) {
    char32_t u32Ch;
    if (!ConvChU16ToU32(u16Ch, u32Ch)) {
        return false;
    }
    if (!ConvChU32ToU8(u32Ch, u8Ch)) {
        return false;
    }
    return true;
}

文字列

後は文字列の各文字を変換していきます。

bool ConvU8ToU16(const std::string& u8Str, std::u16string& u16Str) {
    for (auto u8It = u8Str.begin(); u8It != u8Str.end(); ++u8It) {
        auto numBytes = GetU8ByteCount((*u8It));
        if (numBytes == 0) {
            return false;
        }

        std::array<char, 4> u8Ch;
        u8Ch[0] = (*u8It);
        for (int i = 1; i < numBytes; i++) {
            ++u8It;
            if (u8It == u8Str.end()) {
                return false;
            }
            u8Ch[i] = (*u8It);
        }

        std::array<char16_t, 2> u16Ch;
        if (!ConvChU8ToU16(u8Ch, u16Ch)) {
            return false;
        }

        u16Str.push_back(u16Ch[0]);
        if (u16Ch[1] != 0) {
            u16Str.push_back(u16Ch[1]);
        }
    }
    return true;
}

bool ConvU8ToU32(const std::string& u8Str, std::u32string& u32Str) {
    for (auto u8It = u8Str.begin(); u8It != u8Str.end(); ++u8It) {
        auto numBytes = GetU8ByteCount((*u8It));
        if (numBytes == 0) {
            return false;
        }

        std::array<char, 4> u8Ch;
        u8Ch[0] = (*u8It);
        for (int i = 1; i < numBytes; i++) {
            ++u8It;
            if (u8It == u8Str.end()) {
                return false;
            }
            u8Ch[i] = (*u8It);
        }

        char32_t u32Ch;
        if (!ConvChU8ToU32(u8Ch, u32Ch)) {
            return false;
        }

        u32Str.push_back(u32Ch);
    }
    return true;
}

bool ConvU16ToU8(const std::u16string& u16Str, std::string& u8Str) {
    for (auto u16It = u16Str.begin(); u16It != u16Str.end(); ++u16It) {
        std::array<char16_t, 2> u16Ch;
        if (IsU16HighSurrogate((*u16It))) {
            u16Ch[0] = (*u16It);
            ++u16It;
            if (u16It == u16Str.end()) {
                return false;
            }
            u16Ch[1] = (*u16It);
        } else {
            u16Ch[0] = (*u16It);
            u16Ch[1] = 0;
        }

        std::array<char, 4> u8Ch;
        if (!ConvChU16ToU8(u16Ch, u8Ch)) {
            return false;
        }
        if (u8Ch[0] != 0) {
            u8Str.push_back(u8Ch[0]);
        }
        if (u8Ch[1] != 0) {
            u8Str.push_back(u8Ch[1]);
        }
        if (u8Ch[2] != 0) {
            u8Str.push_back(u8Ch[2]);
        }
        if (u8Ch[3] != 0) {
            u8Str.push_back(u8Ch[3]);
        }
    }
    return true;
}

bool ConvU16ToU32(const std::u16string& u16Str, std::u32string& u32Str) {
    for (auto u16It = u16Str.begin(); u16It != u16Str.end(); ++u16It) {
        std::array<char16_t, 2> u16Ch;
        if (IsU16HighSurrogate((*u16It))) {
            u16Ch[0] = (*u16It);
            ++u16It;
            if (u16It == u16Str.end()) {
                return false;
            }
            u16Ch[1] = (*u16It);
        } else {
            u16Ch[0] = (*u16It);
            u16Ch[1] = 0;
        }

        char32_t u32Ch;
        if (!ConvChU16ToU32(u16Ch, u32Ch)) {
            return false;
        }
        u32Str.push_back(u32Ch);
    }
    return true;
}

bool ConvU32ToU8(const std::u32string& u32Str, std::string& u8Str) {
    for (auto u32It = u32Str.begin(); u32It != u32Str.end(); ++u32It) {
        std::array<char, 4> u8Ch;
        if (!ConvChU32ToU8((*u32It), u8Ch)) {
            return false;
        }

        if (u8Ch[0] != 0) {
            u8Str.push_back(u8Ch[0]);
        }
        if (u8Ch[1] != 0) {
            u8Str.push_back(u8Ch[1]);
        }

        if (u8Ch[2] != 0) {
            u8Str.push_back(u8Ch[2]);
        }
        if (u8Ch[3] != 0) {
            u8Str.push_back(u8Ch[3]);
        }
    }
    return true;
}

bool ConvU32ToU16(const std::u32string& u32Str, std::u16string& u16Str) {
    for (auto u32It = u32Str.begin(); u32It != u32Str.end(); ++u32It) {
        std::array<char16_t, 2> u16Ch;
        if (!ConvChU32ToU16((*u32It), u16Ch)) {
            return false;
        }

        if (u16Ch[0] != 0) {
            u16Str.push_back(u16Ch[0]);
        }
        if (u16Ch[1] != 0) {
            u16Str.push_back(u16Ch[1]);
        }
    }
    return true;
}

参照

https://github.com/benikabocha/unicode_conv

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした