Comment écrire la base de journal (2) en c / c ++

98

Existe-t-il un moyen d'écrire la fonction de journal (base 2)?

Le langage C a 2 fonctions intégrées - >>

1. logqui est la base e.

2. log10base 10;

Mais j'ai besoin de la fonction de journal de la base 2.Comment calculer cela.

Russell
la source
1
Pour les calculs du globe oculaire, le logarithme de base 2 est presque égal au logarithme de base 10 plus le logarithme naturel. De toute évidence, il est préférable d'écrire une version plus précise (et plus rapide) dans un programme.
David Thornley
Pour les entiers, vous pouvez effectuer une boucle sur le décalage de bits à droite et vous arrêter lorsque vous atteignez 0. Le nombre de boucles est une approximation du journal
Basile Starynkevitch

Réponses:

198

Mathématiques simples:

    log 2 ( x ) = log y ( x ) / log y (2)

y peut être n'importe quoi, ce qui pour les fonctions de journal standard est soit 10, soit e .

Adam Crume
la source
56

C99 a log2(ainsi que log2fet log2lpour float et long double).

Matthew Flaschen
la source
53

Si vous recherchez un résultat intégral, vous pouvez simplement déterminer le bit le plus élevé défini dans la valeur et renvoyer sa position.

tomlogic
la source
27
Il existe également une méthode intéressante pour cela (tirée de la Integer.highestOneBit(int)méthode de Java ):i |= (i >> 1); i |= (i >> 2); i |= (i >> 4); i |= (i >> 8); i |= (i >> 16); return i - (i >>> 1);
Joey
38
... ouwhile (i >>= 1) { ++l; }
Lee Daniel Crocker
2
@Joey Cela fonctionnerait en supposant que l'entier soit large de 32 bits, non? Pour 64 bits, il y aurait un supplément i>>32. Mais comme Java n'a que des entiers 32 bits, c'est bien. Pour C / C ++, il doit être pris en compte.
Zoso
43
#define M_LOG2E 1.44269504088896340736 // log2(e)

inline long double log2(const long double x){
    return log(x) * M_LOG2E;
}

(la multiplication peut être plus rapide que la division)

logicray
la source
1
Je voulais juste clarifier - en utilisant les règles de conversion du journal + le fait que log_2 (e) = 1 / log_e (2) -> nous obtenons le résultat
Guy L
16
log2(int n) = 31 - __builtin_clz(n)
w00t
la source
9

Comme indiqué sur http://en.wikipedia.org/wiki/Logarithm :

logb(x) = logk(x) / logk(b)

Ce qui signifie que:

log2(x) = log10(x) / log10(2)
Patrick
la source
11
Notez que vous pouvez précalculer log10 (2) pour augmenter les performances.
corsiKa
@Johannes: Je doute que le compilateur pré-calcule log10 (2). Le compilateur ne sait pas que log10 renverra la même valeur à chaque fois. Pour tout ce que le compilateur sait, log10 (2) pourrait renvoyer des valeurs différentes lors d'appels successifs.
abelenky
@abelenky: Ok, je reprends ça. Puisque le compilateur ne voit jamais la source de l' log()implémentation, il ne le fera pas. Ma faute.
Joey
3
@abelenky: Puisqu'une log10()fonction est définie dans le standard C, le compilateur est libre de la traiter "spécialement", y compris le précalcul du résultat, ce qui, je crois, était la suggestion de @Johannes?
caf
1
@CarlNorum: Je viens de vérifier, et gcc 4.7 au moins remplace log10(2)par une constante.
caf
8

Si vous voulez le rendre rapide, vous pouvez utiliser une table de recherche comme dans Bit Twiddling Hacks (entiers log2 uniquement).

uint32_t v; // find the log base 2 of 32-bit v
int r;      // result goes here

static const int MultiplyDeBruijnBitPosition[32] = 
{
  0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
  8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31
};

v |= v >> 1; // first round down to one less than a power of 2 
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;

r = MultiplyDeBruijnBitPosition[(uint32_t)(v * 0x07C4ACDDU) >> 27];

De plus, vous devriez jeter un œil aux méthodes intégrées de vos compilateurs, comme celle _BitScanReversequi pourrait être plus rapide car elle peut être entièrement calculée en matériel.

Jetez également un œil aux doublons possibles Comment faire un entier log2 () en C ++?

