Comment calculez-vous la base de log 2 en Java pour les entiers?


J'utilise la fonction suivante pour calculer la base log 2 pour les entiers:

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

A-t-il des performances optimales?

Quelqu'un connaît-il la fonction API J2SE prête à cet effet?

UPD1 Étonnamment pour moi, l'arithmétique des points flottants semble être plus rapide que l'arithmétique des entiers.

UPD2 En raison des commentaires, je vais mener une enquête plus détaillée.

UPD3 Ma fonction arithmétique entière est 10 fois plus rapide que les Mathématiques.log (n) / Math.log (2).

Author: qben, 2010-07-22

9 answers

Si vous envisagez d'utiliser des virgule flottante pour aider à l'arithmétique entière, vous devez être prudent.

J'essaie généralement d'éviter les calculs FP autant que possible.

Les opérations en virgule flottante ne sont pas exactes. Vous ne pouvez jamais savoir avec certitude à quoi (int)(Math.log(65536)/Math.log(2)) évaluera. Par exemple, Math.ceil(Math.log(1<<29) / Math.log(2)) est 30 sur mon PC où mathématiquement il devrait être exactement 29. Je n'ai pas trouvé de valeur pour x où (int)(Math.log(x)/Math.log(2)) échoue (juste parce qu'il n'y a que 32 valeurs" dangereuses"), mais cela ne signifie pas qu'il fonctionnera de la même manière sur n'importe quel PC.

L'astuce habituelle ici est d'utiliser "epsilon" lors de l'arrondissement. Comme (int)(Math.log(x)/Math.log(2)+1e-10) ne devrait jamais échouer. Le choix de cet" epsilon " n'est pas une tâche triviale.

Plus de démonstration, en utilisant une tâche plus générale-essayer d'implémenter int log(int x, int base):

Le code de test:

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

Si nous utilisons l'implémentation la plus simple du logarithme,

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

Ceci imprime:

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

Pour me débarrasser complètement des erreurs, j'ai dû ajouter epsilon qui est entre 1e - 11 et 1e-14. Auriez-vous pu le dire avant de tester? Je ne pouvais certainement pas.

 62
Author: Rotsor, 2010-07-22 03:22:54

C'est la fonction que j'utilise pour ce calcul:

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

Il est légèrement plus rapide que Entier.numberOfLeadingZeros () (20-30%) et presque 10 fois plus rapide (jdk 1.6 x64) qu'un Math.implémentation basée sur log() comme celle-ci:

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

Les Deux fonctions renvoient les mêmes résultats pour toutes les valeurs d'entrée possibles.

Mise à Jour: Le serveur Java 1.7 JIT est capable de remplacer quelques fonctions mathématiques statiques par des implémentations alternatives basées sur des intrinsèques CPU. L'un des ces fonctions est de type Entier.numberOfLeadingZeros (). Donc, avec une machine virtuelle de serveur 1.7 ou plus récente, une implémentation comme celle de la question est en fait légèrement plus rapide que binlog ci-dessus. Malheureusement, le client JIT ne semble pas avoir cette optimisation.

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

Cette implémentation renvoie également les mêmes résultats pour toutes les 2^32 valeurs d'entrée possibles que les deux autres implémentations que j'ai postées ci-dessus.

Voici les temps d'exécution réels sur mon PC (Sandy Bridge i7):

JDK 1.7 Machine virtuelle client 32 bits:

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

J'ai besoin d'une machine virtuelle de serveur JDK 1.7 x64:

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

Ceci est le code de test:

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 );
 77
Author: x4u, 2013-11-18 11:41:46

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

 29
Author: Chris B., 2010-07-22 01:09:43

Vous pouvez utiliser l'identité

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

Cela serait donc applicable pour log2.

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

Il suffit de brancher ceci dans la méthode java Math log10....

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

 25
Author: hvgotcodes, 2010-07-22 01:11:46

Pourquoi pas:

public static double log2(int n)
{
    return (Math.log(n) / Math.log(2));
}
 16
Author: TofuBeer, 2010-07-22 01:09:28

Il y a la fonction dans les bibliothèques de goyave:

LongMath.log2()

Donc je suggère de l'utiliser.

 8
Author: Demetr, 2015-04-24 21:47:13

Pour ajouter à la réponse x4u, qui vous donne le plancher du journal binaire d'un nombre, cette fonction renvoie le plafond du journal binaire d'un nombre :

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);
}
 3
Author: Ofek Ron, 2016-09-04 08:47:57

Ajoutons:

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

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

 0
Author: Guido Celada, 2014-09-23 20:57:24

Pour calculer la base log 2 de n, l'expression suivante peut être utilisée:

double res = log10(n)/log10(2);
 -2
Author: Akanksha, 2018-07-26 05:17:10