5

我的 Unicode 库中有一部分将 UTF-16 解码为原始 Unicode 代码点。但是,它没有按预期工作。

这是代码的相关部分(省略 UTF-8 和字符串操作):

typedef struct string {
    unsigned long length;
    unsigned *data;
} string;

string *upush(string *s, unsigned c) {
    if (!s->length) s->data = (unsigned *) malloc((s->length = 1) * sizeof(unsigned));
    else            s->data = (unsigned *) realloc(s->data, ++s->length * sizeof(unsigned));
    s->data[s->length - 1] = c;
    return s;
}

typedef struct string16 {
    unsigned long length;
    unsigned short *data;
} string16;

string u16tou(string16 old) {
    unsigned long i, cur = 0, need = 0;
    string new;
    new.length = 0;
    for (i = 0; i < old.length; i++)
        if (old.data[i] < 0xd800 || old.data[i] > 0xdfff) upush(&new, old.data[i]);
        else
            if (old.data[i] > 0xdbff && !need) {
                cur = 0; continue;
            } else if (old.data[i] < 0xdc00) {
                need = 1;
                cur = (old.data[i] & 0x3ff) << 10;
                printf("cur 1: %lx\n", cur);
            } else if (old.data[i] > 0xdbff) {
                cur |= old.data[i] & 0x3ff;
                upush(&new, cur);
                printf("cur 2: %lx\n", cur);
                cur = need = 0;
            }
    return new;
}

它是如何工作的?

string是一个保存 32 位值的结构,适用string16于 16 位值,如 UTF-16。upush所做的只是将一个完整的 Unicode 代码点添加到 a中,并string根据需要重新分配内存。

u16tou是我关注的部分。它遍历string16,正常传递非代理值,并将代理对转换为完整的代码点。错误放置的代理将被忽略。

一对中的第一个代理将其最低 10 位向左移动 10 位,从而形成最终代码点的高 10 位。另一个代理将其最低 10 位添加到最后,然后将其附加到字符串中。

问题?

让我们试试最高的代码点,好吗?

U+10FFFD,最后一个有效的 Unicode 代码点,被编码为0xDBFF 0xDFFDUTF-16。让我们尝试解码。

string16 b;
b.length = 2;
b.data = (unsigned short *) malloc(2 * sizeof(unsigned short));
b.data[0] = 0xdbff;
b.data[1] = 0xdffd;
string a = u16tou(b);
puts(utoc(a));

使用utoc(未显示;我知道它正在工作(见下文))函数将其转换回 UTF-8char *以进行打印,我可以在终端中看到我得到了U+0FFFFD,而不是U+10FFFD结果。

在计算器

在gcalctool中手动进行所有转换会导致相同的错误答案。所以我的语法本身没有错,但算法错了。该算法对我来说似乎是正确的,但它以错误的答案结束。

我究竟做错了什么?

4

2 回答 2

5

解码代理对时需要加上0x10000;引用rfc 2781,您缺少的步骤是第 5 步:

    1) 如果 W1 < 0xD800 或 W1 > 0xDFFF,则字符值 U 为值
       W1 的。终止。

    2) 判断 W1 是否在 0xD800 和 0xDBFF 之间。如果不是,则顺序
       错误,使用 W1 无法获取有效字符。
       终止。

    3)如果没有W2(即序列以W1结束),或者如果W2
       不在 0xDC00 和 0xDFFF 之间,序列错误。
       终止。

    4)构造一个20位无符号整数U',取低10位
       W1 的 10 个高位和 10 个低位
       W2 作为它的 10 个低位。

    5) U'加上0x10000,得到字符值U。终止。

IE。一个解决方法是在您第一次阅读后添加一行:

cur = (old.data[i] & 0x3ff) << 10;
cur += 0x10000;
于 2010-09-24T13:18:03.107 回答
0

您似乎缺少0x10000.

根据this WIKI page,UTF-16 代理对的构造如下:

UTF-16 使用两个代码单元(称为代理对)表示非 BMP 字符(U+10000 到 U+10FFFF)。从代码点中减去第一个 10000 16 得到一个 20 位的值。然后将其拆分为两个 10 位值,每个值都表示为一个代理项,最高有效一半位于第一个代理项中。

于 2010-09-24T13:43:51.280 回答