Si tengo un número entero n, y quiero saber la posición del bit más significativo (es decir, si el bit menos significativo está a la derecha, quiero saber la posición del bit más a la izquierda que es un 1), ¿Cuál es el método más rápido / eficaz para averiguarlo?
Sé que POSIX admite un ffs()
método en strings.h para encontrar el primer bit establecido, pero no parece haber un fls()
método correspondiente .
¿Hay alguna forma realmente obvia de hacer esto que me falta?
¿Qué sucede en los casos en los que no puede usar las funciones POSIX para la portabilidad?
Editar: ¿Qué pasa con una solución que funciona en arquitecturas de 32 y 64 bits (parece que muchos de los listados de código solo funcionarían en entradas de 32 bits)?
Respuestas:
GCC tiene :
Espero que se traduzcan en algo razonablemente eficiente para su plataforma actual, ya sea uno de esos sofisticados algoritmos de jugueteo de bits o una sola instrucción.
Un truco útil si su entrada puede ser cero es
__builtin_clz(x | 1)
: establecer incondicionalmente el bit bajo sin modificar ningún otro hace que la salida sea31
parax=0
, sin cambiar la salida para ninguna otra entrada.Para evitar tener que hacer eso, su otra opción son intrínsecos específicos de la plataforma como ARM GCC
__clz
(no se necesita encabezado) o x86_lzcnt_u32
en CPU que admiten lalzcnt
instrucción. (Tenga en cuenta quelzcnt
decodifica comobsr
en CPU más antiguas en lugar de fallar, lo que da 31-lzcnt para entradas distintas de cero).Desafortunadamente, no hay forma de aprovechar de manera portátil las diversas instrucciones CLZ en plataformas que no son x86 que definen el resultado para input = 0 como 32 o 64 (según el ancho del operando). x86 también
lzcnt
hace eso, mientras quebsr
produce un índice de bits que el compilador tiene que cambiar a menos que usted lo use31-__builtin_clz(x)
.(El "resultado indefinido" no es C Undefined Behavior, solo un valor que no está definido. En realidad, es lo que estaba en el registro de destino cuando se ejecutó la instrucción. AMD documenta esto, Intel no lo hace, pero las CPU de Intel implementan ese comportamiento . Pero es que no lo estaba previamente en la variable C que está asignando a, eso no es por lo general cómo funcionan las cosas cuando gcc convierte en C asm. Véase también ¿por qué romper la "dependencia de salida" de LZCNT importa? )
fuente
__builtin_ctz
overffs
, que se compila en un BSF y un CMOV para manejar el caso de input-was-zero. En arquitecturas sin una implementación lo suficientemente corta (por ejemplo, ARM antiguo sin laclz
instrucción), gcc emite una llamada a una función auxiliar libgcc.Asumiendo que está en x86 y juega un poco de ensamblador en línea, Intel proporciona una
BSR
instrucción ("exploración de bits inversa"). Es rápido en algunos x86 (microcodificado en otros). Del manual:(Si está en PowerPC, hay una
cntlz
instrucción similar ("contar ceros a la izquierda").)Código de ejemplo para gcc:
Vea también este tutorial de ensamblador en línea , que muestra (sección 9.4) que es considerablemente más rápido que el código en bucle.
fuente
Dado que 2 ^ N es un número entero con solo el N-ésimo conjunto de bits (1 << N), encontrar la posición (N) del conjunto de bits más alto es el número entero base 2 de ese número entero.
http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
Este algoritmo "obvio" puede no ser transparente para todos, pero cuando te das cuenta de que el código se desplaza a la derecha un bit repetidamente hasta que el bit más a la izquierda se haya desactivado (ten en cuenta que C trata cualquier valor distinto de cero como verdadero) y devuelve el número de turnos, tiene perfecto sentido. También significa que funciona incluso cuando se establece más de un bit; el resultado es siempre para el bit más significativo.
Si se desplaza hacia abajo en esa página, hay variaciones más rápidas y complejas. Sin embargo, si sabe que está tratando con números con muchos ceros iniciales, el enfoque ingenuo puede proporcionar una velocidad aceptable, ya que el cambio de bits es bastante rápido en C y el algoritmo simple no requiere indexar una matriz.
NOTA: Cuando utilice valores de 64 bits, tenga mucho cuidado con el uso de algoritmos muy inteligentes; muchos de ellos solo funcionan correctamente para valores de 32 bits.
fuente
>>>
. Más probablemente el comparador!= 0
y algunos paréntesis no especificados.Esto debería ser increíblemente rápido:
fuente
Esto es como encontrar una especie de registro de enteros. Hay trucos para jugar un poco, pero he creado mi propia herramienta para ello. El objetivo, por supuesto, es la velocidad.
Me doy cuenta de que la CPU ya tiene un detector de bits automático, que se utiliza para la conversión de enteros a flotantes. Así que usa eso.
Esta versión convierte el valor en un doble, luego lee el exponente, que le dice dónde estaba el bit. El cambio de fantasía y la resta es extraer las partes adecuadas del valor IEEE.
Es un poco más rápido usar flotadores, pero un flotador solo puede darle las primeras posiciones de 24 bits debido a su menor precisión.
Para hacer esto de manera segura, sin un comportamiento indefinido en C ++ o C, use en
memcpy
lugar de conversión de puntero para juegos de palabras. Los compiladores saben cómo integrarlo de manera eficiente.O en C99 y posteriores, use a
union {double d; uint32_t u[2];};
. Pero tenga en cuenta que en C ++, los juegos de palabras de tipo union solo se admiten en algunos compiladores como una extensión, no en ISO C ++.Por lo general, esto será más lento que un intrínseco específico de la plataforma para una instrucción de conteo de ceros a la izquierda, pero ISO C portátil no tiene tal función. Algunas CPU también carecen de una instrucción de conteo de cero a la izquierda, pero algunas de ellas pueden convertir números enteros a
double
. Sin embargo, volver a escribir un patrón de bits FP a un número entero puede ser lento (por ejemplo, en PowerPC requiere una función de almacenamiento / recarga y, por lo general, provoca un bloqueo de carga-golpe-almacenamiento).Este algoritmo podría ser potencialmente útil para implementaciones de SIMD, porque menos CPU tienen SIMD
lzcnt
. x86 solo recibió tal instrucción con AVX512CDfuente
Kaz Kylheku aquí
Evalué dos enfoques para esto en números de 63 bits (el tipo long long en gcc x86_64), manteniéndome alejado del bit de signo.
(Resulta que necesito este "bit más alto" para algo, ¿sabe?)
Implementé la búsqueda binaria basada en datos (basada en una de las respuestas anteriores). También implementé un árbol de decisiones completamente desenrollado a mano, que es solo código con operandos inmediatos. Sin bucles, sin mesas.
El árbol de decisión (más alto_bits_unrollado) comparado con un 69% más rápido, excepto para el caso n = 0 para el cual la búsqueda binaria tiene una prueba explícita.
La prueba especial de la búsqueda binaria para el caso 0 es solo un 48% más rápida que el árbol de decisiones, que no tiene una prueba especial.
Compilador, máquina: (GCC 4.5.2, -O3, x86-64, Intel Core i5 de 2867 Mhz).
Programa de prueba rápido y sucio:
Usando solo -O2, la diferencia se vuelve mayor. El árbol de decisiones es casi cuatro veces más rápido.
También comparé el código ingenuo de cambio de bits:
Esto solo es rápido para números pequeños, como era de esperar. Al determinar que el bit más alto es 1 para n == 1, se comparó más de un 80% más rápido. Sin embargo, la mitad de los números elegidos al azar en el espacio de 63 bits tienen el bit 63 configurado.
En la entrada 0x3FFFFFFFFFFFFFFF, la versión del árbol de decisión es bastante más rápida que en 1, y muestra ser 1120% más rápida (12,2 veces) que el bit shifter.
También compararé el árbol de decisiones con las incorporaciones de GCC y también probaré una combinación de entradas en lugar de repetirlas con el mismo número. Puede haber alguna predicción de rama pegajosa y quizás algunos escenarios de almacenamiento en caché poco realistas que lo hacen artificialmente más rápido en las repeticiones.
fuente
Qué pasa
?
fuente
1 registro, 13 instrucciones. Lo crea o no, esto suele ser más rápido que la instrucción BSR mencionada anteriormente, que opera en tiempo lineal. Este es el tiempo logarítmico.
De http://aggregate.org/MAGIC/#Most%20Significant%201%20Bit
fuente
__builtin_clz
si está habilitado con-march=native
o algo (ya que es rápido en todas las CPU que lo admiten). Incluso en CPU como la familia AMD Bulldozer donde BSR es "lento", no es tan lento: 7 m-ops con 4 ciclos de latencia y uno por 4c de rendimiento. En Atom, BSR es realmente lento: 16 ciclos. En Silvermont, son 10 uops con 10 ciclos de latencia. Esto podría ser una latencia ligeramente menor que BSR en Silvermont, pero IDK.Aquí hay algunos puntos de referencia (simples) de algoritmos que se dan actualmente en esta página ...
Los algoritmos no se han probado en todas las entradas de unsigned int; así que verifica eso primero, antes de usar algo a ciegas;)
En mi máquina, clz (__builtin_clz) y asm funcionan mejor. asm parece incluso más rápido que clz ... pero podría deberse al simple punto de referencia ...
fuente
Aunque probablemente solo usaría este método si necesitara absolutamente el mejor rendimiento posible (por ejemplo, para escribir algún tipo de IA de juego de mesa que involucre bitboards), la solución más eficiente es usar ASM en línea. Consulte la sección Optimizaciones de esta publicación de blog para obtener código con una explicación.
fuente
Necesitaba una rutina para hacer esto y antes de buscar en la web (y encontrar esta página) se me ocurrió mi propia solución basada en una búsqueda binaria. ¡Aunque estoy seguro de que alguien ha hecho esto antes! Se ejecuta en tiempo constante y puede ser más rápido que la solución "obvia" publicada, aunque no estoy haciendo grandes afirmaciones, solo publico por interés.
fuente
eso es algún tipo de búsqueda binaria, funciona con todo tipo de tipos enteros (¡sin firmar!)
para completar:
fuente
typedef
s ni nada excepto macros de preprocesador. Esta es una convención ampliamente aceptada.Algunas respuestas demasiado complejas aquí. La técnica Debruin solo debe usarse cuando la entrada ya es una potencia de dos, de lo contrario, hay una mejor manera. Para una potencia de 2 entradas, Debruin es absolutamente más rápido, incluso más rápido que
_BitScanReverse
en cualquier procesador que haya probado. Sin embargo, en el caso general,_BitScanReverse
(o como se llame al intrínseco en su compilador) es el más rápido (aunque en ciertas CPU se puede microcodificar).Si la función intrínseca no es una opción, aquí hay una solución de software óptima para procesar entradas generales.
Tenga en cuenta que esta versión no requiere una búsqueda de Debruin al final, a diferencia de la mayoría de las otras respuestas. Calcula la posición en su lugar.
Sin embargo, las tablas pueden ser preferibles, si las llama repetidamente suficientes veces, el riesgo de una falla de caché se ve eclipsado por la aceleración de una tabla.
Esto debería producir el mayor rendimiento de cualquiera de las respuestas de software que se dan aquí, pero si solo lo llama ocasionalmente, prefiera una solución sin tablas como mi primer fragmento.
fuente
Como señalan las respuestas anteriores, hay varias formas de determinar el bit más significativo. Sin embargo, como también se señaló, es probable que los métodos sean exclusivos de los registros de 32 bits o de 64 bits. La página de bithacks de stanford.edu ofrece soluciones que funcionan tanto para la informática de 32 bits como de 64 bits. Con un poco de trabajo, se pueden combinar para proporcionar un enfoque de arquitectura cruzada sólido para obtener el MSB. La solución a la que llegué que compiló / funcionó en computadoras de 64 y 32 bits fue:
fuente
#ifdef BUILD_64
bandera? En cuyo caso no necesitaría redefinirse dentro del condicional.Una versión en C usando aproximaciones sucesivas:
Ventaja: el tiempo de ejecución es constante independientemente del número proporcionado, ya que el número de bucles es siempre el mismo. (4 bucles cuando se usa "unsigned int")
fuente
msb += (n>>msb) ? step : -step;
), es probable que más compiladores creen un ensamblaje sin ramificaciones, evitando predicciones erróneas de ramificaciones en cada paso ( stackoverflow.com/questions/11227809/… ).Sé que esta pregunta es muy antigua, pero después de haber implementado una función msb () , descubrí que la mayoría de las soluciones presentadas aquí y en otros sitios web no son necesariamente las más eficientes, al menos para mi definición personal de eficiencia (consulte también la Actualización a continuación ). Este es el por qué:
La mayoría de las soluciones (especialmente aquellas que emplean algún tipo de esquema de búsqueda binaria o el enfoque ingenuo que hace un escaneo lineal de derecha a izquierda) parecen descuidar el hecho de que para números binarios arbitrarios, no hay muchas que comiencen con una secuencia muy larga de ceros. De hecho, para cualquier ancho de bit, la mitad de todos los enteros comienzan con 1 y una cuarta parte comienza con 01 . ¿Ves a dónde voy? Mi argumento es que un escaneo lineal que comienza desde la posición de bit más significativa hasta la menos significativa (de izquierda a derecha) no es tan "lineal" como podría parecer a primera vista.
Se puede mostrar 1 , que para cualquier ancho de bit, el número promedio de bits que deben probarse es como máximo 2. Esto se traduce en una complejidad de tiempo amortizado de O (1) con respecto al número de bits (!) .
Por supuesto, el peor de los casos sigue siendo O (n) , peor que el O (log (n)) que se obtiene con los enfoques de búsqueda binaria, pero como hay tan pocos casos peores, son insignificantes para la mayoría de las aplicaciones ( Actualizar : no del todo: puede haber pocos, pero pueden ocurrir con una alta probabilidad; consulte la Actualización a continuación).
Aquí está el enfoque "ingenuo" que se me ocurrió, que al menos en mi máquina supera a la mayoría de los otros enfoques (los esquemas de búsqueda binaria para entradas de 32 bits siempre requieren log 2 (32) = 5 pasos, mientras que este algoritmo tonto requiere menos de 2 en promedio) - perdón por ser C ++ y no C puro:
Actualización : si bien lo que escribí aquí es perfectamente cierto paraenteros arbitrarios , donde cada combinación de bits es igualmente probable (mi prueba de velocidad simplemente midió cuánto tiempo tomó determinar el MSB para todos los enteros de 32 bits), enteros de la vida real, por que tal función será llamada, generalmente sigue un patrón diferente: en mi código, por ejemplo, esta función se usa para determinar si el tamaño de un objeto es una potencia de 2, o para encontrar la siguiente potencia de 2 mayor o igual que una tamaño del objeto . Supongo que la mayoría de las aplicaciones que utilizan MSB implican números que son mucho más pequeños que el número máximo que puede representar un entero (los tamaños de los objetos rara vez utilizan todos los bits en un tamaño_t). En este caso, mi solución funcionará peor que un enfoque de búsqueda binaria, por lo que probablemente debería preferirse este último, aunque mi solución será más rápida en todos los números enteros.
TL; DR: Los enteros de la vida real probablemente tendrán un sesgo hacia el peor de los casos de este algoritmo simple, lo que hará que su rendimiento sea peor al final, a pesar del hecho de que se amortiza O (1) para enteros verdaderamente arbitrarios.
1 El argumento es el siguiente (borrador): Sea n el número de bits (ancho de bits). Hay un total de 2 n enteros que se pueden representar con n bits. Existen 2 n - 1 enteros que comienzan con 1 (el primer 1 es fijo, los n - 1 bits restantes pueden ser cualquier cosa). Esos números enteros requieren solo una interacción del ciclo para determinar el MSB. Además, hay 2 n - 2 enteros que comienzan con 01 , que requieren 2 iteraciones, 2 n - 3 enteros que comienzan con 001 , que requieren 3 iteraciones, y así sucesivamente.
Si sumamos todas las iteraciones requeridas para todos los números enteros posibles y las dividimos por 2 n , el número total de números enteros, obtenemos el número promedio de iteraciones necesarias para determinar el MSB para enteros de n bits:
(1 * 2 norte - 1 + 2 * 2 norte - 2 + 3 * 2 norte - 3 + ... + norte) / 2 norte
Esta serie de iteraciones promedio es realmente convergente y tiene un límite de 2 para n hacia el infinito
Por lo tanto, el algoritmo ingenuo de izquierda a derecha tiene en realidad una complejidad de tiempo constante amortizada de O (1) para cualquier número de bits.
fuente
c99nos ha dado
log2
. Esto elimina la necesidad de todas laslog2
implementaciones especiales de salsa que ve en esta página. Puede utilizar lalog2
implementación del estándar así:Una
n
de las0UL
necesidades que protegerse también, porque:He escrito un ejemplo con ese cheque que establece arbitrariamente
Index
aULONG_MAX
aquí: https://ideone.com/u26vsilos estudio visualEl corolario de la única respuesta de gcc de ephemient es:
La documentación para
_BitScanReverse
estados queIndex
sea:En la práctica, he descubierto que si
n
es0UL
queIndex
se establece en0UL
, al igual que lo sería para unan
de1UL
. Pero lo único garantizado en la documentación en el caso de unn
de0UL
es que la devolución es:Por lo tanto, de manera similar a la
log2
implementación preferible anterior, el retorno debe verificarse estableciendoIndex
un valor marcado en este caso. He vuelto a escribir un ejemplo de usoULONG_MAX
de este valor de bandera aquí: http://rextester.com/GCU61409fuente
_BitScanReverse
devuelve 0 solo si la entrada fue0
. Esto es como laBSR
instrucción de x86 , que establece ZF basándose solo en la entrada, no en la salida. Es interesante que MS diga que los documentosindex
no se configuran cuando no1
se encuentra ningún bit; que también coincide con el comportamiento de x86 asmbsr
. (AMD lo documenta como dejar el registro de destino sin modificar en src = 0, pero Intel solo dice salida indefinida a pesar de que sus CPU implementan el comportamiento de dejar sin modificar). Esto es diferente a x86lzcnt
, que da32
por no encontrado._BitScanReverse
usa indexación basada en cero, por lo tanto, sin
es 1, entonces el índice del bit establecido es de hecho 0. Desafortunadamente, como dice sin
es 0, la salida también es 0 :( Esto significa que no hay forma de usar el retorno a distinguir entren
1 o 0. Eso es lo que estaba tratando de comunicar. ¿Crees que hay una mejor manera de decir esto?Index
. Ese no es el valor de retorno . Devuelve un valor booleano que es falso si la entrada fue cero (y esta es la razón por la que el índice se pasa por referencia en lugar de devolverse normalmente). godbolt.org/g/gQKJdE . Y lo verifiqué: a pesar de la redacción de los documentos de MS,_BitScanReverse
no deja Index sin configurarn==0
: solo obtiene el valor que estaba en el registro que usó. (Que en su caso fue probablemente el mismo registro que usóIndex
después, lo que le llevó a ver a0
).log2
desde C99.Piense en operadores bit a bit.
Entendí mal la pregunta la primera vez. Debería producir un int con el bit más a la izquierda establecido (los otros cero). Suponiendo que cmp se establezca en ese valor:
fuente
8
debería serCHAR_BIT
. Es muy poco probable que esta sea la forma más rápida, porque la predicción errónea de la rama ocurrirá al salir del bucle, a menos que se use con la misma entrada repetidamente. Además, para entradas pequeñas (muchos ceros), tiene que repetirse mucho. Esta es como la forma alternativa que usaría como versión fácil de verificar en una prueba unitaria para comparar con versiones optimizadas.Ampliando el punto de referencia de Josh ... se puede mejorar el clz de la siguiente manera
Con respecto al asm: tenga en cuenta que existen bsr y bsrl (esta es la versión "larga"). el normal podría ser un poco más rápido.
fuente
Tenga en cuenta que lo que está intentando hacer es calcular el entero log2 de un entero,
Observe que puede intentar buscar más de 1 bit a la vez.
Este enfoque utiliza una búsqueda binaria
Otro método de búsqueda binaria, quizás más legible,
Y como querrás probarlos,
fuente
Poner esto en 'otro' enfoque, parece ser diferente de otros que ya se han dado.
devuelve
-1
six==0
, de lo contrariofloor( log2(x))
(resultado máximo 31)Reduzca el problema de 32 a 4 bits, luego use una tabla. Quizás poco elegante, pero pragmático.
Esto es lo que uso cuando no quiero usar
__builtin_clz
debido a problemas de portabilidad.Para hacerlo más compacto, se podría usar un bucle para reducir, agregando 4 ar cada vez, máximo 7 iteraciones. O algún híbrido, como (para 64 bits): bucle para reducir a 8, prueba para reducir a 4.
fuente
Guau, eso fueron muchas respuestas. No lamento haber respondido una vieja pregunta.
Esta respuesta es bastante similar a otra respuesta ... bueno.
fuente
1<<k
es un buen toque. ¿Y las máscaras?(1 << (1<<k-1)-1<< (1<<k-1)
? (most optimal
? ¿Comparas un superlativo?)&
y&~
.) Puede reemplazar las constantes hexadecimales por similares((type)1<<(1<<k))-1<<(1<<k)
.El código:
O obtenga la parte entera de la instrucción FPU FYL2X (Y * Log2 X) configurando Y = 1
fuente
double
, lo que probablemente sea bueno si realmente almacena / recarga en lugar de juegos de palabras de otra manera, por ejemplo, con unamovq
instrucción como la que podría obtener aquí en x86.[7FFFFFFFFFFFFE00 - 7FFFFFFFFFFFFFFF]
.Otro cartel proporcionó una tabla de búsqueda utilizando una búsqueda de ancho de bytes . En caso de que desee obtener un poco más de rendimiento (a costa de 32 K de memoria en lugar de solo 256 entradas de búsqueda), aquí hay una solución que usa una tabla de búsqueda de 15 bits , en C # 7 para .NET .
Lo interesante es inicializar la tabla. Dado que es un bloque relativamente pequeño que queremos durante la vida útil del proceso, asigno memoria no administrada para esto usando
Marshal.AllocHGlobal
. Como puede ver, para obtener el máximo rendimiento, todo el ejemplo está escrito como nativo:La tabla requiere una inicialización única mediante el código anterior. Es de solo lectura, por lo que se puede compartir una única copia global para acceso simultáneo. Con esta tabla puede buscar rápidamente el registro de enteros 2 , que es lo que estamos buscando aquí, para todos los distintos anchos de enteros (8, 16, 32 y 64 bits).
Observe que la entrada de la tabla para
0
, el único entero para el que la noción de 'bit de conjunto más alto' no está definida, recibe el valor-1
. Esta distinción es necesaria para el manejo adecuado de las palabras superiores con valor 0 en el código siguiente. Sin más preámbulos, aquí está el código para cada una de las diversas primitivas enteras:Versión ulong (64 bits)
Versión uint (32 bits)
Varias sobrecargas por lo anterior
Esta es una solución completa y funcional que representa el mejor rendimiento en .NET 4.7.2 para numerosas alternativas que comparé con un arnés de prueba de rendimiento especializado. Algunos de estos se mencionan a continuación. Los parámetros de prueba fueron una densidad uniforme de todas las posiciones de 65 bits, es decir, 0 ... 31/63 más valor
0
(que produce el resultado -1). Los bits por debajo de la posición del índice de destino se completaron al azar. Las pruebas fueron solo x64 , modo de lanzamiento, con optimizaciones JIT habilitadas.Ese es el final de mi respuesta formal aquí; lo que sigue son algunas notas informales y enlaces al código fuente para candidatos de prueba alternativos asociados con la prueba que ejecuté para validar el rendimiento y la corrección del código anterior.
La versión proporcionada anteriormente, codificada como Tab16A, fue un ganador constante en muchas carreras. Estos diversos candidatos, en forma de trabajo activo / cero, se pueden encontrar aquí , aquí y aquí .
Es de destacar que el terrible rendimiento de
ntdll.dll!RtlFindMostSignificantBit
via P / Invoke:Es realmente una lástima, porque aquí está toda la función real:
No puedo imaginar el bajo rendimiento que se origina con estas cinco líneas, por lo que las penalizaciones de transición administrada / nativa deben ser las culpables. También me sorprendió que las pruebas realmente favorecieran las
short
tablas de búsqueda directa de 32 KB (y 64 KB) (16 bits) sobre las tablas de búsqueda de 128 bytes (y 256 bytes)byte
(8 bits). Pensé que lo siguiente sería más competitivo con las búsquedas de 16 bits, pero esta última superó consistentemente esto:Lo último que señalaré es que me sorprendió bastante que a mi método deBruijn no le fuera mejor. Este es el método que antes había estado usando de forma generalizada:
Hay mucha discusión sobre cuán superiores y geniales son los métodos deBruijn en esta pregunta SO , y tendía a estar de acuerdo. Mi especulación es que, si bien los métodos de tabla de búsqueda directa y deBruijn (que encontré que son los más rápidos) tienen que realizar una búsqueda de tabla, y ambos tienen una ramificación mínima, solo deBruijn tiene una operación de multiplicación de 64 bits. Solo probé las
IndexOfMSB
funciones aquí, no el deBruijn,IndexOfLSB
pero espero que este último tenga muchas más posibilidades, ya que tiene muchas menos operaciones (ver arriba), y es probable que continúe usándolo para LSB.fuente
Mi humilde método es muy simple:
MSB (x) = INT [Log (x) / Log (2)]
Traducción: El MSB de x es el valor entero de (logaritmo de base x dividido por el logaritmo de base 2).
Esto se puede adaptar fácil y rápidamente a cualquier lenguaje de programación. Pruébelo en su calculadora para comprobar por sí mismo que funciona.
fuente
int(math.log((1 << 48) - 1) / math.log(2))
es 48.Aquí hay una solución rápida para C que funciona en GCC y Clang ; listo para ser copiado y pegado.
Y una pequeña versión mejorada para C ++ .
El código asume que
value
no será así0
. Si desea permitir 0, debe modificarlo.fuente
Supongo que su pregunta es para un número entero (llamado v a continuación) y no un número entero sin signo.
Si desea que funcione sin tener en cuenta el signo, puede agregar un 'v << = 1;' adicional. antes del bucle (y cambie el valor r a 30 en consecuencia). Por favor avíseme si olvidé algo. No lo he probado pero debería funcionar bien.
fuente
v <<= 1
es un comportamiento indefinido (UB) cuandov < 0
.0x8000000
, tal vez te refieres a un 0 extra.