Qual é a maneira mais rápida / eficiente de encontrar o bit de conjunto mais alto (msb) em um inteiro em C?

119

Se eu tiver algum número inteiro n e quiser saber a posição do bit mais significativo (ou seja, se o bit menos significativo estiver à direita, quero saber a posição do bit mais à esquerda que é 1), qual é o método mais rápido / eficiente de descobrir?

Eu sei que POSIX oferece suporte a um ffs()método em strings.h para encontrar o primeiro conjunto de bits, mas não parece haver um fls()método correspondente .

Existe alguma maneira realmente óbvia de fazer isso que estou perdendo?

E nos casos em que você não pode usar funções POSIX para portabilidade?

Edit: Que tal uma solução que funciona em arquiteturas de 32 e 64 bits (muitas das listagens de código parecem que só funcionam em ints de 32 bits).

Zxaos
fonte
há algumas implementações aqui: graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightLinear (Edit: Depois de reler sua pergunta, percebi que o link acima é para encontrar o conjunto de bits mais à direita, não mais à esquerda como você deseja, embora sem uma noção do tamanho da palavra, é difícil de responder)
spender
Isso conta zeros à direita ; a pergunta era sobre zeros à esquerda. Pelo menos, em uma rápida olhada, não o vejo lá.
Darius Bacon,
2
você deseja especificamente o número de bits 'n' ou 2 ^ n seria suficiente?
Alnitak,
1
Observe os algoritmos "Log Base 2" - como diz Anderson no artigo: "O log base 2 de um inteiro é igual à posição do conjunto de bits mais alto (ou conjunto de bits mais significativo, MSB)"
Michael Burr

Respostas:

64

GCC tem :

 - Função interna: int __builtin_clz (unsigned int x)
     Retorna o número de bits 0 iniciais em X, começando no máximo
     posição significativa do bit. Se X for 0, o resultado é indefinido.

 - Função integrada: int __builtin_clzl (não assinado longo)
     Semelhante a `__builtin_clz ', exceto que o tipo de argumento é` sem sinal
     longo'.

 - Função integrada: int __builtin_clzll (unsigned long long)
     Semelhante a `__builtin_clz ', exceto que o tipo de argumento é` sem sinal
     longo longo'.

Eu esperaria que eles fossem traduzidos em algo razoavelmente eficiente para sua plataforma atual, seja um daqueles algoritmos sofisticados de bit-twiddling ou uma única instrução.


Um truque útil se a sua entrada pode ser zero é __builtin_clz(x | 1): incondicionalmente definindo o baixo bit sem modificar quaisquer outros faz com que a saída 31para x=0, sem alterar a saída para qualquer outra entrada.

Para evitar a necessidade de fazer isso, sua outra opção são intrínsecos específicos da plataforma, como ARM GCC __clz(nenhum cabeçalho necessário) ou x86 _lzcnt_u32em CPUs que suportam a lzcntinstrução. (Cuidado com isso lzcntdecodifica como bsrem CPUs mais antigas em vez de falhas, o que dá 31-lzcnt para entradas diferentes de zero.)

Infelizmente, não há como aproveitar as vantagens das várias instruções CLZ em plataformas não x86 que definem o resultado para input = 0 como 32 ou 64 (de acordo com a largura do operando). O x86 também lzcntfaz isso, enquanto bsrproduz um índice de bits que o compilador deve inverter a menos que você use 31-__builtin_clz(x).

(O "resultado indefinido" não é C Undefined Behavior, apenas um valor que não está definido. É na verdade tudo o que estava no registro de destino quando a instrução foi executada. AMD documenta isso, Intel não, mas CPUs da Intel implementam esse comportamento . Mas ele não o que estava anteriormente na variável C você está atribuindo a, isso não é geralmente como as coisas funcionam quando gcc transforma C em asm. Veja também por que quebrar a "saída de dependência" de LZCNT importa? )

efêmero
fonte
5
MSVC terá _BitScanReverse
ratchet freak
1
O comportamento indefinido em zero permite que eles compilem para uma única instrução BSR no x86, mesmo quando LZCNT não estiver disponível. Esta é uma grande vantagem para __builtin_ctzover ffs, que compila em um BSF e um CMOV para lidar com o caso de entrada era zero. Em arquiteturas sem uma implementação curta o suficiente (por exemplo, ARM antigo sem a clzinstrução), o gcc emite uma chamada para uma função auxiliar libgcc.
Peter Cordes
41

Supondo que você esteja no x86 e jogo para um pouco de montador embutido, a Intel fornece uma BSRinstrução ("varredura reversa de bits"). É rápido em alguns x86s (microcodificado em outros). Do manual:

Pesquisa o operando de origem para o bit definido mais significativo (1 bit). Se um bit 1 mais significativo for encontrado, seu índice de bit é armazenado no operando de destino. O operando de origem pode ser um registro ou um local de memória; o operando de destino é um registrador. O índice de bits é um deslocamento sem sinal do bit 0 do operando de origem. Se o operando fonte de conteúdo for 0, o conteúdo do operando destino é indefinido.

(Se você estiver no PowerPC, há uma cntlzinstrução semelhante ("contar zeros à esquerda").)

Código de exemplo para gcc:

#include <iostream>

int main (int,char**)
{
  int n=1;
  for (;;++n) {
    int msb;
    asm("bsrl %1,%0" : "=r"(msb) : "r"(n));
    std::cout << n << " : " << msb << std::endl;
  }
  return 0;
}

Veja também este tutorial de assembler embutido , que mostra (seção 9.4) que ele é consideravelmente mais rápido do que código em loop.

timday
fonte
4
Na verdade, essa instrução é geralmente microcodificada em um loop e é bastante lenta.
rlbond
2
Qual ? BSR ou CNTLZ? Como li o x86-timing.pdf mencionado acima, o BSR é lento apenas nos Pentiums Netburst. Não sei nada sobre PowerPC embora.
timday
5
... OK, em uma inspeção mais detalhada, verifique se "BSR é rápido apenas em P3 / Pentium-M / Core2 x86s". Lento no Netburst e AMD.
timday
1
Apenas um aviso: seus dois últimos links estão mortos.
Baum mit Augen
2
@rlbond: huh, o BSR em P4 Prescott é 2 uops com latência de 16 ciclos (!), com uma taxa de transferência por 4c. Mas no Netburst anterior, é apenas 4 ciclos de latência (ainda 2 uops) e um por 2c de taxa de transferência. (fonte: agner.org/optimize ). Na maioria das CPUs, ele também tem uma dependência de sua saída que o gcc não considera (quando a entrada é zero, o comportamento real é deixar o destino inalterado). Isso pode levar a problemas como stackoverflow.com/questions/25078285/… . IDK: por que o gcc perdeu o BSR ao consertar isso.
Peter Cordes
38

Como 2 ^ N é um número inteiro com apenas o enésimo bit definido (1 << N), encontrar a posição (N) do bit mais alto é o log de número inteiro de base 2 desse inteiro.

http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious

unsigned int v;
unsigned r = 0;

while (v >>= 1) {
    r++;
}

Este algoritmo "óbvio" pode não ser transparente para todos, mas quando você percebe que o código muda um bit repetidamente para a direita até que o bit mais à esquerda seja deslocado (observe que C trata qualquer valor diferente de zero como verdadeiro) e retorna o número de turnos, faz todo o sentido. Também significa que funciona mesmo quando mais de um bit é definido - o resultado é sempre para o bit mais significativo.

Se você rolar para baixo nessa página, verá variações mais rápidas e complexas. No entanto, se você sabe que está lidando com números com muitos zeros à esquerda, a abordagem ingênua pode fornecer uma velocidade aceitável, uma vez que o deslocamento de bits é bastante rápido em C e o algoritmo simples não requer a indexação de um array.

NOTA: Ao usar valores de 64 bits, seja extremamente cauteloso ao usar algoritmos muito inteligentes; muitos deles funcionam corretamente apenas para valores de 32 bits.

Quinn Taylor
fonte
2
@Johan Avançar com um depurador pode ajudar a explicar por que o loop termina. Basicamente, é porque a expressão na condição é avaliada como 0 (que é tratada como falsa) assim que o último 1 bit for deslocado para a direita.
Quinn Taylor
2
Boa ideia para usar o resultado final assim :)
Johan
6
nota: deve ser sem sinal, para inteiros com sinal, o deslocamento à direita falha para números negativos.
Xantix
2
Xantix: A mudança em C / C ++ é uma mudança lógica, então funciona bem. Para Java, JavaScript ou D, você precisa usar o operador de deslocamento lógico >>>. Além disso, provavelmente o comparador != 0e algum número não especificado de parênteses.
Chase
8
@Chase: Não, não é. É uma mudança lógica para os não assinados . Para assinado , pode ou não ser uma mudança lógica (e geralmente é aritmética, na verdade).
Tim Čas
17

