Tamsayılar için Java'daki günlük tabanı 2'yi nasıl hesaplarsınız?


138

Ben tamsayılar için günlük tabanı 2 hesaplamak için aşağıdaki işlevi kullanın:

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

En iyi performansa sahip mi?

Birisi bu amaç için hazır J2SE API işlevini biliyor mu?

UPD1 Şaşırtıcı bir şekilde, kayan nokta aritmetiği tam sayı aritmetiğinden daha hızlı görünüyor.

UPD2 Yorumlar nedeniyle daha ayrıntılı inceleme yapacağım.

UPD3 Tamsayı aritmetik işlevim Math.log (n) /Math.log (2) 'den 10 kat daha hızlı.


1
Bunun performansını nasıl test ettiniz? Sistemimde (Core i7, jdk 1.6 x64) tamsayı sürümü kayan nokta sürümünden neredeyse 10 kat daha hızlıdır. Aslında işlevin sonucu ile bir şeyler yaptığınızdan emin olun, böylece JIT hesaplamayı tamamen kaldıramaz!
x4u

Haklısın. Hesaplama sonuçlarını kullanmadım ve derleyici bir şeyi optimize etti. Şimdi seninle aynı sonuca sahibim - tamsayı işlevi 10 kat daha hızlı (Core 2 Duo, jdk 1.6 c64)
Nulldevice

6
Bu etkili bir şekilde verir Math.floor(Math.log(n)/Math.log(2)), bu yüzden gerçekten günlük tabanı 2 hesaplamak değil!
Dori

Yanıtlar:


74

Tamsayı aritmetikleri için kayan nokta kullanmayı düşünüyorsanız, dikkatli olmalısınız.

Genellikle mümkün olduğunda FP hesaplamalarından kaçınmaya çalışırım.

Kayan nokta işlemleri kesin değildir. Neyi (int)(Math.log(65536)/Math.log(2))değerlendireceğinden emin olamazsınız . Örneğin, Math.ceil(Math.log(1<<29) / Math.log(2))bilgisayarımda 30 tam olarak 29 olması gereken bir yerdir. (int)(Math.log(x)/Math.log(2))Başarısız olduğu yerde x için bir değer bulamadım (sadece 32 "tehlikeli" değer olduğu için), ancak bunun işe yarayacağı anlamına gelmez. herhangi bir bilgisayarda aynı şekilde.

Buradaki olağan numara, yuvarlama sırasında "epsilon" kullanıyor. Gibi (int)(Math.log(x)/Math.log(2)+1e-10)başarısız asla. Bu "epsilon" un seçimi önemsiz bir görev değildir.

Daha genel bir görev kullanarak daha fazla gösteri - uygulamayı denemek int log(int x, int base):

Test kodu:

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

Logaritmanın en basit uygulamasını kullanırsak,

static int log(int x, int base)
{
    return (int) (Math.log(x) / Math.log(base));
}

bu yazdırır:

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
...

Tamamen hatalardan kurtulmak için 1e-11 ve 1e-14 arasında epsilon eklemek zorunda kaldım. Testten önce bunu söyleyebilir misiniz? Kesinlikle yapamadım.


3
"Bu herhangi bir PC'de aynı şekilde çalışacağı anlamına gelmez" - Kullansaydınız strictfp, değil mi?
Ken

@Ken: Belki ... Ama ancak tüm olası giriş değerlerini kapsamlı bir şekilde numaralandırdıktan sonra emin olabilirsiniz. (burada çok az şanslıyız)
Rotsor

2
Teknik olarak, evet, ama bu herhangi bir işlev için geçerlidir. Bir noktada, mevcut belgeleri kullanırsanız ve "tüm olası giriş değerleri" nin iyi seçilmiş ama kaybolan küçük bir kısmını test ederseniz, programınızın yeterince iyi çalışacağına güvenmeniz gerekir. strictfpaslında katı olduğu için çok fazla saçmalık var gibi görünüyor. :-)
Ken

return ((long)Math.log(x) / (long)Math.log(base));tüm hataları çözmeye ne dersiniz ?
Hata değil

92

Bu hesaplama için kullandığım işlev budur:

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) 'den biraz daha hızlı ve bunun gibi Math.log () tabanlı bir uygulamadan neredeyse 10 kat daha hızlıdır (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 );
}

Her iki işlev de tüm olası giriş değerleri için aynı sonuçları döndürür.

Güncelleme: Java 1.7 sunucusu JIT, birkaç statik matematik işlevini CPU iç yapısına dayalı alternatif uygulamalarla değiştirebilir. Bu işlevlerden biri Integer.numberOfLeadingZeros () işlevidir. 1.7 veya daha yeni bir sunucu VM'siyle, söz konusu olan gibi bir uygulama aslında binlogyukarıdakinden biraz daha hızlıdır . Ne yazık ki, istemci JIT bu optimizasyona sahip görünmüyor.

