整数のJavaで対数の底2をどのように計算しますか?


138

次の関数を使用して、整数の2を底とする対数を計算します。

public static int log2(int n){
    if(n <= 0) throw new IllegalArgumentException();
    return 31 - Integer.numberOfLeadingZeros(n);
}

最適なパフォーマンスはありますか?

誰かがその目的のために準備ができているJ2SE API関数を知っていますか?

UPD1 驚いたことに、浮動小数点演算は整数演算よりも高速に見えます。

UPD2 コメントのため、より詳細な調査を行います。

UPD3 私の整数演算関数は、Math.log(n)/Math.log(2)よりも10倍高速です。


1
これのパフォーマンスをどのようにテストしましたか?私のシステム(Core i7、jdk 1.6 x64)では、整数バージョンは浮動小数点バージョンよりもほぼ10倍高速です。JITが計算を完全に削除できないように、関数の結果で実際に何かを行うようにしてください!
x4u 2010

あなたは正しいです。計算結果を使用せず、コンパイラは何かを最適化しました。これで私はあなたと同じ結果になります-整数関数は10倍高速です(Core 2 Duo、jdk 1.6 c64)
Nulldevice

6
これは実質的にを与えるMath.floor(Math.log(n)/Math.log(2))ので、実際には対数の底2を計算しません。
ドリ

回答:


74

整数演算に役立つ浮動小数点の使用を考えている場合は、注意が必要です。

私は通常、可能な限りいつでもFP計算を避けようとします。

浮動小数点演算は正確ではありません。あなたは何が(int)(Math.log(65536)/Math.log(2))評価されるかを確実に知ることはできません。たとえば、Math.ceil(Math.log(1<<29) / Math.log(2))私のPCでは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の間のイプシロンを追加する必要がありました。テストする前にこれを教えてくれませんか?私は絶対にできませんでした。


3
「これは、どのPCでも同じように機能することを意味するものではありません」-使用した場合、そうでしょうかstrictfp
ケン

@ケン:たぶん...しかし、すべての可能な入力値を徹底的に列挙した後でないと確信が持てません。(幸運なことに、ここにそれらがほとんどいない)
Rotsor 2010

2
技術的にはそうですが、どの関数にも当てはまります。ある時点で、利用可能なドキュメンテーションを使用して、「すべての可能な入力値」の適切に選択されたもののほとんどない部分をテストする場合、プログラムが十分に機能することを信頼する必要があります。 strictfp実際、厳格であるために多くのがらくたを実際に持っているようです。:-)
Ken

return ((long)Math.log(x) / (long)Math.log(base));すべてのエラーを解決するにはどうすればよいですか?
バグではない

92

これは、この計算に使用する関数です。

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 );

9
x86のBSR命令はを実行しますが32 - numberOfLeadingZeros、0については未定義であるため、(JIT)コンパイラは、その必要がないことを証明できない場合、ゼロ以外をチェックする必要があります。BMI命令セット拡張機能(Haswell以降)が導入されましたLZCNT。これnumberOfLeadingZerosは、1つの命令で正確に完全に実装されます。どちらも3サイクルのレイテンシであり、1サイクルあたり1スループットです。したがってnumberOfLeadingZeros、優れたJVMを簡単に使用できるので、の使用を絶対にお勧めします。(奇妙なことの1つlzcntは、上書きするレジスタの古い値に誤って依存していることです。)
Peter Cordes

Java 1.7サーバーのJIT CPU組み込み関数の置き換えについてのコメントに最も興味があります。参照URLはありますか?(JITソースコードリンクも問題あり
ません

37

試す Math.log(x) / Math.log(2)


8
数学的にはこれは正しいですが、Rotsorの回答で説明されているように、不正確な浮動小数点演算が原因で誤計算のリスクがあることに注意してください。
leeyuiwah 2017年

28

あなたはアイデンティティを使うことができます

            log[a]x
 log[b]x = ---------
            log[a]b

したがって、これはlog2に適用されます。

            log[10]x
 log[2]x = ----------
            log[10]2

これをjava Math log10メソッドに接続するだけです。

http://mathforum.org/library/drmath/view/55565.html


3
数学的にはこれは正しいですが、Rotsorの回答で説明されているように、不正確な浮動小数点演算が原因で誤計算のリスクがあることに注意してください。
leeyuiwah 2017年

18

何故なの:

public static double log2(int n)
{
    return (Math.log(n) / Math.log(2));
}

6
数学的にはこれは正しいですが、Rotsorの回答で説明されているように、不正確な浮動小数点演算が原因で誤計算のリスクがあることに注意してください。
leeyuiwah

9

guavaライブラリには関数があります:

LongMath.log2()

だから私はそれを使うことを勧めます。


このパッケージをアプリケーションに追加するにはどうすればよいですか?
Elvin Mammadov、2015年

ここからjarをダウンロードし、プロジェクトのビルドパスに追加します。
Debosmit Ray 2016

2
1つの関数を使用するためだけにアプリケーションにライブラリを追加する必要がありますか?
Tash Pemhiwa 16年

7
なぜそれを使うことを正確に提案するのですか?Guavaのソースをざっと読むと、OPの方法(非常に明確に理解されたいくつかのコード行)と同じことを行いますが、それ以外では役に立たない依存関係を追加します。グーグルが何かを提供するからといって、問題と解決策を自分で理解することほど良くはありません。
デイブ

3

数値のバイナリログの下限を与えるx4u回答に追加するには、この関数は数値のバイナリログのceilを返します。

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);
}

「数値」変数はどこにありますか?
barteks2x

3

Math.log10を使用したとき、いくつかのケースはうまくいきました:

public static double log2(int n)
{
    return (Math.log10(n) / Math.log10(2));
}

0

追加しましょう:

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


それはルックアップテーブルを作成することになります。OPは、対数を「計算」するより速い方法を求めていました。
Dave

-4

nの対数2を計算するには、次の式を使用できます。

double res = log10(n)/log10(2);

2
この回答はすでに何度か投稿されており、丸め誤差のために不正確である可能性があることがすでに確認されています。OPが積分値を要求したことに注意してください。ここから整数に到達するためにどの丸め精度を使用する必要があるかは、まったく明確ではありません。
AnotherParker
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.