Isso deve ser rápido:

int msb(unsigned int v) {
  static const int pos[32] = {0, 1, 28, 2, 29, 14, 24, 3,
    30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19,
    16, 7, 26, 12, 18, 6, 11, 5, 10, 9};
  v |= v >> 1;
  v |= v >> 2;
  v |= v >> 4;
  v |= v >> 8;
  v |= v >> 16;
  v = (v >> 1) + 1;
  return pos[(v * 0x077CB531UL) >> 27];
}
Protagonista
fonte
25
Deslocamentos de 7 bits, 5 ou instruções, uma multiplicação e uma falha potencial de cache. :) Você fez o benchmark ou olhou para o assembler gerado? Ele poderia acabar bastante lento, dependendo de como muito do que o compilador pode eliminar.
jalf
5
Eu sou novo aqui. Eu não recebo os votos negativos, rapazes. Eu forneci a única resposta com código-fonte que realmente funciona.
Protagonista de
9
A "possível perda de cache" provavelmente se deve a esse código que exige acesso à sua tabela de pesquisa. Se essa tabela não for armazenada em cache quando for chamada, haverá uma paralisação enquanto ela é buscada. Isso pode tornar o desempenho do pior caso muito pior do que as soluções que não usam um LUT.
descontrair
13
não é realmente o ponto. Ele usa muito mais cache de dados do que o necessário (até mais de uma linha de cache) e mais cache de instruções do que o necessário. Provavelmente, você terá perdas de cache que poderiam ter sido evitadas na primeira vez que você chamar a função, e isso poluirá o cache mais do que o necessário; portanto, após a chamada, outro código pode encontrar mais falhas do que o necessário. Frequentemente, os LUTs não valem o trabalho porque falhas de cache são caras. Mas eu apenas disse que era algo que gostaria de avaliar antes de afirmar que era "rápido como um raio". Não que seja definitivamente um problema.
jalf
6
A tabela tem 32 entradas, e cada valor é <255 (127), então defina a tabela como tipo unsigned char, e ela caberá em uma única linha de cache L1 de 32 bytes. E tudo se encaixa em duas linhas de cache.
ChuckCottrill
16

Isso é como encontrar um tipo de log de inteiro. Existem pequenos truques, mas fiz minha própria ferramenta para isso. O objetivo, é claro, é velocidade.

Minha constatação é que a CPU já tem um detector automático de bits, usado para conversão de inteiro para float! Então use isso.

double ff=(double)(v|1);
return ((*(1+(uint32_t *)&ff))>>20)-1023;  // assumes x86 endianness

Essa versão converte o valor em um duplo e, em seguida, lê o expoente, que informa onde o bit estava. A mudança e subtração extravagantes são extrair as partes adequadas do valor IEEE.

É um pouco mais rápido usar floats, mas um float só pode fornecer as primeiras posições de 24 bits por causa de sua precisão menor.


Para fazer isso com segurança, sem comportamento indefinido em C ++ ou C, use em memcpyvez de conversão de ponteiro para trocadilhos. Os compiladores sabem como embuti-lo de forma eficiente.

// static_assert(sizeof(double) == 2 * sizeof(uint32_t), "double isn't 8-byte IEEE binary64");
// and also static_assert something about FLT_ENDIAN?

double ff=(double)(v|1);

uint32_t tmp;
memcpy(&tmp, ((const char*)&ff)+sizeof(uint32_t), sizeof(uint32_t));
return (tmp>>20)-1023;

Ou em C99 e posterior, use a union {double d; uint32_t u[2];};. Mas note que em C ++, o tipo de união punning só é suportado em alguns compiladores como uma extensão, não em ISO C ++.


Isso geralmente será mais lento do que um intrínseco específico de plataforma para uma instrução de contagem de zeros à esquerda, mas o ISO C portátil não tem essa função. Algumas CPUs também carecem de uma instrução de contagem zero à esquerda, mas algumas delas podem converter números inteiros em com eficiência double. A conversão de um padrão de bit FP de volta para um inteiro pode ser lenta, porém (por exemplo, no PowerPC, isso requer um armazenamento / recarregamento e geralmente causa um bloqueio de carregamento, acerto e armazenamento).

Este algoritmo pode ser potencialmente útil para implementações SIMD, porque menos CPUs têm SIMD lzcnt. x86 só obteve tal instrução com AVX512CD

SPWorley
fonte
2
Sim. E o gcc fará coisas desagradáveis ​​com código como este com -O2 devido a otimizações de alias de tipo.
MSN
4
a conversão entre número inteiro e ponto flutuante pode ser surpreendentemente caro em CPU x86
jalf,
1
Sim, os custos da FPU são altos. Mas as medições de tempo reais mostraram que isso era mais rápido do que operações de todos os bits ou especialmente quaisquer loops. Experimente e leve o mais rápido é sempre o melhor conselho. Eu não tive problemas com GCC e -O2 com isso.
SPWorley
1
Não é um comportamento indefinido (ler um valor por meio de um ponteiro de um tipo incompatível)?
dreamlax
3
Hacker's Delight explica como corrigir o erro em flutuações de 32 bits em 5-3 Contando zeros à esquerda. Aqui está o código deles, que usa uma união anônima para sobrepor asFloat e asInt: k = k & ~ (k >> 1); asFloat = (float) k + 0,5f; n = 158 - (asInt >> 23); (e sim, isso depende do comportamento definido pela implementação)
D Coetzee
11

Kaz Kylheku aqui

Eu comparei duas abordagens para este número de mais de 63 bits (o tipo long long no gcc x86_64), ficando longe do bit de sinal.

(Acontece que preciso deste "encontrar o bit mais alto" para algo, você vê.)

Implementei a pesquisa binária baseada em dados (estritamente baseada em uma das respostas acima). Eu também implementei uma árvore de decisão completamente desenrolada manualmente, que é apenas um código com operandos imediatos. Sem loops, sem tabelas.

A árvore de decisão (higher_bit_unrolled) foi avaliada como 69% mais rápida, exceto para o caso n = 0 para o qual a pesquisa binária tem um teste explícito.

O teste especial da busca binária para 0 caso é apenas 48% mais rápido do que a árvore de decisão, que não tem um teste especial.

Compilador, máquina: (GCC 4.5.2, -O3, x86-64, 2867 Mhz Intel Core i5).

int highest_bit_unrolled(long long n)
{
  if (n & 0x7FFFFFFF00000000) {
    if (n & 0x7FFF000000000000) {
      if (n & 0x7F00000000000000) {
        if (n & 0x7000000000000000) {
          if (n & 0x4000000000000000)
            return 63;
          else
            return (n & 0x2000000000000000) ? 62 : 61;
        } else {
          if (n & 0x0C00000000000000)
            return (n & 0x0800000000000000) ? 60 : 59;
          else
            return (n & 0x0200000000000000) ? 58 : 57;
        }
      } else {
        if (n & 0x00F0000000000000) {
          if (n & 0x00C0000000000000)
            return (n & 0x0080000000000000) ? 56 : 55;
          else
            return (n & 0x0020000000000000) ? 54 : 53;
        } else {
          if (n & 0x000C000000000000)
            return (n & 0x0008000000000000) ? 52 : 51;
          else
            return (n & 0x0002000000000000) ? 50 : 49;
        }
      }
    } else {
      if (n & 0x0000FF0000000000) {
        if (n & 0x0000F00000000000) {
          if (n & 0x0000C00000000000)
            return (n & 0x0000800000000000) ? 48 : 47;
          else
            return (n & 0x0000200000000000) ? 46 : 45;
        } else {
          if (n & 0x00000C0000000000)
            return (n & 0x0000080000000000) ? 44 : 43;
          else
            return (n & 0x0000020000000000) ? 42 : 41;
        }
      } else {
        if (n & 0x000000F000000000) {
          if (n & 0x000000C000000000)
            return (n & 0x0000008000000000) ? 40 : 39;
          else
            return (n & 0x0000002000000000) ? 38 : 37;
        } else {
          if (n & 0x0000000C00000000)
            return (n & 0x0000000800000000) ? 36 : 35;
          else
            return (n & 0x0000000200000000) ? 34 : 33;
        }
      }
    }
  } else {
    if (n & 0x00000000FFFF0000) {
      if (n & 0x00000000FF000000) {
        if (n & 0x00000000F0000000) {
          if (n & 0x00000000C0000000)
            return (n & 0x0000000080000000) ? 32 : 31;
          else
            return (n & 0x0000000020000000) ? 30 : 29;
        } else {
          if (n & 0x000000000C000000)
            return (n & 0x0000000008000000) ? 28 : 27;
          else
            return (n & 0x0000000002000000) ? 26 : 25;
        }
      } else {
        if (n & 0x0000000000F00000) {
          if (n & 0x0000000000C00000)
            return (n & 0x0000000000800000) ? 24 : 23;
          else
            return (n & 0x0000000000200000) ? 22 : 21;
        } else {
          if (n & 0x00000000000C0000)
            return (n & 0x0000000000080000) ? 20 : 19;
          else
            return (n & 0x0000000000020000) ? 18 : 17;
        }
      }
    } else {
      if (n & 0x000000000000FF00) {
        if (n & 0x000000000000F000) {
          if (n & 0x000000000000C000)
            return (n & 0x0000000000008000) ? 16 : 15;
          else
            return (n & 0x0000000000002000) ? 14 : 13;
        } else {
          if (n & 0x0000000000000C00)
            return (n & 0x0000000000000800) ? 12 : 11;
          else
            return (n & 0x0000000000000200) ? 10 : 9;
        }
      } else {
        if (n & 0x00000000000000F0) {
          if (n & 0x00000000000000C0)
            return (n & 0x0000000000000080) ? 8 : 7;
          else
            return (n & 0x0000000000000020) ? 6 : 5;
        } else {
          if (n & 0x000000000000000C)
            return (n & 0x0000000000000008) ? 4 : 3;
          else
            return (n & 0x0000000000000002) ? 2 : (n ? 1 : 0);
        }
      }
    }
  }
}