bkausbk
la source
Pourquoi la multiplication et la recherche de table à la fin? Ne pourriez-vous pas simplement faire (v + 1) qui arrondirait à la prochaine puissance de deux? Et puis, vous pouvez passer à droite par un pour arrondir à la puissance suivante de 2.
Safayet Ahmed
@SafayetAhmed Veuillez décrire comment vous voulez trouver le log2 d'un nombre avec cette méthode. Je ne connais pas de moyen plus simple d'obtenir cette valeur. En plus d'utiliser l'arithmétique ci-dessus avec la table de recherche, on peut utiliser un algorithme itératif / récursif ou utiliser du matériel dédié / intégré pour faire le calcul.
bkausbk
Supposons que les bits d'une variable de 32 bits v soient numérotés de 0 (LSB) à N (MSB). Supposons que le bit d'ensemble le plus significatif de v soit n. Serait-il correct de dire que n représente le plancher (log2 (v))? N'êtes-vous pas intéressé à trouver simplement n donné v?
Safayet Ahmed
J'ai réalisé que ce que je décrivais vous donnerait simplement la puissance la plus basse la plus proche de 2 et non le logarithme réel. La multiplication et la recherche de table permettent de passer de la puissance de deux au logarithme. Vous déplacez le nombre 0x07C4ACDD vers la gauche d'un certain montant. La quantité de décalage vers la gauche dépendra de la puissance de deux. Le nombre est tel que toute séquence consécutive de 5 bits est unique. (0000 0111 1100 0100 0110 1100 1101 1101) vous donne les séquences (00000) (00001) ... (11101). En fonction de votre décalage vers la gauche, vous obtenez l'un de ces modèles de 5 bits. Puis recherche de table. Très agréable.
Safayet Ahmed
3
log2(x) = log10(x) / log10(2)
le vide
la source
Voter pour la simplicité, la clarté et fournir un code basé sur les informations fournies par l'OP.
yoyo le
3
uint16_t log2(uint32_t n) {//but truncated
     if (n==0) throw ...
     uint16_t logValue = -1;
     while (n) {//
         logValue++;
         n >>= 1;
     }
     return logValue;
 }

Fondamentalement, le même que celui de tomlogic .

Ustaman Sangat
la source
1
Il y a quelques problèmes avec cette solution, mais en général, c'est bien si vous voulez éviter les virgules flottantes. Vous comptez sur le débordement pour que cela fonctionne puisque vous initialisez un entier non signé avec -1. Cela peut être résolu en l'initialisant à 0, puis en renvoyant la valeur - 1, en supposant que vous vérifiez le cas 0, ce que vous faites. L'autre problème est que vous comptez sur l'arrêt de la boucle lorsque n == 0, ce que vous devez indiquer explicitement. À part cela, c'est génial si vous voulez éviter les virgules flottantes.
Rian Quinn
2

Vous devez inclure math.h (C) ou cmath (C ++) Bien sûr, gardez à l'esprit que vous devez suivre les mathématiques que nous connaissons ... seuls les nombres> 0.

Exemple:

#include <iostream>
#include <cmath>
using namespace std;

int main(){
    cout<<log2(number);
}
user2143904
la source
2

J'avais besoin d'avoir plus de précision que la position du bit le plus significatif, et le microcontrôleur que j'utilisais n'avait pas de bibliothèque mathématique. J'ai trouvé que simplement utiliser une approximation linéaire entre 2 ^ n valeurs pour les arguments de valeur entière positive fonctionnait bien. Voici le code:

uint16_t approx_log_base_2_N_times_256(uint16_t n)
{
    uint16_t msb_only = 0x8000;
    uint16_t exp = 15;

    if (n == 0)
        return (-1);
    while ((n & msb_only) == 0) {
        msb_only >>= 1;
        exp--;
    }

    return (((uint16_t)((((uint32_t) (n ^ msb_only)) << 8) / msb_only)) | (exp << 8));
}

Dans mon programme principal, j'avais besoin de calculer N * log2 (N) / 2 avec un résultat entier:

temp = (((uint32_t) N) * approx_log_base_2_N_times_256) / 512;

et toutes les valeurs 16 bits n'ont jamais été décalées de plus de 2%

David Reinagel
la source
1

Toutes les réponses ci-dessus sont correctes. Cette réponse ci-dessous peut être utile si quelqu'un en a besoin. J'ai vu cette exigence dans de nombreuses questions que nous résolvons en utilisant C.

log2 (x) = logy (x) / logy (2)

Cependant, si vous utilisez le langage C et que vous voulez que le résultat soit un entier, vous pouvez utiliser ce qui suit:

int result = (int)(floor(log(x) / log(2))) + 1;

J'espère que cela t'aides.

Mazhar MIK
la source
0

Consultez vos cours de mathématiques de base, log n / log 2. Peu importe que vous choisissiez logou log10dans ce cas, diviser par logla nouvelle base fait l'affaire.

Pieter
la source
0

Version améliorée de ce qu'a fait Ustaman Sangat

static inline uint64_t
log2(uint64_t n)
{
    uint64_t val;
    for (val = 0; n > 1; val++, n >>= 1);

    return val;
}
Rian Quinn
la source