public static int log2nlz( int bits )
{
    if( bits == 0 )
        return 0; // or throw exception
    return 31 - Integer.numberOfLeadingZeros( bits );
}

Bu uygulama ayrıca, yukarıda gönderdiğim diğer iki uygulama ile aynı 2 ^ 32 olası giriş değeri için aynı sonuçları döndürür.

İşte bilgisayarımdaki gerçek çalışma zamanları (Sandy Bridge i7):

JDK 1.7 32 Bit istemci VM:

binlog:         11.5s
log2nlz:        16.5s
log2fp:        118.1s
log(x)/log(2): 165.0s

JDK 1.7 x64 sunucu VM'si:

binlog:          5.8s
log2nlz:         5.1s
log2fp:         89.5s
log(x)/log(2): 108.1s

Bu test kodudur:

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'nın BSRtalimatı bunu yapar 32 - numberOfLeadingZeros, ancak 0 için tanımlanmamıştır, bu nedenle (JIT) derleyicisi, zorunlu olmadığını kanıtlayamıyorsa sıfırdan farklı olup olmadığını kontrol etmelidir. BMI komut seti uzantıları (Haswell ve daha yenisi) tek bir komutta LZCNTtam olarak uygulayan tanıtıldı numberOfLeadingZeros. Her ikisi de 3 döngü gecikme süresi, döngü başına 1 işlem hacmi. Bu yüzden kesinlikle kullanmanızı tavsiye ederim numberOfLeadingZeros, çünkü bu iyi bir JVM için kolaylaştırır. (Garip olan tek şey lzcnt, üzerine
Peter Cordes

Java 1.7 Server JIT CPU intrinsics replacements hakkındaki yorumunuzla ilgileniyorum. Referans URL'niz var mı? (JIT Kaynak kodu bağlantısı da iyi.)
kevinarpe

37

Deneyin Math.log(x) / Math.log(2)


8
Matematiksel olarak bu doğru olsa da, lütfen Rotsor'un cevabında açıklandığı gibi, kesin olmayan kayan nokta aritmetiği nedeniyle yanlış hesaplama riski bulunduğunu unutmayın.
leeyuiwah

28

kimliğini kullanabilirsin

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

bu yüzden bu log2 için geçerlidir.

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

Sadece bu java Math log10 yöntemine takın ....

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


3
Matematiksel olarak bu doğru olsa da, lütfen Rotsor'un cevabında açıklandığı gibi, kesin olmayan kayan nokta aritmetiği nedeniyle yanlış hesaplama riski bulunduğunu unutmayın.
leeyuiwah

18

Neden olmasın:

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

6
Matematiksel olarak bu doğru olsa da, lütfen Rotsor'un cevabında açıklandığı gibi, kesin olmayan kayan nokta aritmetiği nedeniyle yanlış hesaplama riski bulunduğunu unutmayın.
leeyuiwah

9

Guava kütüphanelerinde fonksiyon vardır:

LongMath.log2()

Bu yüzden kullanmanızı öneririm.


Bu paketi uygulamama nasıl ekleyebilirim?
Elvin Mammadov

Kavanozu buradan indirin ve projenizin oluşturma yoluna ekleyin.
Şubat'ta Debosmit Ray 6

2
Yalnızca bir işlevi kullanmak için uygulamama bir kütüphane eklemeli miyim?
Tash Pemhiwa

7
Neden tam olarak kullanılmasını öneriyorsunuz? Guava kaynağının hızlı bir şekilde okunması, OP'nin yöntemiyle aynı şeyi yaptığını (çok açık bir şekilde anlaşılan birkaç kod satırı), aksi takdirde işe yaramaz bir bağımlılık eklemek pahasına gösteriyor. Google'ın bir şey sağlaması, sorunu anlamanız ve kendiniz çözmekten daha iyi yapmaz.
Dave

3

Size bir sayının ikili günlüğünün tabanını veren x4u yanıtına eklemek için, bu işlev bir sayının ikili günlüğünün tavanını döndürür:

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

"Sayı" değişkeni nerede?
barteks2x

3

Math.log10 kullandığımda bazı durumlar işe yaradı:

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

0

ekleyelim:

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

Kaynak: https://github.com/pochuan/cs166/blob/master/ps1/rmq/SparseTableRMQ.java


Bu bir arama tablosu oluşturmak olurdu. OP bir logaritmayı "hesaplamak" için daha hızlı bir yol istedi.
Dave

-4

N'nin günlük tabanı 2'yi hesaplamak için aşağıdaki ifade kullanılabilir:

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

2
Bu yanıt zaten birkaç kez gönderildi ve yuvarlama hatası nedeniyle potansiyel olarak yanlış olduğu fark edildi. OP'nin integral değerini istediğine dikkat edin; buradan bir tamsayıya ulaşmak için hangi yuvarlama hassasiyetinin kullanılması gerektiği açık değildir.
AnotherParker
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.