Goの標準logでやってるintをasciiにする変換処理を見てみる

Goのlogパッケージでやってるintをasciiにする変換処理が何やってるか最初わけわかんなかったけど、多少理解できたからメモしておく。対象のコードは以下の通り。標準で年月日をログの先頭に付与できる機能(2009/01/23 01:23:23 log-messageみたいな感じ)があってそこで使用されている。

src/log/log.go - The Go Programming Language

// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
func itoa(buf *[]byte, i int, wid int) {
    // Assemble decimal in reverse order.
    var b [20]byte
    bp := len(b) - 1
    for i >= 10 || wid > 1 {
        wid--
        q := i / 10
        b[bp] = byte('0' + i - q*10)
        bp--
        i = q
    }
    // i < 10
    b[bp] = byte('0' + i)
    *buf = append(*buf, b[bp:]...)
}

例えば、

itoa(buf, 2017, 4)

とすると、bufには'2''0''1''7'が設定される。つまり、2017とログに出力される。

まず、単純にintをasciiにする処理を見てみる。上記のコードではb[bp] = byte('0' + i - q*10)b[bp] = byte('0' + i)に当たるところ。 そもそもasciiでは10進数の数字と文字の数字は以下のように対応している。

10進数 : 文字
48 : 0
49 : 1
50 : 2
51 : 3
52 : 4
53 : 5
54 : 6
55 : 7
56 : 8
57 : 9

このため、例えばintの7をasciiの7にしたい場合にはintの55を持つ必要がある。このintからasciiへの変換処理をどのように実装するかというと、byte('0' + i)に当たる。つまり、byte('0' + 7) = byte(48 + 7) = byte(55) = '7'という具合である。'0' = 48を基準にしてここにintを足すことでintからasciiへの変換処理を実現している。

では、b[bp] = byte('0' + i - q*10)の処理はどうだろうか。なお、widに関してはここでは説明しない。例えばi = 2017とすると1回目のループで各変数は以下のようになる。

// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
func itoa(buf *[]byte, i int, wid int) {
    // Assemble decimal in reverse order.
    var b [20]byte
    bp := len(b) - 1     // bp = 19
    for i >= 10 || wid > 1 {
        wid--
        q := i / 10     // q = 2017 / 10 = 201
        b[bp] = byte('0' + i - q*10)     // b[19] = byte('0' + 2017 - 201*10) = byte('0' + 2017 - 2010) = byte(48 + 7) = byte(55) =  '7'
        bp--     // bp = 18
        i = q     // i = 201
    }
    // i < 10
    b[bp] = byte('0' + i)
    *buf = append(*buf, b[bp:]...)
}

これを見るとわかるように2017の7(int)を'7'(ascii)と変換していることがわかる。また、ループを繰り返すと一の位から順にintからasciiに変換していることもわかる。

上記のようにしてintからasciiへの変換を実現している。

まとめ

標準パッケージは全て「イカした」実装していて自分じゃ思いつかないようなやり方ばっかりだろうから読んでもわからなさそう、とか勝手に思い込んでた。だけど、読んでみるとプレーンな実装箇所は案外泥臭いやり方(上記q := i / 10のところとか!)だし、理解できるところも多くて、今まで自分が勝手に持ってた心理的障壁みたいなものがちょっと取り除かれた気がする。