int highest_bit(long long n)
{
  const long long mask[] = {
    0x000000007FFFFFFF,
    0x000000000000FFFF,
    0x00000000000000FF,
    0x000000000000000F,
    0x0000000000000003,
    0x0000000000000001
  };
  int hi = 64;
  int lo = 0;
  int i = 0;

  if (n == 0)
    return 0;

  for (i = 0; i < sizeof mask / sizeof mask[0]; i++) {
    int mi = lo + (hi - lo) / 2;

    if ((n >> mi) != 0)
      lo = mi;
    else if ((n & (mask[i] << lo)) != 0)
      hi = mi;
  }

  return lo + 1;
}

Programa de teste rápido e sujo:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int highest_bit_unrolled(long long n);
int highest_bit(long long n);

main(int argc, char **argv)
{
  long long n = strtoull(argv[1], NULL, 0);
  int b1, b2;
  long i;
  clock_t start = clock(), mid, end;

  for (i = 0; i < 1000000000; i++)
    b1 = highest_bit_unrolled(n);

  mid = clock();

  for (i = 0; i < 1000000000; i++)
    b2 = highest_bit(n);

  end = clock();

  printf("highest bit of 0x%llx/%lld = %d, %d\n", n, n, b1, b2);

  printf("time1 = %d\n", (int) (mid - start));
  printf("time2 = %d\n", (int) (end - mid));
  return 0;
}

Usando apenas -O2, a diferença se torna maior. A árvore de decisão é quase quatro vezes mais rápida.

Eu também comparei com o código ingênuo de mudança de bits:

int highest_bit_shift(long long n)
{
  int i = 0;
  for (; n; n >>= 1, i++)
    ; /* empty */
  return i;
}

Isso é rápido apenas para números pequenos, como seria de esperar. Ao determinar que o bit mais alto é 1 para n == 1, fez o benchmarking mais de 80% mais rápido. No entanto, metade dos números escolhidos aleatoriamente no espaço de 63 bits têm o conjunto de 63 bits!

Na entrada 0x3FFFFFFFFFFFFFFF, a versão da árvore de decisão é um pouco mais rápida do que em 1 e mostra ser 1120% mais rápida (12,2 vezes) do que o bit shifter.

Também vou comparar a árvore de decisão com os builtins do GCC e também tentar uma mistura de entradas em vez de repetir com o mesmo número. Pode haver alguma previsão de branch travado acontecendo e talvez alguns cenários de cache irrealistas que o tornam artificialmente mais rápido nas repetições.

Kaz
fonte
9
Não estou dizendo que isso não seja bom, mas seu programa de teste aqui testa apenas o mesmo número, que após 2-3 iterações terá definido os preditores de branch para sua posição final e, depois disso, farão previsões de branch perfeitas. O bom é que, com uma distribuição totalmente aleatória, metade dos números terá uma previsão próxima da perfeita, ou seja, bit63.
Surt
8

A respeito

int highest_bit(unsigned int a) {
    int count;
    std::frexp(a, &count);
    return count - 1;
}

?

Marco Amagliani
fonte
Esta é uma versão lenta (mas mais portátil) desta resposta , o que explica por que funciona.
Peter Cordes
6
unsigned int
msb32(register unsigned int x)
{
        x |= (x >> 1);
        x |= (x >> 2);
        x |= (x >> 4);
        x |= (x >> 8);
        x |= (x >> 16);
        return(x & ~(x >> 1));
}

1 registro, 13 instruções. Acredite ou não, isso geralmente é mais rápido do que a instrução BSR mencionada acima, que opera em tempo linear. Este é o tempo logarítmico.

De http://aggregate.org/MAGIC/#Most%20Significant%201%20Bit

rlbond
fonte
7
O código acima não responde à pergunta. Ele retorna um número inteiro não assinado onde o bit on mais significativo em x permanece ativado e todos os outros bits são desativados. A questão era devolver a posição do bit mais significativo.
Protagonista de
3
Você pode então usar uma abordagem de sequência De Bruijn para encontrar o índice do bit definido. :-)
R .. GitHub PARE DE AJUDAR O ICE
5
@Protagonista, ele disse em um comentário que qualquer uma das duas é suficiente.
rlbond de
Este (da mesma página) faria o que você precisa, mas requer uma função adicional. aggregate.org/MAGIC/#Log2%20of%20an%20Integer
Quinn Taylor
1
BSR é rápido em CPUs Intel, pelo menos desde o Core2. LZCNT é rápido em CPUs AMD, e o gcc o usa __builtin_clzse estiver habilitado com -march=nativeou algo assim (já que é rápido em todos os CPUs que o suportam). Mesmo em CPUs como a família AMD Bulldozer, onde o BSR é "lento", não é tão lento: 7 m-ops com latência de 4 ciclos e um por rendimento de 4c. No Atom, o BSR é muito lento: 16 ciclos. Em Silvermont, é 10 uops com latência de 10 ciclos. Isso pode ser uma latência um pouco menor do que BSR em Silvermont, mas IDK.
Peter Cordes
6

Aqui estão alguns benchmarks (simples) de algoritmos fornecidos atualmente nesta página ...

Os algoritmos não foram testados em todas as entradas de int sem sinal; então verifique isso primeiro, antes de usar algo às cegas;)

Na minha máquina, clz (__builtin_clz) e asm funcionam melhor. asm parece ainda mais rápido do que clz ... mas pode ser devido ao benchmark simples ...

//////// go.c ///////////////////////////////
// compile with:  gcc go.c -o go -lm
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/***************** math ********************/

#define POS_OF_HIGHESTBITmath(a) /* 0th position is the Least-Signif-Bit */    \
  ((unsigned) log2(a))         /* thus: do not use if a <= 0 */  

#define NUM_OF_HIGHESTBITmath(a) ((a)               \
                  ? (1U << POS_OF_HIGHESTBITmath(a))    \
                  : 0)



/***************** clz ********************/

unsigned NUM_BITS_U = ((sizeof(unsigned) << 3) - 1);
#define POS_OF_HIGHESTBITclz(a) (NUM_BITS_U - __builtin_clz(a)) /* only works for a != 0 */

#define NUM_OF_HIGHESTBITclz(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITclz(a))  \
                 : 0)


/***************** i2f ********************/

double FF;
#define POS_OF_HIGHESTBITi2f(a) (FF = (double)(ui|1), ((*(1+(unsigned*)&FF))>>20)-1023)


#define NUM_OF_HIGHESTBITi2f(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITi2f(a))  \
                 : 0)




/***************** asm ********************/

unsigned OUT;
#define POS_OF_HIGHESTBITasm(a) (({asm("bsrl %1,%0" : "=r"(OUT) : "r"(a));}), OUT)

#define NUM_OF_HIGHESTBITasm(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITasm(a))  \
                 : 0)




