次の関数を使用して、整数の対数の底2を計算します。
public static int log2(int n){
if(n <= 0) throw new IllegalArgumentException();
return 31 - Integer.numberOfLeadingZeros(n);
}
最適なパフォーマンスがありますか?
誰かがその目的のために準備ができているJ2SE API関数を知っていますか?
PD1驚いたことに、浮動小数点演算は整数演算よりも高速に見えます。
PD2コメントのため、私はより詳細な調査を実施します。
PD私の整数演算関数は、Math.log(n)/Math.log(2)よりも10倍高速です。
整数演算を支援するために浮動小数点を使用することを考えている場合は、注意する必要があります。
私は通常、可能な限りFP計算を避けるようにします。
浮動小数点演算は正確ではありません。 (int)(Math.log(65536)/Math.log(2))
が何に評価されるかを確実に知ることはできません。たとえば、PCでMath.ceil(Math.log(1<<29) / Math.log(2))
は30です。数学的には正確に29になります。(int)(Math.log(x)/Math.log(2))
が失敗するxの値は見つかりませんでした(「危険な」値が32個しかないため)。どのPCでも同じように動作します。
ここでの通常のトリックは、丸め時に「イプシロン」を使用することです。 (int)(Math.log(x)/Math.log(2)+1e-10)
のように失敗することはありません。この「イプシロン」の選択は簡単な作業ではありません。
より一般的なタスクを使用したより多くのデモ-int log(int x, int base)
を実装しよう:
テストコード:
static int pow(int base, int power) {
int result = 1;
for (int i = 0; i < power; i++)
result *= base;
return result;
}
private static void test(int base, int pow) {
int x = pow(base, pow);
if (pow != log(x, base))
System.out.println(String.format("error at %d^%d", base, pow));
if(pow!=0 && (pow-1) != log(x-1, base))
System.out.println(String.format("error at %d^%d-1", base, pow));
}
public static void main(String[] args) {
for (int base = 2; base < 500; base++) {
int maxPow = (int) (Math.log(Integer.MAX_VALUE) / Math.log(base));
for (int pow = 0; pow <= maxPow; pow++) {
test(base, pow);
}
}
}
対数の最も単純な実装を使用する場合、
static int log(int x, int base)
{
return (int) (Math.log(x) / Math.log(base));
}
これは印刷します:
error at 3^5
error at 3^10
error at 3^13
error at 3^15
error at 3^17
error at 9^5
error at 10^3
error at 10^6
error at 10^9
error at 11^7
error at 12^7
...
エラーを完全に取り除くには、1e-11と1e-14の間にあるイプシロンを追加する必要がありました。テストする前にこれを教えてもらえますか?絶対にできませんでした。
これは、この計算に使用する関数です。
public static int binlog( int bits ) // returns 0 for bits=0
{
int log = 0;
if( ( bits & 0xffff0000 ) != 0 ) { bits >>>= 16; log = 16; }
if( bits >= 256 ) { bits >>>= 8; log += 8; }
if( bits >= 16 ) { bits >>>= 4; log += 4; }
if( bits >= 4 ) { bits >>>= 2; log += 2; }
return log + ( bits >>> 1 );
}
Integer.numberOfLeadingZeros()(20-30%)よりわずかに速く、このようなMath.log()ベースの実装よりもほぼ10倍(jdk 1.6 x64)高速です:
private static final double log2div = 1.000000000001 / Math.log( 2 );
public static int log2fp0( int bits )
{
if( bits == 0 )
return 0; // or throw exception
return (int) ( Math.log( bits & 0xffffffffL ) * log2div );
}
両方の関数は、可能なすべての入力値に対して同じ結果を返します。
更新: Java 1.7サーバーJITは、いくつかの静的な数学関数をCPU組み込み関数に基づく代替実装に置き換えることができます。これらの関数の1つはInteger.numberOfLeadingZeros()です。したがって、1.7以降のサーバーVMでは、問題のような実装は実際には上記のbinlog
よりもわずかに高速です。残念ながら、クライアントJITにはこの最適化がないようです。
public static int log2nlz( int bits )
{
if( bits == 0 )
return 0; // or throw exception
return 31 - Integer.numberOfLeadingZeros( bits );
}
また、この実装は、上で投稿した他の2つの実装と同じ2 ^ 32の入力値すべてに対して同じ結果を返します。
PC(Sandy Bridge i7)での実際のランタイムは次のとおりです。
JDK 1.7 32ビットクライアントVM:
binlog: 11.5s
log2nlz: 16.5s
log2fp: 118.1s
log(x)/log(2): 165.0s
JDK 1.7 x64サーバーVM:
binlog: 5.8s
log2nlz: 5.1s
log2fp: 89.5s
log(x)/log(2): 108.1s
これはテストコードです。
int sum = 0, x = 0;
long time = System.nanoTime();
do sum += log2nlz( x ); while( ++x != 0 );
time = System.nanoTime() - time;
System.out.println( "time=" + time / 1000000L / 1000.0 + "s -> " + sum );
Math.log(x) / Math.log(2)
を試してください
あなたはアイデンティティを使用できます
log[a]x
log[b]x = ---------
log[a]b
したがって、これはlog2に適用されます。
log[10]x
log[2]x = ----------
log[10]2
これをJava Math log10メソッドにプラグインするだけです。
何故なの:
public static double log2(int n)
{
return (Math.log(n) / Math.log(2));
}
Guavaライブラリには次の機能があります。
LongMath.log2()
だから私はそれを使用することをお勧めします。
Math.log10を使用すると、いくつかのケースでうまくいきました。
public static double log2(int n)
{
return (Math.log10(n) / Math.log10(2));
}
数値のバイナリログの下限を与えるx4uの答えに追加するには、この関数は数値のバイナリログの上限を返します。
public static int ceilbinlog(int number) // returns 0 for bits=0
{
int log = 0;
int bits = number;
if ((bits & 0xffff0000) != 0) {
bits >>>= 16;
log = 16;
}
if (bits >= 256) {
bits >>>= 8;
log += 8;
}
if (bits >= 16) {
bits >>>= 4;
log += 4;
}
if (bits >= 4) {
bits >>>= 2;
log += 2;
}
if (1 << log < number)
log++;
return log + (bits >>> 1);
}
追加しましょう:
int[] fastLogs;
private void populateFastLogs(int length) {
fastLogs = new int[length + 1];
int counter = 0;
int log = 0;
int num = 1;
fastLogs[0] = 0;
for (int i = 1; i < fastLogs.length; i++) {
counter++;
fastLogs[i] = log;
if (counter == num) {
log++;
num *= 2;
counter = 0;
}
}
}
ソース: https://github.com/pochuan/cs166/blob/master/ps1/rmq/SparseTableRMQ.Java