UTF-8単位で1文字とか

Rubyならeach_charでいいんでしょうが、C++でそれと同じようなことをしようと思ったらよく分からなかったので。WEB+DB PRESS Vol.53のp108付近(検索エンジンのところ)のことをやりたかった。

キモは

(byte & 0xC0) == 0x80

の部分で

まずUTF-8の符号方式では、2バイトめ以降は先頭2ビットが10で始まるという事実を利用し、まず各バイトがUTF-8の先頭かどうかを判定し((buf[i] & 0xC0) == 0x80)、その場合はそれらがタームであるとしparsedに追加をしていきます。

と説明がされている。

ゆとりなおいらにはビット演算が色々分かってないので、その辺から勉強しないといけない。0xC0というのは11000000を16進数で書いたもの。"&"と"&&"は全然違って、"&"はビット積となる。例えば、11100011と11000000のビット積を取ると11000000となる(両方1のところだけ生き残る)。最後にこのビット積が0x80(2進数だと10000000)と等しいかを比べる。この場合だと等しくなく、先頭の文字だと判定できる(ビット積が0x80になっていると先頭ではないと分かる)。

他のケースとして、例えば

  • 10100011と11000000 => 10000000
    • 0x80となり先頭ではない
  • 11100011と11000000 => 11000000
    • 0x80ではないので先頭
  • 01100011と11000000 => 01000000
    • 0x80ではないので先頭(でいいのか?)
  • 10100011と11000000 => 10000000
    • 0x80となり先頭ではない

というのがあって、どれも10から始まるものは先頭にできている。「0xC0とのビット積を取って、0x80と等しいかを見る」という操作と「元のビットの先頭が10で始まっているかを見る」という操作が対応していることが分かる(!!)。

# -*- coding: utf-8 -*-
str = "abあい"

s = String.new
chars = []
first = true
(0..str.bytesize-1).each{|i|
  byte = str.getbyte(i)
  template = "#{byte.to_s(2)}(#{byte}) & #{0xC0.to_s(2)}(0xC0) => #{(byte & 0xC0).to_s(2)} equals #{0x80.to_s(2)}?"
  if (first || (i != str.bytesize && (byte & 0xC0) == 0x80))
    puts "#{template} => yes!!"
    # 先頭ではないケース
    s << byte
    first = false
    next
  end
  puts "#{template} => no!!"
  # 先頭のケース
  chars.push s
  s = String.new
  break if i == str.bytesize - 1
  s << byte
}
chars.push s

puts chars.join(", ")

実行するとこんな感じでUTF-8単位の一文字で分割ができている。

/Users/syou6162/ruby% /opt/local/bin/ruby1.9 bit.rb
1100001(97) & 11000000(0xC0) => 1000000 equals 10000000? => yes!!
1100010(98) & 11000000(0xC0) => 1000000 equals 10000000? => no!!
11100011(227) & 11000000(0xC0) => 11000000 equals 10000000? => no!!
10000001(129) & 11000000(0xC0) => 10000000 equals 10000000? => yes!!
10000010(130) & 11000000(0xC0) => 10000000 equals 10000000? => yes!!
11100011(227) & 11000000(0xC0) => 11000000 equals 10000000? => no!!
10000001(129) & 11000000(0xC0) => 10000000 equals 10000000? => yes!!
10000100(132) & 11000000(0xC0) => 10000000 equals 10000000? => yes!!
a, b, あ, い

うおー、基礎がなってないって感じですね。。。

初めてのRuby

初めてのRuby