Dans les bibliothèques standard C++, je n'ai trouvé qu'une méthode de journalisation à virgule flottante. Maintenant, j'utilise log pour trouver le niveau d'un index dans une arborescence binaire (floor(2log(index))
).
Code (C++):
int targetlevel = int(log(index)/log(2));
Je crains que pour certains éléments Edge (les éléments de valeur 2 ^ n), log renvoie n-1.999999999999 au lieu de n.0. Cette peur est-elle correcte? Comment puis-je modifier ma déclaration afin qu'elle renvoie toujours une réponse correcte?
Vous pouvez utiliser cette méthode à la place:
int targetlevel = 0;
while (index >>= 1) ++targetlevel;
Note: cela modifiera l'index. Si vous avez besoin de rien, créez un autre int temporaire.
La casse d'angle correspond à 0. Vous devriez probablement le vérifier séparément et lever une exception ou renvoyer une erreur si index == 0.
Si vous êtes sur une plate-forme x86 ou x86-64 récente (et vous l’êtes probablement), utilisez l’instruction bsr
qui renvoie la position du bit le plus élevé dans un entier non signé. Il s'avère que c'est exactement la même chose que log2 (). Voici une courte fonction C ou C++ qui appelle bsr
à l'aide d'ASM en ligne:
#include <stdint.h>
static inline uint32_t log2(const uint32_t x) {
uint32_t y;
asm ( "\tbsr %1, %0\n"
: "=r"(y)
: "r" (x)
);
return y;
}
Si vous voulez juste un journal entier rapide2 opération, la fonction mylog2()
suivante le fera sans se soucier de la précision en virgule flottante:
#include <limits.h>
static unsigned int mylog2 (unsigned int val) {
if (val == 0) return UINT_MAX;
if (val == 1) return 0;
unsigned int ret = 0;
while (val > 1) {
val >>= 1;
ret++;
}
return ret;
}
#include <stdio.h>
int main (void) {
for (unsigned int i = 0; i < 20; i++)
printf ("%u -> %u\n", i, mylog2(i));
putchar ('\n');
for (unsigned int i = 0; i < 10; i++)
printf ("%u -> %u\n", i+UINT_MAX-9, mylog2(i+UINT_MAX-9));
return 0;
}
Le code ci-dessus contient également un petit harnais de test afin que vous puissiez vérifier le comportement:
0 -> 4294967295
1 -> 0
2 -> 1
3 -> 1
4 -> 2
5 -> 2
6 -> 2
7 -> 2
8 -> 3
9 -> 3
10 -> 3
11 -> 3
12 -> 3
13 -> 3
14 -> 3
15 -> 3
16 -> 4
17 -> 4
18 -> 4
19 -> 4
4294967286 -> 31
4294967287 -> 31
4294967288 -> 31
4294967289 -> 31
4294967290 -> 31
4294967291 -> 31
4294967292 -> 31
4294967293 -> 31
4294967294 -> 31
4294967295 -> 31
Il renverra UINT_MAX
pour une valeur d'entrée égale à 0 indiquant un résultat non défini. Vous devez donc le vérifier (aucun entier non signé valide n'aura un logarithme aussi élevé).
À propos, il y a des bidouilles incroyablement rapides à faire exactement cela (trouver le bit le plus élevé défini dans un nombre de complément à 2) disponible à partir de ici . Je ne suggérerais pas de les utiliser à moins que la vitesse ne soit essentielle (je préfère la lisibilité), mais vous devriez être informé de leur existence.
Voici ce que je fais pour les entiers non signés 64 bits. Ceci calcule le plancher du logarithme en base 2, ce qui équivaut à l'indice du bit de poids fort. Cette méthode est très rapide pour les grands nombres car elle utilise une boucle déroulée qui s'exécute toujours dans log₂64 = 6 étapes.
Essentiellement, elle soustrait des carrés progressivement plus petits dans la suite {0 ≤ k ≤ 5: 2 ^ (2 ^ k)} = {2³², 2¹⁶, 2⁸, 2⁴, 2², 2¹} = {4294967296, 65536, 256 , 16, 4, 2, 1} et additionne les exposants k des valeurs soustraites.
int uint64_log2(uint64_t n)
{
#define S(k) if (n >= (UINT64_C(1) << k)) { i += k; n >>= k; }
int i = -(n == 0); S(32); S(16); S(8); S(4); S(2); S(1); return i;
#undef S
}
Notez que cela retourne –1 si l'entrée 0 invalide est donnée (ce que recherche la -(n == 0)
initiale). Si vous ne comptez jamais l'invoquer avec n == 0
, vous pouvez remplacer int i = 0;
par l'initialiseur et ajouter assert(n != 0);
à l'entrée de la fonction.
Les logarithmes de nombres entiers en base 10 peuvent être calculés de la même manière - le plus grand carré à tester étant 10¹⁶, car log₁₀2⁶⁴ 19,2659 ...
int uint64_log10(uint64_t n)
{
#define S(k, m) if (n >= UINT64_C(m)) { i += k; n /= UINT64_C(m); }
int i = -(n == 0);
S(16,10000000000000000); S(8,100000000); S(4,10000); S(2,100); S(1,10);
return i;
#undef S
}
Cela a été proposé dans les commentaires ci-dessus. Utilisation des commandes intégrées de gcc:
static inline int log2i(int x) {
assert(x > 0);
return sizeof(int) * 8 - __builtin_clz(x) - 1;
}
static void test_log2i(void) {
assert_se(log2i(1) == 0);
assert_se(log2i(2) == 1);
assert_se(log2i(3) == 1);
assert_se(log2i(4) == 2);
assert_se(log2i(32) == 5);
assert_se(log2i(33) == 5);
assert_se(log2i(63) == 5);
assert_se(log2i(INT_MAX) == sizeof(int)*8-2);
}
Je n’ai jamais eu de problème avec la précision en virgule flottante de la formule utilisée (et une vérification rapide des nombres de 1 à 231 - 1 n'a trouvé aucune erreur), mais si vous êtes inquiet, vous pouvez utiliser cette fonction à la place, qui renvoie les mêmes résultats et qui est environ 66% plus rapide dans mes tests:
int HighestBit(int i){
if(i == 0)
return -1;
int bit = 31;
if((i & 0xFFFFFF00) == 0){
i <<= 24;
bit = 7;
}else if((i & 0xFFFF0000) == 0){
i <<= 16;
bit = 15;
}else if((i & 0xFF000000) == 0){
i <<= 8;
bit = 23;
}
if((i & 0xF0000000) == 0){
i <<= 4;
bit -= 4;
}
while((i & 0x80000000) == 0){
i <<= 1;
bit--;
}
return bit;
}
int targetIndex = floor(log(i + 0.5)/log(2.0));
Ce n'est pas standard ou nécessairement portable, mais cela fonctionnera généralement. Je ne sais pas à quel point c'est efficace.
Convertissez l'index entier en un nombre à virgule flottante d'une précision suffisante. La représentation sera exacte, en supposant que la précision soit suffisante.
Recherchez la représentation des nombres à virgule flottante IEEE, extrayez l'exposant et apportez les modifications nécessaires pour trouver le journal de base 2.
Si vous utilisez C++ 11, vous pouvez en faire une fonction constexpr:
constexpr std::uint32_t log2(std::uint32_t n)
{
return (n > 1) ? 1 + log2(n >> 1) : 0;
}
Il y a des réponses similaires ci-dessus. Cette réponse
Les fonctions:
static int floorLog2(int64_t x)
{
assert(x > 0);
return 63 - __builtin_clzl(x);
}
static int ceilLog2(int64_t x)
{
if (x == 1)
// On my system __builtin_clzl(0) returns 63. 64 would make more sense
// and would be more consistent. According to stackoverflow this result
// can get even stranger and you should just avoid __builtin_clzl(0).
return 0;
else
return floorLog2(x-1) + 1;
}
Code de test:
for (int i = 1; i < 35; i++)
std::cout<<"floorLog2("<<i<<") = "<<floorLog2(i)
<<", ceilLog2("<<i<<") = "<<ceilLog2(i)<<std::endl;
Essentiellement déterminé quantifier les bits sont nécessaires pour représenter l'intervalle numérique: [0..maxvalue].
unsigned binary_depth( unsigned maxvalue )
{
int depth=0;
while ( maxvalue ) maxvalue>>=1, depth++;
return depth;
}
Dans les sous-ensembles, les résultats obtenus correspondent à floor(log2(x))
, que sont représentés de log2(x)
quando x
é u potência de 2.
xyy-1
-1
11
221
321
432
532
632
732
84
À quelle profondeur projetez-vous votre arbre? Vous pouvez définir une plage de say ... +/- 0.00000001 sur le nombre afin de le forcer à une valeur entière.
En fait, je ne suis pas certain que vous atteindrez un chiffre comme 1.99999999, car votre log2 ne devrait perdre aucune précision lors du calcul de 2 ^ n valeurs (car le nombre à virgule flottante est arrondi à la puissance 2 la plus proche.
Cette fonction j'ai écrit ici
// The 'i' is for int, there is a log2 for double in stdclib
inline unsigned int log2i( unsigned int x )
{
unsigned int log2Val = 0 ;
// Count Push off bits to right until 0
// 101 => 10 => 1 => 0
// which means hibit was 3rd bit, its value is 2^3
while( x>>=1 ) log2Val++; // div by 2 until find log2. log_2(63)=5.97, so
// take that as 5, (this is a traditional integer function!)
// eg x=63 (111111), log2Val=5 (last one isn't counted by the while loop)
return log2Val ;
}
Réécrire la réponse de Todd Lehman pour qu'elle soit plus générique:
#include <climits>
template<typename N>
constexpr N ilog2(N n) {
N i = 0;
for (N k = sizeof(N) * CHAR_BIT; 0 < (k /= 2);) {
if (n >= static_cast<N>(1) << k) { i += k; n >>= k; }
}
return i;
}
Clang avec -O3
déroule la boucle:
0000000100000f50 pushq %rbp
0000000100000f51 movq %rsp, %rbp
0000000100000f54 xorl %eax, %eax
0000000100000f56 cmpl $0xffff, %edi
0000000100000f5c setg %al
0000000100000f5f shll $0x4, %eax
0000000100000f62 movl %eax, %ecx
0000000100000f64 sarl %cl, %edi
0000000100000f66 xorl %edx, %edx
0000000100000f68 cmpl $0xff, %edi
0000000100000f6e setg %dl
0000000100000f71 leal (,%rdx,8), %ecx
0000000100000f78 sarl %cl, %edi
0000000100000f7a leal (%rax,%rdx,8), %eax
0000000100000f7d xorl %edx, %edx
0000000100000f7f cmpl $0xf, %edi
0000000100000f82 setg %dl
0000000100000f85 leal (,%rdx,4), %ecx
0000000100000f8c sarl %cl, %edi
0000000100000f8e leal (%rax,%rdx,4), %eax
0000000100000f91 xorl %edx, %edx
0000000100000f93 cmpl $0x3, %edi
0000000100000f96 setg %dl
0000000100000f99 leal (%rdx,%rdx), %ecx
0000000100000f9c sarl %cl, %edi
0000000100000f9e leal (%rax,%rdx,2), %ecx
0000000100000fa1 xorl %eax, %eax
0000000100000fa3 cmpl $0x1, %edi
0000000100000fa6 setg %al
0000000100000fa9 orl %ecx, %eax
0000000100000fab popq %rbp
Lorsque n
est constant, le résultat est calculé en temps de compilation.
Étant donné le fonctionnement des nombres en virgule flottante (grossièrement, mantisse * 2 ^ exposant), alors tout nombre inférieur à 2 ^ 127 représentant une puissance de 2 sera représenté exactement sans erreur.
Cela donne une solution triviale mais plutôt compliquée: interprétez le motif binaire du nombre à virgule flottante comme un entier et regardez simplement l'exposant. C'est la solution de David Thornley ci-dessus.
float f = 1;
for (int i = 0; i < 128; i++)
{
int x = (*(int*)(&f)>>23) - 127;
int l = int(log(f) / log(2));
printf("i = %d, log = %d, f = %f quick = %d\n",
i, l, f, x);
f *= 2;
}
Il n’est pas vrai que tout entier puisse être représenté par un nombre à virgule flottante - seuls ceux avec moins de bits que la mantisse peut en représenter. Dans les flotteurs 32 bits, cela vaut 23 bits.
Ceci est un ancien post mais je partage mon algorithme à une ligne:
unsigned uintlog2(unsigned x)
{
unsigned l;
for(l=0; x>1; x>>=1, l++);
return l;
}