メソッドのシグネチャはgetChars(int i, int index, char[] buf)で整数iを表す文字列をbufに格納するというもの。このとき文字列はindexから0の方向にLSB -> MSBの順で埋められていく。
これだけ聞くと簡単そうに聞こえるが、高速化のためにおもしろい処理がされている。
とりあえず実装を載せておく。
static void getChars(int i, int index, char[] buf) { int q, r; int charPos = index; char sign = 0; if (i < 0) { sign = '-'; i = -i; } // Generate two digits per iteration while (i >= 65536) { q = i / 100; // really: r = i - (q * 100); r = i - ((q << 6) + (q << 5) + (q << 2)); i = q; buf [--charPos] = DigitOnes[r]; buf [--charPos] = DigitTens[r]; } // Fall thru to fast mode for smaller numbers // assert(i <= 65536, i); for (;;) { q = (i * 52429) >>> (16+3); r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ... buf [--charPos] = digits [r]; i = q; if (i == 0) break; } if (sign != 0) { buf [--charPos] = sign; } }
まず第一段の処理としてiが65536以上の場合は2桁(10進数で)ずつ処理していく。
コメントを見ると、
r = i - ((q << 6) + (q << 5) + (q << 2))
と
r = i - (q * 100)
が等価であることが分かる。確かに100 = 64 + 32 + 4なのでそのとおり。DigitOnesとDigitTensはそれぞれ2桁の数字の1桁目と2桁目をO(1)で取り出すためのテーブル。これはハードコーディングされている。
まあここまでは分かる。
問題は次の第二段目の処理。
q = (i * 52429) >>> (16+3);
r = i - ((q << 3) + (q << 1));
何ですか、これは。。
何ですか、これは。∑( ̄Д ̄;)
先ほどの100倍と同じように、
r = i - ((q << 3) + (q << 1));
は、r = i - (q * 10)のこと。
ということは逆算して考えると、
q = (i * 52429) >>> (16+3);
ってのは、
q = i / 10ですか?試してみると確かにそうなっている。
でも何で52429と(16+3)なの?他の数字じゃだめなの??
ということで、いろいろ試行錯誤して「52429」が唯一使える数字ということを確認したコードが以下。
package com.kenjih.test; public class Clazz { public void run() { // 割り算をシフト演算にしたいので、2の冪乗のみ for (int i = 0 ; i < 32 ; i++) { int x = 1<<i; // 10 * y / x == 1となるようなyが候補。 int y = x/10; if (y++ == 0) continue; // 65535を掛けたとき0xffffffffを越えないか? //(シフト演算が>>>なのでCでいうところのunsigned intの範囲までなら扱える) if (y * 65535L > 0x0ffffffffL) continue; // 60009 * y - 6000 * x < xか? // 上式が成り立たない場合は、60009 * y % x = 6001(以上)になってしまう。 if (x * 6001 > 60009 * y) System.out.println(y); } } public static void main(String[] args) { new Clazz().run(); } }以下が実行結果。
$ 52429
まさにシフト演算のマジックやぁ~~。(ノ ̄□ ̄)ノオオオォォォォ!
コイツはしびれますね。
で結局これってほんとに速いの??
ということで高速化を考えず書いた普通の処理と比べてみる。
package com.kenjih.test; public class MyInteger { private static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; public static String toString(int i) { char[] buf = new char[16]; boolean minus = i < 0; int pos = 16; while (i > 0) { buf[--pos] = digits[i % 10]; i /= 10; } if (minus) buf[pos] = '-'; return new String(buf, pos, (16 - pos)); } }普通に書くとまあこんなもんでしょっ。
桁数が大きい方が高速化が生きて来そうなので、1,000,000,000 - 1,100,000,000までを変換する処理も用いて比べてみた。
比較結果。
Integer = 0m4.499s
MyInteger = 0m5.681s
2割くらい速い!(微妙っ。いや、この違いは結構大きいのか?)
0 件のコメント:
コメントを投稿