Counting UTF-8 characters with word
こちらも search non ascii 同様にワード単位で見れば早くなります。具体的には、そもそも UTF-8 は trail byte が [\x80-\xBF] に限定され、またこの範囲は lead byte には出現しません。つまり、バイト列の中から、0b10xxxxxx 以外のバイトの数を数えれば、それがそのまま文字列長になります。これをワード単位で並列して実行する場合は、バイトの最上位ビットの否定と、2番目のビットの論理和をとり、それが真なものを数えれば大丈夫です。
範囲 | 2進 | 最上位ビット | 最上位ビットの否定 | 2番目 | 論理和 |
---|---|---|---|---|---|
\x00-\x7F | 0b0x0000 | 0 | 1 | x | 1 OR x = 1 |
\x80-\xBF | 0b100000 | 1 | 0 | 0 | 0 OR 0 = 0 |
\xC0-\xFF | 0b000000 | 1 | 1 | 0 | 1 OR 0 = 1 |
#if SIZEOF_VOIDP == 8 # define NONASCII_MASK 0x8080808080808080LL #elif SIZEOF_VOIDP == 4 # define NONASCII_MASK 0x80808080UL #endif #define is_utf8_lead_byte(c) (((c)&0xC0) != 0x80) static inline uintptr_t count_utf8_lead_bytes_with_word(const uintptr_t *s) { uintptr_t d = *s; d |= ~(d>>1); d >>= 6; d &= NONASCII_MASK >> 7; d += (d>>8); d += (d>>16); #if SIZEOF_VOIDP == 8 d += (d>>32); #endif return (d&0xF); } const int utf8_length(const char *p, const char *e) { intptr_t align; if (SIZEOF_VOIDP * 2 <= e - p) { const intptr_t lowbits = SIZEOF_VOIDP - 1; const intptr_t *wp, *we; wp = (const intptr_t *)(~lowbits & ((intptr_t)p + lowbits)); we = (const intptr_t *)(~lowbits & (intptr_t)e); for (; p < (const char *)wp; ++p) { if (is_utf8_lead_byte(*p)) len++; } for (; wp < we; ++wp) { if (*wp & NONASCII_MASK) break; } p = (const char *)wp; } for (; p < e; ++p) { if (is_utf8_lead_byte(*p)) len++; } return len; }