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
のところとか!)だし、理解できるところも多くて、今まで自分が勝手に持ってた心理的障壁みたいなものがちょっと取り除かれた気がする。