/***************** bitshift1 ********************/

#define NUM_OF_HIGHESTBITbitshift1(a) (({   \
  OUT = a;                  \
  OUT |= (OUT >> 1);                \
  OUT |= (OUT >> 2);                \
  OUT |= (OUT >> 4);                \
  OUT |= (OUT >> 8);                \
  OUT |= (OUT >> 16);               \
      }), (OUT & ~(OUT >> 1)))          \



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

#define POS_OF_HIGHESTBITbitshift2(a) (({   \
  OUT = a;                  \
  OUT |= OUT >> 1;              \
  OUT |= OUT >> 2;              \
  OUT |= OUT >> 4;              \
  OUT |= OUT >> 8;              \
  OUT |= OUT >> 16;             \
  OUT = (OUT >> 1) + 1;             \
      }), POS[(OUT * 0x077CB531UL) >> 27])

#define NUM_OF_HIGHESTBITbitshift2(a) ((a)              \
                       ? (1U << POS_OF_HIGHESTBITbitshift2(a)) \
                       : 0)



#define LOOPS 100000000U

int main()
{
  time_t start, end;
  unsigned ui;
  unsigned n;

  /********* Checking the first few unsigned values (you'll need to check all if you want to use an algorithm here) **************/
  printf("math\n");
  for (ui = 0U; ui < 18; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITmath(ui));

  printf("\n\n");

  printf("clz\n");
  for (ui = 0U; ui < 18U; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITclz(ui));

  printf("\n\n");

  printf("i2f\n");
  for (ui = 0U; ui < 18U; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITi2f(ui));

  printf("\n\n");

  printf("asm\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITasm(ui));
  }

  printf("\n\n");

  printf("bitshift1\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITbitshift1(ui));
  }

  printf("\n\n");

  printf("bitshift2\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITbitshift2(ui));
  }

  printf("\n\nPlease wait...\n\n");


  /************************* Simple clock() benchmark ******************/
  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITmath(ui);
  end = clock();
  printf("math:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITclz(ui);
  end = clock();
  printf("clz:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITi2f(ui);
  end = clock();
  printf("i2f:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITasm(ui);
  end = clock();
  printf("asm:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITbitshift1(ui);
  end = clock();
  printf("bitshift1:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITbitshift2(ui);
  end = clock();
  printf("bitshift2\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  printf("\nThe lower, the better. Take note that a negative exponent is good! ;)\n");

  return EXIT_SUCCESS;
}
Josh
fonte
6

Embora eu provavelmente só usasse esse método se absolutamente exigisse o melhor desempenho possível (por exemplo, para escrever algum tipo de IA de jogo de tabuleiro envolvendo quadros de bits), a solução mais eficiente é usar o ASM embutido. Consulte a seção Otimizações desta postagem do blog para obter o código com uma explicação.

[...], a bsrlinstrução de montagem calcula a posição do bit mais significativo. Assim, poderíamos usar esta asmdeclaração:

asm ("bsrl %1, %0" 
     : "=r" (position) 
     : "r" (number));
Noldorin
fonte
Para expandir: a solução de loop padrão (deslocando para a esquerda e verificando MSB) é provavelmente a mais legível. Como em todos os casos que envolvem bit twiddling, a velocidade do ASM não pode ser superada, embora não haja motivo para bagunçar seu código, a menos que seja necessário. Hacks são uma solução intermediária - vá para um lado ou para o outro.
Noldorin
Eu diria que pegar o logaritmo seria uma solução perfeitamente legível (verifique o conjunto gerado para ver se o compilador pode otimizá-lo para usar esta instrução asm)
jalf
Às vezes, a solução ASM em linha é mais lenta, dependendo da implementação no microcódigo da CPU.
rlbond
5
@rlbound: Mal posso acreditar nisso, embora possa estar enganado. Em qualquer CPU moderna, alguém pensaria que seria traduzido para uma única instrução ...
Noldorin,
3
@Noldorin é um pouco tarde, mas .. É por definição uma única instrução, mas se for microcodificado como rlbond sugere, então essa única instrução poderia decodificar para um monte de µops internamente. Isso tende a ser o caso nas microarquiteturas da AMD e Intel Atom, mas nas microarquiteturas Intel normais é uma única operação até o fim.
Harold
4

Eu precisava de uma rotina para fazer isso e antes de pesquisar na web (e encontrar esta página), criei minha própria solução baseada em uma pesquisa binária. Embora eu tenha certeza de que alguém já fez isso antes! Ele roda em tempo constante e pode ser mais rápido do que a solução "óbvia" postada, embora eu não esteja fazendo grandes afirmações, apenas postando por interesse.

int highest_bit(unsigned int a) {
  static const unsigned int maskv[] = { 0xffff, 0xff, 0xf, 0x3, 0x1 };
  const unsigned int *mask = maskv;
  int l, h;

  if (a == 0) return -1;

  l = 0;
  h = 32;

  do {
    int m = l + (h - l) / 2;

    if ((a >> m) != 0) l = m;
    else if ((a & (*mask << l)) != 0) h = m;

    mask++;
  } while (l < h - 1);

  return l;
}
rato perigoso
fonte
4

isso é algum tipo de pesquisa binária, funciona com todos os tipos de inteiros (sem sinal!)

#include <climits>
#define UINT (unsigned int)
#define UINT_BIT (CHAR_BIT*sizeof(UINT))

int msb(UINT x)
{
    if(0 == x)
        return -1;

    int c = 0;

    for(UINT i=UINT_BIT>>1; 0<i; i>>=1)
    if(static_cast<UINT>(x >> i))
    {
        x >>= i;
        c |= i;
    }

    return c;
}

para completar:

#include <climits>
#define UINT unsigned int
#define UINT_BIT (CHAR_BIT*sizeof(UINT))

int lsb(UINT x)
{
    if(0 == x)
        return -1;

    int c = UINT_BIT-1;

    for(UINT i=UINT_BIT>>1; 0<i; i>>=1)
    if(static_cast<UINT>(x << i))
    {
        x <<= i;
        c ^= i;
    }

    return c;
}

fonte
4
Considere não usar ALL_CAPS para typedefs ou qualquer coisa exceto macros de pré-processador. Esta é uma convenção amplamente aceita.
underscore_d
4

Algumas respostas excessivamente complexas aqui. A técnica Debruin só deve ser usada quando a entrada já é uma potência de dois, caso contrário, há uma maneira melhor. Para uma potência de 2 entradas, o Debruin é o mais rápido absoluto, ainda mais rápido do que _BitScanReverseem qualquer processador que testei. No entanto, no caso geral,_BitScanReverse (ou qualquer que seja o nome do intrínseco em seu compilador) é o mais rápido (embora em certas CPUs ele possa ser microcodificado).

Se a função intrínseca não for uma opção, aqui está uma solução de software ideal para processar entradas gerais.

u8  inline log2 (u32 val)  {
    u8  k = 0;
    if (val > 0x0000FFFFu) { val >>= 16; k  = 16; }
    if (val > 0x000000FFu) { val >>= 8;  k |= 8;  }
    if (val > 0x0000000Fu) { val >>= 4;  k |= 4;  }
    if (val > 0x00000003u) { val >>= 2;  k |= 2;  }
    k |= (val & 2) >> 1;
    return k;
}

Observe que esta versão não requer uma consulta de Debruin no final, ao contrário da maioria das outras respostas. Ele calcula a posição no lugar.

As tabelas podem ser preferíveis, no entanto, se você chamá-las repetidamente o suficiente, o risco de uma falha de cache será eclipsado pelo aumento da velocidade de uma tabela.

u8 kTableLog2[256] = {
0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
};

u8 log2_table(u32 val)  {
    u8  k = 0;
    if (val > 0x0000FFFFuL) { val >>= 16; k  = 16; }
    if (val > 0x000000FFuL) { val >>=  8; k |=  8; }
    k |= kTableLog2[val]; // precompute the Log2 of the low byte

    return k;
}

Isso deve produzir o maior rendimento de qualquer uma das respostas de software fornecidas aqui, mas se você apenas ligar ocasionalmente, prefira uma solução livre de tabela como meu primeiro trecho.

VoidStar
fonte
1
Algumas das respostas não têm ramificações, mas provavelmente serão compiladas com ramificações condicionais. Você apenas comparou com o mesmo valor repetidamente, ou um padrão simples ou algo assim? A previsão incorreta de ramos é um assassino para o desempenho. stackoverflow.com/questions/11227809/…
Peter Cordes
3

Como as respostas acima indicam, há várias maneiras de determinar o bit mais significativo. No entanto, como também foi apontado, os métodos provavelmente serão exclusivos para registradores de 32 ou 64 bits. A página stanford.edu bithacks fornece soluções que funcionam para computação de 32 bits e 64 bits. Com um pouco de trabalho, eles podem ser combinados para fornecer uma abordagem sólida de arquitetura cruzada para obter o MSB. A solução que cheguei para compilar / trabalhar em computadores de 64 e 32 bits foi:

#if defined(__LP64__) || defined(_LP64)
# define BUILD_64   1
#endif

#include <stdio.h>
#include <stdint.h>  /* for uint32_t */

/* CHAR_BIT  (or include limits.h) */
#ifndef CHAR_BIT
#define CHAR_BIT  8
#endif  /* CHAR_BIT */

/* 
 * Find the log base 2 of an integer with the MSB N set in O(N)
 * operations. (on 64bit & 32bit architectures)
 */
int
getmsb (uint32_t word)
{
    int r = 0;
    if (word < 1)
        return 0;
#ifdef BUILD_64
    union { uint32_t u[2]; double d; } t;  // temp
    t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] = 0x43300000;
    t.u[__FLOAT_WORD_ORDER!=LITTLE_ENDIAN] = word;
    t.d -= 4503599627370496.0;
    r = (t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] >> 20) - 0x3FF;
#else
    while (word >>= 1)
    {
        r++;
    }
#endif  /* BUILD_64 */
    return r;
}
David C. Rankin
fonte
Não estava int r; originalmente definido acima da #ifdef BUILD_64bandeira? Nesse caso, não seria necessário redefinir dentro da condicional.
David C. Rankin
3

Uma versão em C usando aproximação sucessiva:

unsigned int getMsb(unsigned int n)
{
  unsigned int msb  = sizeof(n) * 4;
  unsigned int step = msb;
  while (step > 1)
 {
    step /=2;
    if (n>>msb)
     msb += step;
   else
     msb -= step;
 }
  if (n>>msb)
    msb++;
  return (msb - 1);
}

Vantagem: o tempo de execução é constante independentemente do número fornecido, pois o número de loops é sempre o mesmo. (4 loops ao usar "unsigned int")


fonte
Se você escrever com um operador ternário ( msb += (n>>msb) ? step : -step;), mais compiladores provavelmente criarão asm sem ramificação, evitando erros de previsão de ramificação em cada etapa ( stackoverflow.com/questions/11227809/… ).
Peter Cordes
3

Eu sei que esta questão é muito antiga, mas apenas tendo implementado uma função msb () eu mesmo descobri que a maioria das soluções apresentadas aqui e em outros sites não são necessariamente as mais eficientes - pelo menos para minha definição pessoal de eficiência (veja também Atualização abaixo ) Aqui está o porquê:

A maioria das soluções (especialmente aquelas que empregam algum tipo de esquema de busca binária ou a abordagem ingênua que faz uma varredura linear da direita para a esquerda) parecem negligenciar o fato de que, para números binários arbitrários, não há muitos que começam com uma sequência muito longa de zeros. Na verdade, para qualquer largura de bit, metade de todos os inteiros começam com 1 e um quarto deles começam com 01 . Veja onde estou chegando? Meu argumento é que uma varredura linear começando da posição do bit mais significativo para o menos significativo (da esquerda para a direita) não é tão "linear" como pode parecer à primeira vista.

Pode ser mostrado 1 , que para qualquer largura de bit, o número médio de bits que precisam ser testados é no máximo 2. Isso se traduz em uma complexidade de tempo amortizado de O (1) em relação ao número de bits (!) .

Claro, o pior caso ainda é O (n) , pior do que o O (log (n)) que você obtém com abordagens do tipo busca binária, mas como há tão poucos casos piores, eles são insignificantes para a maioria dos aplicativos ( Atualizar : não é bem assim: pode haver poucos, mas podem ocorrer com alta probabilidade - consulte a atualização abaixo).

Aqui está a abordagem "ingênua" que criei, que pelo menos na minha máquina supera a maioria das outras abordagens (esquemas de pesquisa binária para ints de 32 bits sempre requerem log 2 (32) = 5 etapas, enquanto este algoritmo bobo requer menos de 2 em média) - desculpe por ser C ++ e não C puro:

template <typename T>
auto msb(T n) -> int
{
    static_assert(std::is_integral<T>::value && !std::is_signed<T>::value,
        "msb<T>(): T must be an unsigned integral type.");

    for (T i = std::numeric_limits<T>::digits - 1, mask = 1 << i; i >= 0; --i, mask >>= 1)
    {
        if ((n & mask) != 0)
            return i;
    }

    return 0;
}

Atualização : Embora o que escrevi aqui seja perfeitamente verdadeiro parainteiros arbitrários , onde cada combinação de bits é igualmente provável (meu teste de velocidade simplesmente mediu quanto tempo levou para determinar o MSB para todos os inteiros de 32 bits), inteiros da vida real, para que tal função será chamada, geralmente segue um padrão diferente: No meu código, por exemplo, esta função é usada para determinar se o tamanho de um objeto é uma potência de 2, ou para encontrar a próxima potência de 2 maior ou igual a um tamanho do objeto . Meu palpite é que a maioria dos aplicativos que usam o MSB envolvem números que são muito menores do que o número máximo que um inteiro pode representar (os tamanhos dos objetos raramente utilizam todos os bits em um size_t) Nesse caso, minha solução terá um desempenho pior do que uma abordagem de pesquisa binária - então, a última provavelmente deve ser preferida, embora minha solução seja um loop mais rápido por todos os inteiros.
TL; DR: Os inteiros da vida real provavelmente terão uma tendência para o pior caso desse algoritmo simples, o que tornará seu desempenho pior no final - apesar do fato de ser O (1) amortizado para inteiros verdadeiramente arbitrários.

1 O argumento é assim (rascunho): Seja n o número de bits (largura de bits). Há um total de 2 n inteiros que podem ser representados com n bits. Existem 2 n - 1 inteiros começando com 1 (o primeiro 1 é fixo, os n - 1 bits restantes podem ser qualquer coisa). Esses inteiros requerem apenas uma interação do loop para determinar o MSB. Além disso, há 2 n - 2 inteiros começando com 01 , exigindo 2 iterações, 2 n - 3 inteiros começando com 001 , exigindo 3 iterações e assim por diante.

Se somarmos todas as iterações necessárias para todos os inteiros possíveis e dividi-los por 2 n , o número total de inteiros, obtemos o número médio de iterações necessárias para determinar o MSB para inteiros de n bits:

(1 * 2 n - 1 + 2 * 2 n - 2 + 3 * 2 n - 3 + ... + n) / 2 n

Esta série de iterações médias é convergente e tem um limite de 2 para n até o infinito

Assim, o algoritmo ingênuo da esquerda para a direita tem, na verdade, uma complexidade de tempo constante amortizada de O (1) para qualquer número de bits.

Finnegan
fonte
2
Eu não acho que seja necessariamente uma suposição justa que as entradas para funções msb tendem a ser distribuídas uniformemente. Na prática, essas entradas tendem a ser registros de interrupção ou bitboards ou alguma outra estrutura de dados com valores distribuídos desigualmente. Para um benchmark justo, acho que é mais seguro assumir que as saídas (não as entradas) serão distribuídas uniformemente.
johnwbyrd
3

nos deu log2. Isso elimina a necessidade de todas as log2implementações de molhos especiais que você vê nesta página. Você pode usar a log2implementação do padrão assim:

const auto n = 13UL;
const auto Index = (unsigned long)log2(n);

printf("MSB is: %u\n", Index); // Prints 3 (zero offset)

Um nde 0ULprecisa ser evitado também, porque:

-∞ é retornado e FE_DIVBYZERO é gerado

Eu escrevi um exemplo com esse cheque que é definido arbitrariamente Indexcomo ULONG_MAXaqui: https://ideone.com/u26vsi


o o corolário da única resposta gcc do efemiente é:

const auto n = 13UL;
unsigned long Index;

_BitScanReverse(&Index, n);
printf("MSB is: %u\n", Index); // Prints 3 (zero offset)

A documentação para_BitScanReverse estados que Indexsão:

Carregado com a posição do bit do primeiro conjunto de bits (1) encontrado

Na prática, eu descobri que, se né 0ULque Indexestá definido para0UL , assim como seria para um nde 1UL. Mas a única coisa garantida na documentação no caso de um nde 0ULé que a devolução é:

0 se nenhum conjunto de bits foi encontrado

Assim, de forma semelhante à log2implementação preferencial acima, o retorno deve ser verificado definindo Indexum valor sinalizado neste caso. Novamente escrevi um exemplo de uso ULONG_MAXpara este valor de sinalizador aqui: http://rextester.com/GCU61409

Jonathan Mee
fonte
Não, _BitScanReverseretorna 0 apenas se a entrada foi 0. É como a BSRinstrução x86 , que configura ZF com base apenas na entrada, não na saída. Interessante que o MS diz que os documentos deixam por indexdefinir quando nenhum 1bit é encontrado; que corresponde ao comportamento do conjunto x86 de bsrtambém. (A AMD documenta deixando o registro de destino sem modificações em src = 0, mas a Intel apenas diz saída indefinida, embora suas CPUs implementem o comportamento de deixar sem modificações.) Isso é diferente do x86 lzcnt, que dá 32para não encontrado.
Peter Cordes
@PeterCordes _BitScanReverseusa indexação baseada em zero, portanto, se nfor 1, o índice do bit definido é de fato 0. Infelizmente, como você diz se nfor 0, a saída também é 0 :( Isso significa que não há como usar o retorno para distinguir entre n1 ou 0. Era isso que eu estava tentando comunicar. Você acha que há uma maneira melhor de dizer isso?
Jonathan Mee
Acho que você está falando sobre como fica Index. Esse não é o valor de retorno . Ele retorna um booleano que é falso se a entrada for zero (e é por isso que Index é passado por referência em vez de ser retornado normalmente). godbolt.org/g/gQKJdE . E eu verifiquei: apesar do texto dos documentos do MS, _BitScanReversenão deixa o Index indefinido n==0: você apenas obtém o valor que estava no registro que ele usou. (Que no seu caso foi provavelmente o mesmo registro usado Indexposteriormente, levando a você ver a 0).
Peter Cordes
Esta questão não está marcada como c ++.
technosaurus
@technosaurus Obrigado, esqueci-me. Dado que a pergunta é C que realmente temos log2desde C99.
Jonathan Mee
2

Pense em operadores bit a bit.

Eu não entendi a pergunta da primeira vez. Você deve produzir um int com o conjunto de bits mais à esquerda (os outros zero). Supondo que cmp esteja definido com esse valor:

position = sizeof(int)*8
while(!(n & cmp)){ 
   n <<=1;
   position--;
}
Vasil
fonte
O que você quer dizer com converter para uma string? A definição de ffs pega um int e retorna um int. Onde seria a conversão? E a que propósito serviria a conversão se estivermos procurando bits em uma palavra?
dreamlax
Eu não conhecia essa função.
Vasil
O 8deveria ser CHAR_BIT. É muito improvável que esse seja o caminho mais rápido, porque a previsão incorreta do desvio acontecerá ao sair do loop, a menos que seja usado com a mesma entrada repetidamente. Além disso, para pequenas entradas (muitos zeros), ele precisa fazer muitos loops. É como a forma alternativa que você usaria como a versão fácil de verificar em um teste de unidade para comparar com as versões otimizadas.
Peter Cordes
2

Expandindo o benchmark de Josh ... pode-se melhorar o CLZ da seguinte maneira

/***************** clz2 ********************/

#define NUM_OF_HIGHESTBITclz2(a) ((a)                              \
                  ? (((1U) << (sizeof(unsigned)*8-1)) >> __builtin_clz(a)) \
                  : 0)

Com relação ao asm: observe que existem bsr e bsrl (esta é a versão "longa"). o normal pode ser um pouco mais rápido.

JonesD
fonte
1

Observe que o que você está tentando fazer é calcular o inteiro log2 de um inteiro,

#include <stdio.h>
#include <stdlib.h>

unsigned int
Log2(unsigned long x)
{
    unsigned long n = x;
    int bits = sizeof(x)*8;
    int step = 1; int k=0;
    for( step = 1; step < bits; ) {
        n |= (n >> step);
        step *= 2; ++k;
    }
    //printf("%ld %ld\n",x, (x - (n >> 1)) );
    return(x - (n >> 1));
}

Observe que você pode tentar pesquisar mais de 1 bit por vez.

unsigned int
Log2_a(unsigned long x)
{
    unsigned long n = x;
    int bits = sizeof(x)*8;
    int step = 1;
    int step2 = 0;
    //observe that you can move 8 bits at a time, and there is a pattern...
    //if( x>1<<step2+8 ) { step2+=8;
        //if( x>1<<step2+8 ) { step2+=8;
            //if( x>1<<step2+8 ) { step2+=8;
            //}
        //}
    //}
    for( step2=0; x>1L<<step2+8; ) {
        step2+=8;
    }
    //printf("step2 %d\n",step2);
    for( step = 0; x>1L<<(step+step2); ) {
        step+=1;
        //printf("step %d\n",step+step2);
    }
    printf("log2(%ld) %d\n",x,step+step2);
    return(step+step2);
}

Esta abordagem usa uma pesquisa binária

unsigned int
Log2_b(unsigned long x)
{
    unsigned long n = x;
    unsigned int bits = sizeof(x)*8;
    unsigned int hbit = bits-1;
    unsigned int lbit = 0;
    unsigned long guess = bits/2;
    int found = 0;

    while ( hbit-lbit>1 ) {
        //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        //when value between guess..lbit
        if( (x<=(1L<<guess)) ) {
           //printf("%ld < 1<<%d %ld\n",x,guess,1L<<guess);
            hbit=guess;
            guess=(hbit+lbit)/2;
            //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        }
        //when value between hbit..guess
        //else
        if( (x>(1L<<guess)) ) {
            //printf("%ld > 1<<%d %ld\n",x,guess,1L<<guess);
            lbit=guess;
            guess=(hbit+lbit)/2;
            //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        }
    }
    if( (x>(1L<<guess)) ) ++guess;
    printf("log2(x%ld)=r%d\n",x,guess);
    return(guess);
}

Outro método de pesquisa binária, talvez mais legível,

unsigned int
Log2_c(unsigned long x)
{
    unsigned long v = x;
    unsigned int bits = sizeof(x)*8;
    unsigned int step = bits;
    unsigned int res = 0;
    for( step = bits/2; step>0; )
    {
        //printf("log2(%ld) v %d >> step %d = %ld\n",x,v,step,v>>step);
        while ( v>>step ) {
            v>>=step;
            res+=step;
            //printf("log2(%ld) step %d res %d v>>step %ld\n",x,step,res,v);
        }
        step /= 2;
    }
    if( (x>(1L<<res)) ) ++res;
    printf("log2(x%ld)=r%ld\n",x,res);
    return(res);
}

E porque você vai querer testá-los,

int main()
{
    unsigned long int x = 3;
    for( x=2; x<1000000000; x*=2 ) {
        //printf("x %ld, x+1 %ld, log2(x+1) %d\n",x,x+1,Log2(x+1));
        printf("x %ld, x+1 %ld, log2_a(x+1) %d\n",x,x+1,Log2_a(x+1));
        printf("x %ld, x+1 %ld, log2_b(x+1) %d\n",x,x+1,Log2_b(x+1));
        printf("x %ld, x+1 %ld, log2_c(x+1) %d\n",x,x+1,Log2_c(x+1));
    }
    return(0);
}
ChuckCottrill
fonte
1

Colocar isso, visto que é "mais uma" abordagem, parece ser diferente de outras já fornecidas.

retorna -1if x==0, caso contrário floor( log2(x)) (resultado máximo 31)

Reduza o problema de 32 para 4 bits e, em seguida, use uma tabela. Talvez deselegante, mas pragmático.

É o que eu uso quando não quero usar __builtin_clzdevido a problemas de portabilidade.

Para torná-lo mais compacto, pode-se usar um loop para reduzir, adicionando 4 a r de cada vez, no máximo 7 iterações. Ou algum híbrido, como (para 64 bits): loop para reduzir para 8, teste para reduzir para 4.

int log2floor( unsigned x ){
   static const signed char wtab[16] = {-1,0,1,1, 2,2,2,2, 3,3,3,3,3,3,3,3};
   int r = 0;
   unsigned xk = x >> 16;
   if( xk != 0 ){
       r = 16;
       x = xk;
   }
   // x is 0 .. 0xFFFF
   xk = x >> 8;
   if( xk != 0){
       r += 8;
       x = xk;
   }
   // x is 0 .. 0xFF
   xk = x >> 4;
   if( xk != 0){
       r += 4;
       x = xk;
   }
   // now x is 0..15; x=0 only if originally zero.
   return r + wtab[x];
}
greggo
fonte
1

Uau, foram muitas as respostas. Não lamento responder a uma pergunta antiga.

int result = 0;//could be a char or int8_t instead
if(value){//this assumes the value is 64bit
    if(0xFFFFFFFF00000000&value){  value>>=(1<<5); result|=(1<<5);  }//if it is 32bit then remove this line
    if(0x00000000FFFF0000&value){  value>>=(1<<4); result|=(1<<4);  }//and remove the 32msb
    if(0x000000000000FF00&value){  value>>=(1<<3); result|=(1<<3);  }
    if(0x00000000000000F0&value){  value>>=(1<<2); result|=(1<<2);  }
    if(0x000000000000000C&value){  value>>=(1<<1); result|=(1<<1);  }
    if(0x0000000000000002&value){  result|=(1<<0);  }
}else{
  result=-1;
}

Esta resposta é muito semelhante a outra resposta ... tudo bem.

Harry Svensson
fonte
Escrever os valores dos turnos 1<<ké um toque agradável. E as máscaras? (1 << (1<<k-1)-1<< (1<<k-1)? ( most optimal? Você compara um superlativo?)
Barba cinza
@greybeard Se você olhar as edições desta questão, verá quando adicionei a parte "ideal". Esqueci de removê-lo porque mudei minha resposta. Também não tenho certeza porque você está falando sobre as máscaras? (Que máscaras? Não estou te seguindo)
Harry Svensson
( máscara (bit) são valores usados ​​para selecionar / limpar bits seletivamente / usados ​​em &e &~.) Você pode substituir as constantes hexadecimais por semelhantes ((type)1<<(1<<k))-1<<(1<<k).
Greybeard
Ah, certo, estou usando máscaras, esqueci totalmente disso. Eu respondi isso alguns meses atrás ... - Hmmm, bem, já que é avaliado durante o tempo de compilação, eu digo que é equivalente aos valores hexadecimais. No entanto, um é enigmático e o outro é hexadecimal.
Harry Svensson
0

O código:

    // x>=1;
    unsigned func(unsigned x) {
    double d = x ;
    int p= (*reinterpret_cast<long long*>(&d) >> 52) - 1023;
    printf( "The left-most non zero bit of %d is bit %d\n", x, p);
    }

Ou obtenha a parte inteira da instrução FPU FYL2X (Y * Log2 X) configurando Y = 1

jemin
fonte
uhhhhh. que? como funciona isso? é de alguma forma portátil?
underscore_d
Os códigos na janela são portáteis. A função FYL2X () é uma instrução fpu, mas pode ser portada e pode ser encontrada em alguma biblioteca de FPU / matemática.
jemin
@underscore_d Funciona porque os números de ponto flutuante são normalizados ... converter para o deslocamento duplo dos bits da mantissa para eliminar os zeros à esquerda, e este código extrai o expoente e o ajusta para determinar o número de bits deslocados. Certamente não é independente de arquitetura, mas provavelmente funcionará em qualquer máquina que você encontrar.
Jim Balter
Esta é uma versão alternativa desta resposta , veja lá para comentários sobre desempenho e portabilidade. (Especificamente a não portabilidade do lançamento de ponteiro para doubletrocadilho de tipo .) Ele usa matemática de endereço para recarregar apenas os 32 bits altos do , o que provavelmente é bom se realmente armazenar / recarregar em vez de trocadilho de alguma outra maneira, por exemplo, com uma movqinstrução como a que você pode obter aqui no x86.
Peter Cordes
Observe também meu [comentário a essa resposta], onde ofereço o terrível aviso de que esse método fornece a resposta errada para valores (pelo menos) no intervalo [7FFFFFFFFFFFFE00 - 7FFFFFFFFFFFFFFF].
Glenn Slayden
0

Outro pôster forneceu uma tabela de consulta usando uma consulta de todos os bytes . Caso você queira obter um pouco mais de desempenho (ao custo de 32K de memória em vez de apenas 256 entradas de pesquisa), aqui está uma solução usando uma tabela de pesquisa de 15 bits , em C # 7 para .NET .

A parte interessante é inicializar a tabela. Como é um bloco relativamente pequeno que queremos durante o tempo de vida do processo, aloco memória não gerenciada para isso usando Marshal.AllocHGlobal. Como você pode ver, para desempenho máximo, todo o exemplo é escrito como nativo:

readonly static byte[] msb_tab_15;

// Initialize a table of 32768 bytes with the bit position (counting from LSB=0)
// of the highest 'set' (non-zero) bit of its corresponding 16-bit index value.
// The table is compressed by half, so use (value >> 1) for indexing.
static MyStaticInit()
{
    var p = new byte[0x8000];

    for (byte n = 0; n < 16; n++)
        for (int c = (1 << n) >> 1, i = 0; i < c; i++)
            p[c + i] = n;

    msb_tab_15 = p;
}

A tabela requer inicialização única por meio do código acima. É somente leitura, portanto, uma única cópia global pode ser compartilhada para acesso simultâneo. Com esta tabela, você pode consultar rapidamente o log 2 do inteiro , que é o que estamos procurando aqui, para todas as várias larguras de inteiro (8, 16, 32 e 64 bits).

Observe que a entrada da tabela para 0, o único inteiro para o qual a noção de 'bit de conjunto mais alto' é indefinido, recebe o valor -1. Essa distinção é necessária para o tratamento adequado de palavras superiores com valor 0 no código a seguir. Sem mais delongas, aqui está o código para cada um dos vários primitivos inteiros:

versão ulong (64 bits)

/// <summary> Index of the highest set bit in 'v', or -1 for value '0' </summary>
public static int HighestOne(this ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 0x40) - 1;      // handles cases v==0 and MSB==63

    int j = /**/ (int)((0xFFFFFFFFU - v /****/) >> 58) & 0x20;
    j |= /*****/ (int)((0x0000FFFFU - (v >> j)) >> 59) & 0x10;
    return j + msb_tab_15[v >> (j + 1)];
}

Versão uint (32 bits)

/// <summary> Index of the highest set bit in 'v', or -1 for value '0' </summary>
public static int HighestOne(uint v)
{
    if ((int)v <= 0)
        return (int)((v >> 26) & 0x20) - 1;     // handles cases v==0 and MSB==31

    int j = (int)((0x0000FFFFU - v) >> 27) & 0x10;
    return j + msb_tab_15[v >> (j + 1)];
}

Várias sobrecargas para o acima

public static int HighestOne(long v) => HighestOne((ulong)v);
public static int HighestOne(int v) => HighestOne((uint)v);
public static int HighestOne(ushort v) => msb_tab_15[v >> 1];
public static int HighestOne(short v) => msb_tab_15[(ushort)v >> 1];
public static int HighestOne(char ch) => msb_tab_15[ch >> 1];
public static int HighestOne(sbyte v) => msb_tab_15[(byte)v >> 1];
public static int HighestOne(byte v) => msb_tab_15[v >> 1];

Esta é uma solução completa e funcional que representa o melhor desempenho no .NET 4.7.2 para inúmeras alternativas que comparei com um equipamento de teste de desempenho especializado. Alguns deles são mencionados abaixo. Os parâmetros de teste foram uma densidade uniforme de todas as posições de 65 bits, ou seja, 0 ... 31/63 mais o valor 0(que produz o resultado -1). Os bits abaixo da posição do índice de destino foram preenchidos aleatoriamente. Os testes foram x64 apenas , modo de lançamento, com otimizações JIT habilitadas.




Esse é o fim da minha resposta formal aqui; o que se segue são algumas notas casuais e links para o código-fonte para candidatos de teste alternativos associados ao teste que executei para validar o desempenho e a exatidão do código acima.


A versão fornecida acima, codificada como Tab16A, foi uma vencedora consistente em muitas execuções. Esses vários candidatos, em forma ativa de trabalho / scratch, podem ser encontrados aqui , aqui e aqui .

 1 candidatos. HighestOne_Tab16A 622.496
 2 candidatos. HighestOne_Tab16C 628.234
 3 candidatos.HighestOne_Tab8A 649.146
 4 candidatos. HighestOne_Tab8B 656.847
 5 candidatos. HighestOne_Tab16B 657.147
 6 candidatos. HighestOne_Tab16D 659.650
 7 _highest_one_bit_UNMANAGED.HighestOne_U 702.900
 8 de_Bruijn.IndexOfMSB 709.672
 9 _old_2.HighestOne_Old2 715.810
10 _test_A.HighestOne8 757.188
11 _old_1.HighestOne_Old1 757.925
12 _test_A.HighestOne5 (inseguro) 760.387
13 _teste_B.HighestOne8 (inseguro) 763.904
14 _test_A.HighestOne3 (inseguro) 766.433
15 _test_A.HighestOne1 (inseguro) 767.321
16 _teste_A.HighestOne4 (inseguro) 771.702
17 _teste_B.HighestOne2 (inseguro) 772.136
18 _test_B.HighestOne1 (inseguro) 772.527
19 _teste_B.HighestOne3 (inseguro) 774.140
20 _test_A.HighestOne7 (inseguro) 774.581
21 _test_B.HighestOne7 (inseguro) 775.463
22 _test_A.HighestOne2 (inseguro) 776.865
23 candidatos. HighestOne_NoTab 777.698
24 _test_B.HighestOne6 (inseguro) 779.481
25 _test_A.HighestOne6 (inseguro) 781.553
26 _teste_B.HighestOne4 (inseguro) 785.504
27 _teste_B.HighestOne5 (inseguro) 789.797
28 _test_A.HighestOne0 (inseguro) 809.566
29 _test_B.HighestOne0 (inseguro) 814.990
30 _highest_one_bit.HighestOne 824.345
30 _bitarray_ext.RtlFindMostSignificantBit 894.069
31 candidatos. HighestOne_Naive 898.865

Notável é que o péssimo desempenho de ntdll.dll!RtlFindMostSignificantBitvia P / Invoke:

[DllImport("ntdll.dll"), SuppressUnmanagedCodeSecurity, SecuritySafeCritical]
public static extern int RtlFindMostSignificantBit(ulong ul);

É realmente uma pena, porque aqui está toda a função real:

    RtlFindMostSignificantBit:
        bsr rdx, rcx  
        mov eax,0FFFFFFFFh  
        movzx ecx, dl  
        cmovne      eax,ecx  
        ret

Não consigo imaginar o desempenho ruim originado com essas cinco linhas, então as penalidades de transição gerenciada / nativa devem ser as culpadas. Também fiquei surpreso que o teste realmente favoreceu as shorttabelas de pesquisa direta de 32 KB (e 64 KB) (16 bits) em relação às tabelas de pesquisa de 128 bytes (e 256 bytes) byte(8 bits). Achei que o seguinte seria mais competitivo com as pesquisas de 16 bits, mas o último superou consistentemente isso:

public static int HighestOne_Tab8A(ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 64) - 1;

    int j;
    j =  /**/ (int)((0xFFFFFFFFU - v) >> 58) & 32;
    j += /**/ (int)((0x0000FFFFU - (v >> j)) >> 59) & 16;
    j += /**/ (int)((0x000000FFU - (v >> j)) >> 60) & 8;
    return j + msb_tab_8[v >> j];
}

A última coisa que vou apontar é que fiquei bastante chocado porque meu método deBruijn não se saiu melhor. Este é o método que eu estava usando amplamente anteriormente:

const ulong N_bsf64 = 0x07EDD5E59A4E28C2,
            N_bsr64 = 0x03F79D71B4CB0A89;

readonly public static sbyte[]
bsf64 =
{
    63,  0, 58,  1, 59, 47, 53,  2, 60, 39, 48, 27, 54, 33, 42,  3,
    61, 51, 37, 40, 49, 18, 28, 20, 55, 30, 34, 11, 43, 14, 22,  4,
    62, 57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19, 29, 10, 13, 21,
    56, 45, 25, 31, 35, 16,  9, 12, 44, 24, 15,  8, 23,  7,  6,  5,
},
bsr64 =
{
     0, 47,  1, 56, 48, 27,  2, 60, 57, 49, 41, 37, 28, 16,  3, 61,
    54, 58, 35, 52, 50, 42, 21, 44, 38, 32, 29, 23, 17, 11,  4, 62,
    46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, 31, 22, 10, 45,
    25, 39, 14, 33, 19, 30,  9, 24, 13, 18,  8, 12,  7,  6,  5, 63,
};

public static int IndexOfLSB(ulong v) =>
    v != 0 ? bsf64[((v & (ulong)-(long)v) * N_bsf64) >> 58] : -1;

public static int IndexOfMSB(ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 64) - 1;

    v |= v >> 1; v |= v >> 2;  v |= v >> 4;   // does anybody know a better
    v |= v >> 8; v |= v >> 16; v |= v >> 32;  // way than these 12 ops?
    return bsr64[(v * N_bsr64) >> 58];
}

Há muita discussão sobre como os métodos deBruijn são superiores e excelentes nessa questão do SO , e eu tendia a concordar. Minha especulação é que, embora os métodos deBruijn e de tabela de pesquisa direta (que descobri ser mais rápidos) tenham que fazer uma pesquisa de tabela e ambos tenham ramificações mínimas, apenas o deBruijn tem uma operação de multiplicação de 64 bits. Eu apenas testei as IndexOfMSBfunções aqui - não o deBruijn - IndexOfLSBmas espero que o último tenha uma chance muito melhor, já que tem muito menos operações (veja acima), e provavelmente continuarei a usá-lo para LSB.

Glenn Slayden
fonte
1
O cache L1D em CPUs x86 modernas é de apenas 32kiB. Um LUT grande provavelmente será pior do que um LUT pequeno, a menos que você use os mesmos valores repetidamente. Se não for, você terá perdas frequentes de cache.
Peter Cordes
0

Meu método humilde é muito simples:

MSB (x) = INT [Log (x) / Log (2)]

Tradução: O MSB de x é o valor inteiro de (Log da Base x dividido pelo Log da Base 2).

Isso pode ser facilmente e rapidamente adaptado a qualquer linguagem de programação. Experimente na sua calculadora para ver por si mesmo se funciona.

SpartanWar
fonte
Isso funciona se você só estiver interessado na eficiência do desenvolvedor. Se você deseja eficiência de tempo de execução, você precisa de um algoritmo alternativo.
Mikko Rantalainen
Isso pode falhar devido a um erro de arredondamento. Por exemplo, em CPython 2 e 3, int(math.log((1 << 48) - 1) / math.log(2))é 48.
benrg
0

Aqui está uma solução rápida para C que funciona no GCC e no Clang ; pronto para ser copiado e colado.

#include <limits.h>

unsigned int fls(const unsigned int value)
{
    return (unsigned int)1 << ((sizeof(unsigned int) * CHAR_BIT) - __builtin_clz(value) - 1);
}

unsigned long flsl(const unsigned long value)
{
    return (unsigned long)1 << ((sizeof(unsigned long) * CHAR_BIT) - __builtin_clzl(value) - 1);
}

unsigned long long flsll(const unsigned long long value)
{
    return (unsigned long long)1 << ((sizeof(unsigned long long) * CHAR_BIT) - __builtin_clzll(value) - 1);
}

E uma versão um pouco melhorada para C ++ .

#include <climits>

constexpr unsigned int fls(const unsigned int value)
{
    return (unsigned int)1 << ((sizeof(unsigned int) * CHAR_BIT) - __builtin_clz(value) - 1);
}

constexpr unsigned long fls(const unsigned long value)
{
    return (unsigned long)1 << ((sizeof(unsigned long) * CHAR_BIT) - __builtin_clzl(value) - 1);
}

constexpr unsigned long long fls(const unsigned long long value)
{
    return (unsigned long long)1 << ((sizeof(unsigned long long) * CHAR_BIT) - __builtin_clzll(value) - 1);
}

O código assume que valuenão será 0. Se você deseja permitir 0, você precisa modificá-lo.

NO_NAME
fonte
0

Presumo que sua pergunta seja para um número inteiro (chamado v abaixo) e não um número inteiro sem sinal.

int v = 612635685; // whatever value you wish

unsigned int get_msb(int v)
{
    int r = 31;                         // maximum number of iteration until integer has been totally left shifted out, considering that first bit is index 0. Also we could use (sizeof(int)) << 3 - 1 instead of 31 to make it work on any platform.

    while (!(v & 0x80000000) && r--) {   // mask of the highest bit
        v <<= 1;                        // multiply integer by 2.
    }
    return r;                           // will even return -1 if no bit was set, allowing error catch
}

Se quiser que funcione sem levar em conta o sinal, você pode adicionar um extra 'v << = 1;' antes do loop (e altere o valor de r para 30 de acordo). Por favor, me avise se eu esqueci alguma coisa. Não testei, mas deve funcionar bem.

Antonin GAVREL
fonte
v <<= 1é um comportamento indefinido (UB) quando v < 0.
chux - Reintegrar Monica
0x8000000, talvez você queira dizer um 0 a mais aqui.
MM