Base128とは

英数字(a-z, A-Z, 0-9)とひらがな(あ-ん)の128文字を使ってバイト列をエンコードするものです. 実用性はともかく,文字数はBase64より少なくなるというメリットがあります. デメリットは,エンコード後のデータ量がものすごく増えることと,あらかじめエンコード後/デコード後のデータ量がわからない点です.

https://github.com/dozen/encoding

使い方

import

import "github.com/dozen/encoding/base128"

encode

e := base128.NewEncoding(base128.StdEncoding)

e.EncodeToString([]byte("Hello, world!")) //=>kZtけふ8ぢg7どぺmふQお

decode

e := base128.NewEncoding(base128.StdEncoding)

fmt.Printf("%s\n", e.Decode("kZtけふ8ぢg7どぺmふQお")) //=>Hello, world!

実装

Base64を参考にして作り始めたのですがとりあえず動くようにしようとあれこれしている間に全く別物になってしまいました.

Base64の場合,64文字を用いるので6bitを表現できます.エンコード時には,元の8bitのバイト列を6bitに区切るために,4byteずつuintに格納してから6bitずつビットシフトしています。

val := uint(src[si+0])<<16 | uint(src[si+1])<<8 | uint(src[si+2])

dst[di+0] = enc.encode[val>>18&0x3F]
dst[di+1] = enc.encode[val>>12&0x3F]
dst[di+2] = enc.encode[val>>6&0x3F]
dst[di+3] = enc.encode[val&0x3F]

こんなかんじです.encoding/base64/base64.goの100行目ぐらいのところです.

これを参考にして,元の8bitのバイト列を7byteずつ取り出し,7bitずつ区切ることにしました.7byte=56bitなのでuintには収まる範囲です.

val :=	uint(src[si+0])<<48 |
	uint(src[si+1])<<40 |
	uint(src[si+2])<<32 |
	uint(src[si+3])<<24 |
	uint(src[si+4])<<16 |
	uint(src[si+5])<<8 |
	uint(src[si+6])

dst[di+0] = enc.encode[val>>49&0x7F]
dst[di+1] = enc.encode[val>>42&0x7F]
dst[di+2] = enc.encode[val>>35&0x7F]
dst[di+3] = enc.encode[val>>28&0x7F]
dst[di+4] = enc.encode[val>>21&0x7F]
dst[di+5] = enc.encode[val>>14&0x7F]
dst[di+6] = enc.encode[val>>7&0x7F]
dst[di+7] = enc.encode[val&0x7F]

このようになります.

エンコード後の文字列にUTF-8を含むのでBase64そっくりそのままというわけにはいかず,そのへんで躓きました.

また,Base64は3byteを4文字で表現する際に終端が4文字に満たないとき,=で埋めるという処理があります.encoding/base64ではNoPaddingを指定すれば=埋めをしないような処理にも出来ます.

この終端の=埋め処理をBase128ではしていません.切り替えられるようにするべきですが,まだしていません.Base128において=をした場合,最大で6文字の=が並びます.これはちょっと嫌です.

このBase128は使いみちが思いつきませんが,Base256というのも作ってみようかと思います.漢字を含めれば8bitを表現できそうですし,8bitで元のバイト列をnbitへ切り分ける,といった処理をしなくていいのでもっと簡単に作れるような気がします.

そういえば,uintは環境によってサイズが変わるので,uint64を使わないと,32ビット環境でちゃんと動かないですね.これは直さないといけないです.🐘