Base encoding
バイナリデータの文字列エンコード効率化とか調べていて、普段よく利用するBase64以外にも様々なBase encodingの種類があったので少し調べてみた。
Base16, Base32, Base64
Base16
Base16はいわゆるASCIIコード表での16進数表記というイメージで、1byteを2桁の[0-9A-F]で表現する。データの余白が生じないので、Base32/64のように穴埋めの仕様は存在しない。
Base32
Base32は40bit毎にデータを5分割して符号化し、[A-Z2-7]の文字種で表現する。英字は大文字(あるいは小文字のみ?)、数字と判別の迷う「0」「1」「8」は使用されない。穴埋め文字は「=」。
Base64
Base64は24bit毎にデータを3分割して符号化し、[A-Za-z0-9+/]の文字種で表現する。印刷可能な文字のみで構成されるのは全てに共通する特性で、Base64ではデータ量の増加が33%程度となる。
Base64では「+」「/」といったURLでは別の解釈となりうる文字種が含まれるため(「+」は空白、「/」はパス区切りなどとして解釈されうる)、これらを「-」「_」に置き換えたBase64urlという亜流も存在する。WebアプリケーションなどでURLやパスにエンコードされた情報を含めて受け渡す場合Base64urlを使うことが望ましいが、何故かPHPなどでは標準提供されている base64_encode
base64_decode
ではBase64urlに非対応のため自分で変換処理を実装する必要が出てくるという悲しみがある。RubyやPythonでは urlsafe_encode64
があって平和。
Quoted-Printable
0x3d(「=」)を除く0x21-0x7eまでのASCII文字列はそのまま出力、それ以外は =1c
のように「=」に16進表記を並べる形式で出力する。平均では約50%程度のデータ増加量になる。MIMEの仕様の一部であることから分かるようにメール送信などで利用されてたが現在はBase64が主流になっている(と思われる)。
これは別にBase encodingではなかったけれど、なんとなく似たものを思い出したので追記した。
uuencoding
ついでなので、同様にメール用途などで過去に利用されていたエンコード形式も。
Base64と同様に印字可能な64文字にマッピングするが、英大文字+記号で構成されるためBase64と比較すると可視性が悪く記号も悪さをするのでいろいろと不便。
Base58
Base64に対して人間が判別しづらい文字(0
とO
など)や「+」などの特殊文字を除外した58文字を利用するエンコード形式。この特性からビットコインやリップルなどの暗号通貨のアドレス表現などで採用されているとのこと。
Base85
印字可能な85文字種を使ったエンコード形式でいくつかの種類が存在している。いずれも記号類を多く含むため実用時には区切り文字などとの混在に注意する必要がある。データ増加量は25%。
Ascii85
(32bit)はで表現可能という理屈から、32bit単位で5bytesにエンコードする。これは他のBase85でも原則として同じ考え方。
Ascii85はエンコード/デコードのロジックが単純明快で、6/7/6/7/6bitに分割した各データに0x33を加算(または減算)するだけで処理が完了する。
Z85
Ascii85と比較して「`」「"」「'」「\」などを利用しない文字種が選択されているため、文字列リテラルとしてソースコードなどに混在させやすい性質がある。
A Compact Representation of IPv6 Addresses
IPv6アドレスの短縮表現として考案されたエンコード形式。今でも利用されているのか全くの不明。正直読みづらいので流行らなくてよかったのではないかと思う。
base91
可用性を最低限残しつつ印字可能な文字種をできるだけ使ってみようという発想がこれか。
データ増加量が23%というのは優秀だけれど、「-」「\」「'」「"」などを含むのでZ85のほうがバランスとしては優れているようにも感じる。
base100
これは「base100」なのではなく「base💯」なのである。
あらゆる入力データを絵文字に変換するので変換後の表示が実にカオス。Base64よりも高速であるとドヤっているのも面白い。データ増加量など考えてはいけない。
$ echo "the quick brown fox jumped over the lazy dog" | base100 👫👟👜🐗👨👬👠👚👢🐗👙👩👦👮👥🐗👝👦👯🐗👡👬👤👧👜👛🐗👦👭👜👩🐗👫👟👜🐗👣👘👱👰🐗👛👦👞🐁
Base122
バイナリと文字列変換ということで、もはや可視性などは考慮せずにUTF-8で変換可能な文字と置き換えることで可能な限り圧縮しようという発想。当然だがまともに表示できない文字列になる場合もあるが、それでもvalidなUTF-8文字列なのでよかろうという強い意思を感じる。流石というかデータ増加量はこの中では最も小さい14%。
ネイティブ実装が無いのが何よりの難点という感じではあるが、正直ここまで来るとバイナリのまま扱ってもよいのではないだろうか。
JavaScriptでの実装ではブラウザによってBase64よりも高速の場合と低速の場合があるようで悩ましそう。何より、Base64と比較するとgzip圧縮効率が低下するためWebページでの利用は推奨されないという話。ネタとしては面白い。
base65536
未使用のコードポイントを利用して圧縮率を高めるという発想でUTF-32に最適化されたエンコード形式。字面のインパクトが大きい。
一応printableという思想は失われていないのか、What makes a Unicode code point safe? @ Things Of Interestを読む限りでは制御文字や空白系、ゼロ幅文字、サロゲートペアや結合文字なども避けられているようである。括弧や引用符に相当するものもないので文字列リテラルとしても利用しやすい。Unicode正規化で壊れたりもしないように考慮があって意外と考えられている気もする。なぜ全力を尽くしたのか。
base2048
最後の変わり種はbase65536と同じ作者によるTwitterに最適化されたエンコード形式。
Twitterの文字数カウント規則は公式の開発者向けサイトで公開されているが、このルール内で最大限文字数を詰め込むことを目的にした結果、最大385文字を書き込むことができるとのこと。
ここまでやっても日本語などのマルチバイト圏tweetのほうが情報密度高そうなのでチートという感じではある。
おわりに
バイナリデータの文字列エンコードには他にも多くの亜流があるけれど、Base64がデータ効率も特性も実にバランスがよくて納得感だった。組み込みあるいはライブラリ提供されているという観点でも有利だけれど、そういった意味では様々なエンコード形式に対するPythonのカバー率がだいぶ高い雰囲気で面白い(これはWikipedia編集者の知識問題かもしれないけれど)。
独自実装でもよいのでURL safeでもっと効率よい仕組みがあるかと考えていたけれど、普通にBase64urlを使っていくのが大正義であるなあ。