62進数を扱おう
昔、16進数というものを習ったときに、
「どうしてFまでしか使わないんだろう?」
「Zまで使えばもっと大きな値を短く表現できるのに」
と思ったものです。
そこで私は、62進数というものをObjective-Cのカテゴリってやつを使って、NSString, 及びNSNumberクラスでサポートすることを思い立ったわけです。
62進数とは
・10進数の0〜61までの数値を、'0' 〜 'Z'までの文字で表すことにします。
・0〜9までの数字の並びは10進数と同じです。
・9の次は、'a' とします。
・zの次は、'A' とします。
・10進数の62を、62進数で"10"とします。
※これら、数字 → 小文字 → 大文字の順の並びは、BASE64とは逆なので注意です。
62進数のメリット
・大きな数値を10進数で表記するよりも短い文字列で表現できます。
32ビットint値の最大値であれば、
10進数:10文字
62進数:6文字
で表現できます。
BASE64の話
数値(バイナリ)を文字列で表現するのには、BASE64というエンコード方式がよく使われます。数値を単に文字列で表現(送信、保存などのために)したいような場合には、ここでいう62進数ではなく、BASE64を使うべきでしょう。ここでいう62進数は、あくまで、単値の文字列としての表現方法の1つです。BASE64は、0〜63までの10進数値を1文字で表現することが可能ですが、BASE64を「64進数」と呼ぶには、少し無理があります。
なぜなら、
①BASE64では、データを4バイト境界にパディングする
BASE64では、データが4の倍数バイトになります。
データの末尾が4の倍数バイトで終わらない場合は、不足分を '=' で埋めることになっています。
②62='+', 63='/'
61までは、数字とアルファベットで表現できますが、BASE64は、64という切りの良い表現範囲にするために、こともあろうか、+と/を使ってしまいました。そしてパディングには=が使われてしまっています。つまり、BASE64を64進数として使ってしまうと、
78+99/ZB
という文字の羅列を見たときに、これが数式を表しているのか、単なる1つの数値なのかが判断できなくなってしまうのです。
つうことで、実装してみます。
勉強用の課題としてもいいかな、と思ったり。
コメント多めにつけました。
base62.h
まあ、単純なC関数で十分なんですけど、せっかくだから私はこのObjective-Cのカテゴリってやつを使わせてもらうよ。
#import <Foundation/Foundation.h> // // 62進数 // 1文字で、10進数の0〜61までの数値を表現する。 // 0, 1, 2, ... 9, a, b, c, ... z, A, B, C, ... Z, 10, ... // // 注意1 // 16進数と比較して、アルファベットの大文字小文字を区別する点が異なります。 // 注意2 // BASE64と比較して、数字、小文字、大文字の並び順が逆です。 // 62進数対応 - 文字列クラス拡張 @interface NSString(Base62) // 10進数 → 62進数文字列変換サポート +(NSString*)base62StringWithInt:(int)value; // 62進数文字列 → 10進数変換サポート -(int)intValueAsBase62; @end // 62進数対応 - 数値クラス拡張 @interface NSNumber(Base62) // 62進数文字列 → 10進数変換サポート +(NSNumber*)numberWithBase62String:(NSString*)s; // 10進数 → 62進数文字列変換サポート -(NSString*)stringAsBase62; @end
base62.m
多少速度を意識して、中身はC使ってます。
#import "base62.h" // 10進数を1文字に変換するためのテーブル const char tableForDecToBase62Char[62] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; // 1文字を10進数の数値に変換するためのテーブル const char tableForBase62CharToDec[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50, 51,52,53,54,55,56,57,58,59,60,61,0, 0, 0, 0, 0, 0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, 25,26,27,28,29,30,31,32,33,34,35,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // 62進数対応 - 文字列クラス拡張 @implementation NSString(Base62) // 10進数 → 62進数文字列変換サポート +(NSString*)base62StringWithInt:(int)value { // 後のループの都合上、ゼロだけ特別扱い if (0 == value) { return @"0"; } // 一時変数準備 char chars[16]; // 15文字分のバッファを確保 chars[15] = 0; // 文字列の終端をマーク char* pos = chars + 15; // ポインタを文字列末尾にセットアップ bool minus = false; // 負の値だったら、最後に、先頭に '-' をつける // 負の値チェック if (0 > value) { minus = true; // 負の値フラグを立てる value *= -1; // 符号を反転して正の数にする } // 0になるまでvalueを62で割り続ける while (0 != value) { // 次の文字の格納位置にポインタをずらす -- pos; // valueを62で割った余りをテーブルから参照して1文字取得 → ポインタ位置にセット *pos = tableForDecToBase62Char[value % 62]; // 62で割ることによって、次の桁へ移動 value /= 62; } // 負の値だったら、先頭にマイナス符号をつける if (minus) { -- pos; *pos = '-'; // 顔文字じゃないよ! } // ASCII文字列をNSStringにして返却 return [NSString stringWithUTF8String:pos]; } // 62進数文字列 → 10進数変換サポート -(int)intValueAsBase62 { // 一時変数準備 int result = 0; // 計算結果の値を保持する変数を0で初期化 const char* p = [self UTF8String]; // 文字列へのポインタを取得 bool minus = false; // 負の値だったら、最後に符号反転する // 1文字目で符号チェック if ('-' == *p) { minus = true; // 負の値フラグを立てる ++ p; // 1文字目が数字じゃないので、次のポインタを次の文字位置へ } // 文字列の末尾まで、1桁繰り上がるごとに62倍していく while (0 != *p) { // 現在の値を繰り上げ result *= 62; // 1文字を数値に変換して加算する result += tableForBase62CharToDec[*p]; // 次の文字位置へ ++ p; } // 負の値だったら符号を反転 if (minus) { result *= -1; } // 演算結果を返却 return result; } @end // 62進数対応 - 数値クラス拡張 @implementation NSNumber(Base62) // 62進数文字列 → 10進数変換サポート +(NSNumber*)numberWithBase62String:(NSString*)s { return [NSNumber numberWithInt:[s intValueAsBase62]]; } // 62進数文字列 → 10進数変換サポート -(NSString*)stringAsBase62 { return [NSString base62StringWithInt:[self intValue]]; } @end
使い方
int main(int argc, const char** argv) { @autoreleasepool { NSString* s = [NSString base62StringWithInt:123456789]; NSLog(@"%@", s); //=> 8m0Kx int d = [s intValueAsBase62]; NSLog(@"%d", d); //=> 123456789 } }
まあ、ちっぽけなプログラムですが、なかなかよくできた気がします。
では、楽しい時間でした!
またの機会を!