Los 8 bits que representan el número 7 se ven así:
00000111
Se establecen tres bits.
¿Cuáles son los algoritmos para determinar el número de bits establecidos en un entero de 32 bits?
algorithm
binary
bit-manipulation
hammingweight
iec10967
Matt Howells
fuente
fuente
Respuestas:
Esto se conoce como el ' Peso Hamming ', 'popcount' o 'adición lateral'.
El "mejor" algoritmo realmente depende de la CPU en la que se encuentre y cuál sea su patrón de uso.
Algunas CPU tienen una sola instrucción incorporada para hacerlo y otras tienen instrucciones paralelas que actúan en vectores de bits. Las instrucciones paralelas (como x86
popcnt
, en las CPU donde es compatible) seguramente serán las más rápidas. Algunas otras arquitecturas pueden tener una instrucción lenta implementada con un bucle microcodificado que prueba un bit por ciclo ( cita requerida ).Un método de búsqueda de tabla rellenado previamente puede ser muy rápido si su CPU tiene una memoria caché grande y / o está haciendo muchas de estas instrucciones en un ciclo cerrado. Sin embargo, puede sufrir debido al gasto de una 'falta de caché', donde la CPU tiene que recuperar parte de la tabla de la memoria principal. (Busque cada byte por separado para mantener la tabla pequeña).
Si sabe que sus bytes serán principalmente 0 o mayoritariamente 1, entonces existen algoritmos muy eficientes para estos escenarios.
Creo que un muy buen algoritmo de propósito general es el siguiente, conocido como 'paralelo' o 'algoritmo SWAR de precisión variable'. He expresado esto en un pseudo lenguaje similar a C, es posible que deba ajustarlo para que funcione para un lenguaje en particular (por ejemplo, usando uint32_t para C ++ y >>> en Java):
JavaScript: coaccionar a número entero con
|0
el rendimiento: cambiar la primera línea dei = (i|0) - ((i >> 1) & 0x55555555);
Este tiene el mejor comportamiento en el peor de los casos de cualquiera de los algoritmos discutidos, por lo que tratará de manera eficiente cualquier patrón de uso o valores que le arroje.
Cómo funciona este bithack SWAR:
El primer paso es una versión optimizada de enmascaramiento para aislar los bits pares / impares, cambiar para alinearlos y agregarlos. Esto efectivamente hace 16 adiciones separadas en acumuladores de 2 bits ( SWAR = SIMD dentro de un registro ). Al igual
(i & 0x55555555) + ((i>>1) & 0x55555555)
.El siguiente paso toma los pares / impares de esos 16x acumuladores de 2 bits y los agrega nuevamente, produciendo sumas de 8x 4 bits. La
i - ...
optimización no es posible esta vez, por lo que solo enmascara antes / después del cambio. Usar la misma0x33...
constante en ambas ocasiones en lugar de0xccc...
antes de cambiar es algo bueno cuando se compilan ISA que necesitan construir constantes de 32 bits en registros por separado.El paso final de cambiar y agregar se
(i + (i >> 4)) & 0x0F0F0F0F
amplía a 4x acumuladores de 8 bits. Se enmascara después de agregar en lugar de antes, porque el valor máximo en cualquier acumulador de 4 bits es4
, si se establecieron los 4 bits de los bits de entrada correspondientes. 4 + 4 = 8 que todavía cabe en 4 bits, por lo que es imposible llevar entre elementos de mordiscoi + (i >> 4)
.Hasta ahora, esto es SIMD bastante normal usando técnicas SWAR con algunas optimizaciones inteligentes. Continuar con el mismo patrón durante 2 pasos más puede ampliarse a 2x 16 bits y luego 1x 32 bits. Pero hay una forma más eficiente en máquinas con multiplicación rápida de hardware:
Una vez que tengamos suficientes "elementos", una multiplicación con una constante mágica puede sumar todos los elementos en el elemento superior . En este caso elementos de byte. La multiplicación se realiza desplazando a la izquierda y sumando, por lo que se multiplican los
x * 0x01010101
resultadosx + (x<<8) + (x<<16) + (x<<24)
. Nuestros elementos de 8 bits son lo suficientemente anchos (y tienen conteos lo suficientemente pequeños) que esto no produce acarreo en esos 8 bits superiores.Una versión de 64 bits de esto puede hacer elementos de 8x 8 bits en un entero de 64 bits con un multiplicador 0x0101010101010101, y extraer el byte alto con
>>56
. Por lo tanto, no requiere ningún paso adicional, solo constantes más amplias. Esto es lo que GCC utiliza__builtin_popcountll
en sistemas x86 cuando lapopcnt
instrucción de hardware no está habilitada. Si puede usar los componentes internos o intrínsecos para esto, hágalo para darle al compilador la oportunidad de realizar optimizaciones específicas de destino.Con SIMD completo para vectores más anchos (por ejemplo, contando una matriz completa)
Este algoritmo SWAR bit a bit podría paralelizarse para hacerse en múltiples elementos vectoriales a la vez, en lugar de en un solo registro de enteros, para acelerar las CPU con SIMD pero sin instrucción popcount utilizable. (por ejemplo, código x86-64 que debe ejecutarse en cualquier CPU, no solo Nehalem o posterior).
Sin embargo, la mejor manera de usar instrucciones de vectores para popcount es usualmente usando una combinación aleatoria variable para hacer una búsqueda en la tabla de 4 bits a la vez de cada byte en paralelo. (Los 4 bits indexan una tabla de 16 entradas contenida en un registro vectorial).
En las CPU Intel, la instrucción popcnt de hardware de 64 bits puede superar a una implementación SSSE3
PSHUFB
en paralelo en un factor de 2, pero solo si su compilador lo hace bien . De lo contrario, SSE puede salir significativamente adelante. Las versiones más recientes del compilador son conscientes del problema popcnt de dependencia falsa en Intel .Referencias
fuente
unsigned int
, para mostrar fácilmente que está libre de cualquier complicación de bit de signo. También seríauint32_t
más seguro, ya que, ¿obtienes lo que esperas en todas las plataformas?>>
está definida por la implementación para valores negativos. El argumento debe cambiarse (o convertirse) aunsigned
, y dado que el código es específico de 32 bits, probablemente debería estar usandouint32_t
.Considere también las funciones integradas de sus compiladores.
En el compilador de GNU, por ejemplo, puede usar:
En el peor de los casos, el compilador generará una llamada a una función. En el mejor de los casos, el compilador emitirá una instrucción de CPU para hacer el mismo trabajo más rápido.
Los intrínsecos de GCC incluso funcionan en múltiples plataformas. Popcount se convertirá en la corriente principal en la arquitectura x86, por lo que tiene sentido comenzar a usar lo intrínseco ahora. Otras arquitecturas tienen el popcount por años.
En x86, puede decirle al compilador que puede asumir el soporte para la
popcnt
instrucción-mpopcnt
o-msse4.2
también habilitar las instrucciones vectoriales que se agregaron en la misma generación. Ver las opciones de GCC x86 .-march=nehalem
(o-march=
cualquier CPU que desee que asuma y ajuste su código) podría ser una buena opción. Ejecutar el binario resultante en una CPU anterior dará como resultado un error de instrucción ilegal.Para hacer binarios optimizados para la máquina en la que los construye, use
-march=native
(con gcc, clang o ICC).MSVC proporciona un intrínseco para la
popcnt
instrucción x86 , pero a diferencia de gcc, es realmente intrínseco para la instrucción de hardware y requiere soporte de hardware.Usando en
std::bitset<>::count()
lugar de un incorporadoEn teoría, cualquier compilador que sepa explotar eficientemente para la CPU de destino debería exponer esa funcionalidad a través de ISO C ++
std::bitset<>
. En la práctica, podría ser mejor con el bit-hack AND / shift / ADD en algunos casos para algunas CPU de destino.Para las arquitecturas de destino donde el popcount de hardware es una extensión opcional (como x86), no todos los compiladores tienen una
std::bitset
ventaja que se aprovecha cuando está disponible. Por ejemplo, MSVC no tiene forma de habilitar elpopcnt
soporte en tiempo de compilación, y siempre usa una búsqueda de tabla , incluso con/Ox /arch:AVX
(lo que implica SSE4.2, aunque técnicamente hay un bit de función separado parapopcnt
).Pero al menos obtienes algo portátil que funciona en todas partes, y con gcc / clang con las opciones de destino correctas, obtienes una cuenta de hardware para arquitecturas que lo admiten.
Vea asm de gcc, clang, icc y MSVC en el explorador del compilador Godbolt.
x86-64
gcc -O3 -std=gnu++11 -mpopcnt
emite esto:gcc -O3 -std=gnu++11
Emite PowerPC64 (para laint
versión arg):Esta fuente no es específica de x86 o específica de GNU, pero solo se compila bien para x86 con gcc / clang / icc.
También tenga en cuenta que el respaldo de gcc para arquitecturas sin popcount de instrucción única es una búsqueda de tabla byte-at-a-time. Esto no es maravilloso para ARM, por ejemplo .
fuente
std::bitset::count
. después de incluir esto, se compila en una sola__builtin_popcount
llamada.En mi opinión, la "mejor" solución es la que puede leer otro programador (o el programador original dos años después) sin comentarios copiosos. Es posible que desee la solución más rápida o inteligente que algunos ya han proporcionado, pero prefiero la legibilidad a la inteligencia en cualquier momento.
Si desea más velocidad (y suponiendo que la documente bien para ayudar a sus sucesores), puede usar una búsqueda de tabla:
Aunque estos se basan en tamaños de tipo de datos específicos, por lo que no son tan portátiles. Pero, dado que muchas optimizaciones de rendimiento no son portátiles de todos modos, eso puede no ser un problema. Si desea portabilidad, me quedaría con la solución legible.
fuente
if ((value & 1) == 1) { count++; }
concount += value & 1
?De Hacker's Delight, pág. 66, figura 5-2
Se ejecuta en ~ 20-ish instrucciones (dependiente del arco), sin ramificación.
Hacker's Delight es una delicia! Muy recomendable.
fuente
Integer.bitCount(int)
usa esta misma implementación exacta.pop
lugar depopulation_count
(opop_cnt
si debe tener una abreviatura). @MarcoBolis Supongo que será cierto para todas las versiones de Java, pero oficialmente dependería de la implementación :)Creo que la forma más rápida, sin usar tablas de búsqueda y popcount, es la siguiente. Cuenta los bits establecidos con solo 12 operaciones.
Funciona porque puede contar el número total de bits establecidos dividiendo en dos mitades, contando el número de bits establecidos en ambas mitades y luego sumando. También se conoce como
Divide and Conquer
paradigma. Vamos a entrar en detalles ...El número de bits en dos bits puede ser
0b00
,0b01
o0b10
. Vamos a tratar de resolver esto en 2 bits.Esto es lo que se requería: la última columna muestra el recuento de bits establecidos en cada par de dos bits. Si el número dos bits es
>= 2 (0b10)
entoncesand
produce0b01
, de lo que produce0b00
.Esta declaración debe ser fácil de entender. Después de la primera operación tenemos el recuento de bits establecidos en cada dos bits, ahora sumamos ese recuento en cada 4 bits.
Luego resumimos el resultado anterior, dándonos el recuento total de bits establecidos en 4 bits. La última declaración es la más complicada.
Vamos a desglosarlo aún más ...
Es similar a la segunda declaración; Estamos contando los bits establecidos en grupos de 4 en su lugar. Sabemos, debido a nuestras operaciones anteriores, que cada mordisco tiene la cuenta de bits establecidos. Veamos un ejemplo. Supongamos que tenemos el byte
0b01000010
. Significa que el primer mordisco tiene su conjunto de 4 bits y el segundo tiene su conjunto de 2 bits. Ahora sumamos esos mordiscos juntos.Nos da el recuento de bits establecidos en un byte, en el primer mordisco
0b01100010
y, por lo tanto, enmascaramos los últimos cuatro bytes de todos los bytes del número (descartándolos).Ahora cada byte tiene el recuento de bits establecidos en él. Necesitamos sumarlos todos juntos. El truco consiste en multiplicar el resultado por el
0b10101010
que tiene una propiedad interesante. Si nuestro número tiene cuatro bytes,A B C D
dará como resultado un nuevo número con estos bytesA+B+C+D B+C+D C+D D
. Un número de 4 bytes puede tener un máximo de 32 bits establecido, que se puede representar como0b00100000
.Todo lo que necesitamos ahora es el primer byte que tiene la suma de todos los bits establecidos en todos los bytes, y lo obtenemos
>> 24
. Este algoritmo fue diseñado para32 bit
palabras pero puede modificarse fácilmente para64 bit
palabras.fuente
c =
trata? Parece que se debe eliminar. Además, sugiera un conjunto de pares extra A "(((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24" para evitar algunas advertencias clásicas.popcount(int v)
ypopcount(unsigned v)
. Para portabilidad, considerepopcount(uint32_t v)
, etc. Realmente me gusta la parte * 0x1010101.return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
así que no necesitamos contar letras para ver lo que realmente está haciendo (dado que descartó la primera0
, accidentalmente pensé que usó el patrón de bits incorrecto (volteado) como máscara - Eso es hasta que noté que solo hay 7 letras y no 8).Me aburrí y cronometré mil millones de iteraciones de tres enfoques. El compilador es gcc -O3. CPU es lo que sea que pusieron en el Macbook Pro de primera generación.
La más rápida es la siguiente, con 3,7 segundos:
El segundo lugar va al mismo código pero buscando 4 bytes en lugar de 2 medias palabras. Eso tomó alrededor de 5,5 segundos.
El tercer lugar es para el enfoque de 'adición lateral', que tardó 8,6 segundos.
El cuarto lugar es para __builtin_popcount () de GCC, con 11 segundos vergonzosos.
El enfoque de contar un bit a la vez fue muuuucho más lento, y me aburrí de esperar a que se completara.
Entonces, si le importa el rendimiento por encima de todo, utilice el primer enfoque. Si le importa, pero no lo suficiente como para gastar 64Kb de RAM, use el segundo enfoque. De lo contrario, utilice el enfoque legible (pero lento) de un bit a la vez.
Es difícil pensar en una situación en la que desee utilizar el enfoque de giro de bits.
Editar: resultados similares aquí .
fuente
Si está utilizando Java, el método incorporado
Integer.bitCount
lo hará.fuente
Déjame explicarte este algoritmo.
Este algoritmo se basa en el algoritmo de división y conquista. Supongamos que hay un número entero de 8 bits 213 (11010101 en binario), el algoritmo funciona así (cada vez que combina dos bloques vecinos):
fuente
Esta es una de esas preguntas en las que es útil conocer su microarquitectura. Acabo de cronometrar dos variantes en gcc 4.3.3 compiladas con -O3 usando líneas en C ++ para eliminar la sobrecarga de llamadas a funciones, mil millones de iteraciones, manteniendo la suma de todos los conteos para asegurar que el compilador no elimine nada importante, usando rdtsc para el tiempo ( ciclo de reloj preciso).
El Hacker's Delight no modificado tomó 12,2 gigaciclos. Mi versión paralela (contando el doble de bits) se ejecuta en 13.0 gigaciclos. Transcurrieron 10.5s en total para ambos juntos en un Core Duo de 2.4GHz. 25 gigaciclos = poco más de 10 segundos a esta frecuencia de reloj, así que estoy seguro de que mis tiempos son correctos.
Esto tiene que ver con las cadenas de dependencia de instrucciones, que son muy malas para este algoritmo. Casi podría duplicar la velocidad nuevamente usando un par de registros de 64 bits. De hecho, si fuera inteligente y añadiera x + ya un poco antes, podría reducir algunos cambios. La versión de 64 bits con algunos pequeños ajustes saldría parejo, pero volvería a contar el doble de bits.
Con registros SIMD de 128 bits, otro factor más de dos, y los conjuntos de instrucciones SSE a menudo también tienen atajos inteligentes.
No hay razón para que el código sea especialmente transparente. La interfaz es simple, el algoritmo puede ser referenciado en línea en muchos lugares, y es susceptible de una prueba de unidad integral. El programador que se topa con él podría incluso aprender algo. Estas operaciones de bits son extremadamente naturales a nivel de máquina.
OK, decidí probar la versión modificada de 64 bits. Para este un tamaño de (sin firmar largo) == 8
Eso parece correcto (aunque no estoy probando con cuidado). Ahora los tiempos salen en 10.70 gigacycles / 14.1 gigacycles. Ese número posterior sumó 128 mil millones de bits y corresponde a 5.9s transcurridos en esta máquina. La versión no paralela se acelera un poco porque estoy corriendo en modo de 64 bits y le gustan los registros de 64 bits un poco mejor que los registros de 32 bits.
Veamos si hay un poco más de tubería de OOO aquí. Esto fue un poco más complicado, así que en realidad lo probé un poco. Cada término solo suma 64, todos combinados suman 256.
Estuve emocionado por un momento, pero resulta que gcc está jugando trucos en línea con -O3 aunque no estoy usando la palabra clave en línea en algunas pruebas. Cuando dejé que gcc jugara trucos, mil millones de llamadas a pop4 () toma 12.56 gigaciclos, pero determiné que estaba doblando argumentos como expresiones constantes. Un número más realista parece ser 19.6 gc para otro 30% de aceleración. Mi ciclo de prueba ahora se ve así, asegurándome de que cada argumento sea lo suficientemente diferente como para evitar que gcc juegue trucos.
256 mil millones de bits sumados en 8.17s transcurridos. Funciona a 1.02s para 32 millones de bits como referencia en la búsqueda de tabla de 16 bits. No se puede comparar directamente, porque el otro banco no da una velocidad de reloj, pero parece que he sacado el moco de la edición de tabla de 64 KB, que es un uso trágico de la caché L1 en primer lugar.
Actualización: decidió hacer lo obvio y crear pop6 () agregando cuatro líneas duplicadas más. Salió a 22.8 gc, 384 mil millones de bits sumados en 9.5s transcurridos. Entonces hay otro 20% ahora a 800ms por 32 mil millones de bits.
fuente
¿Por qué no dividir iterativamente por 2?
Estoy de acuerdo en que este no es el más rápido, pero el "mejor" es algo ambiguo. Yo diría que "lo mejor" debería tener un elemento de claridad
fuente
El giro de bits del Hacker's Delight se vuelve mucho más claro cuando escribes los patrones de bits.
El primer paso agrega los bits pares a los bits impares, produciendo una suma de bits en cada dos. Los otros pasos agregan fragmentos de orden superior a fragmentos de orden bajo, duplicando el tamaño del fragmento hasta el final, hasta que el conteo final ocupe todo el int.
fuente
Para un medio feliz entre una tabla de búsqueda 2 32 e iterar a través de cada bit individualmente:
De http://ctips.pbwiki.com/CountBits
fuente
Esto se puede hacer en
O(k)
, dondek
es el número de bits establecido.fuente
n &= (n-1)
.No es la mejor solución ni la más rápida, pero encontré la misma pregunta en mi camino y comencé a pensar y pensar. Finalmente, me di cuenta de que se puede hacer así si obtiene el problema desde el lado matemático y dibuja un gráfico, luego descubre que es una función que tiene una parte periódica, y luego se da cuenta de la diferencia entre los períodos ... aqui tienes:
fuente
def f(i, d={0:lambda:0, 1:lambda:1, 2:lambda:1, 3:lambda:2}): return d.get(i, lambda: f(i//4) + f(i%4))()
La función que busca a menudo se denomina "suma lateral" o "recuento de población" de un número binario. Knuth lo analiza en el pre-Fascículo 1A, pp11-12 (aunque hubo una breve referencia en el Volumen 2, 4.6.3- (7)).
El locus classicus es el artículo de Peter Wegner "Una técnica para contar unos en una computadora binaria", de Communications of the ACM , Volumen 3 (1960) Número 5, página 322 . Da dos algoritmos diferentes allí, uno optimizado para los números que se espera que sean "escasos" (es decir, que tengan un pequeño número de unos) y otro para el caso contrario.
fuente
fuente
Pocas preguntas abiertas: -
podemos modificar el algo para admitir el número negativo de la siguiente manera:
ahora para superar el segundo problema podemos escribir algo como:
para referencia completa ver:
http://goursaha.freeoda.com/Miscellaneous/IntegerBitCount.html
fuente
Creo que el método de Brian Kernighan también será útil ... Pasa por tantas iteraciones como bits establecidos. Entonces, si tenemos una palabra de 32 bits con solo el conjunto de bits alto, entonces solo pasará una vez por el ciclo.
fuente
Yo uso el siguiente código que es más intuitivo.
Lógica: n & (n-1) restablece el último bit establecido de n.
PD: Sé que esto no es una solución O (1), aunque es una solución interesante.
fuente
O(ONE-BITS)
. De hecho, es O (1) ya que hay como máximo 32 bits de un bit.¿Qué quieres decir con "Mejor algoritmo"? ¿El código en corto o el código en ayunas? Su código se ve muy elegante y tiene un tiempo de ejecución constante. El código también es muy corto.
Pero si la velocidad es el factor principal y no el tamaño del código, creo que lo siguiente puede ser más rápido:
Creo que esto no será más rápido para un valor de 64 bits, pero un valor de 32 bits puede ser más rápido.
fuente
Escribí una macro de conteo de bits rápido para máquinas RISC alrededor de 1990. No utiliza aritmética avanzada (multiplicación, división,%), recuperaciones de memoria (demasiado lenta), ramas (demasiado lenta), pero asume que la CPU tiene un Desplazador de barril de 32 bits (en otras palabras, >> 1 y >> 32 toman la misma cantidad de ciclos). Se supone que las constantes pequeñas (como 6, 12, 24) no cuestan nada cargar en los registros, o se almacenan en temporarios y reutilizados una y otra vez.
Con estos supuestos, cuenta 32 bits en aproximadamente 16 ciclos / instrucciones en la mayoría de las máquinas RISC. Tenga en cuenta que 15 instrucciones / ciclos está cerca de un límite inferior en el número de ciclos o instrucciones, porque parece tomar al menos 3 instrucciones (máscara, turno, operador) para reducir el número de sumandos a la mitad, por lo que log_2 (32) = 5, 5 x 3 = 15 instrucciones es un cuasi-inferior.
Aquí hay un secreto para el primer y más complejo paso:
así que si tomo la primera columna (A) arriba, la desplazo a la derecha 1 bit y la resto de AB, obtengo la salida (CD). La extensión a 3 bits es similar; puede verificarlo con una tabla booleana de 8 filas como la mía anterior si lo desea.
fuente
Si está utilizando C ++, otra opción es utilizar la metaprogramación de plantilla:
el uso sería:
por supuesto, podría ampliar aún más esta plantilla para usar diferentes tipos (incluso el tamaño de bits de autodetección) pero lo he mantenido simple para mayor claridad.
editar: olvidé mencionar que esto es bueno porque debería funcionar en cualquier compilador de C ++ y, básicamente, simplemente desenrolla el bucle si se usa un valor constante para el conteo de bits (en otras palabras, estoy bastante seguro de que es el método general más rápido encontrarás)
fuente
constexpr
Aunque podría ser bueno .Me gusta especialmente este ejemplo del archivo de la fortuna:
¡Me gusta más porque es muy bonita!
fuente
Java JDK1.5
Integer.bitCount (n);
donde n es el número cuyos 1 se deben contar.
comprobar también
fuente
Encontré una implementación de conteo de bits en una matriz usando instrucciones SIMD (SSSE3 y AVX2). Tiene un rendimiento 2-2.5 veces mejor que si usara la función intrínseca __popcnt64.
Versión SSSE3:
Versión AVX2:
fuente
Siempre uso esto en programación competitiva y es fácil de escribir y eficiente:
fuente
Hay muchos algoritmos para contar los bits establecidos; ¡Pero creo que el mejor es el más rápido! Puedes ver lo detallado en esta página:
Bit Twiddling Hacks
Sugiero este:
Contando bits establecidos en palabras de 14, 24 o 32 bits utilizando instrucciones de 64 bits
Este método requiere una CPU de 64 bits con división rápida de módulo para ser eficiente. La primera opción solo requiere 3 operaciones; la segunda opción toma 10; y la tercera opción toma 15.
fuente
Solución rápida de C # que utiliza una tabla precalculada de recuentos de bits de bytes con ramificación en el tamaño de entrada.
fuente
(0xe994 >>(k*2))&3
, sin acceso a la memoria ...Aquí hay un módulo portátil (ANSI-C) que puede comparar cada uno de sus algoritmos en cualquier arquitectura.
¿Tu CPU tiene bytes de 9 bits? No hay problema :-) Por el momento implementa 2 algoritmos, el algoritmo K&R y una tabla de búsqueda de bytes. La tabla de búsqueda es en promedio 3 veces más rápida que el algoritmo K&R. Si alguien puede encontrar una manera de hacer que el algoritmo "Hacker's Delight" sea portátil, no dude en agregarlo.
.
fuente
lo que puedes hacer es
La lógica detrás de esto es que los bits de n-1 se invierten del bit establecido más a la derecha de n. si n = 6, es decir, 110, entonces 5 es 101, los bits se invierten del bit establecido más a la derecha de n. así que si nosotros y estos dos haremos el bit 0 más a la derecha en cada iteración y siempre vamos al siguiente bit establecido más a la derecha. Por lo tanto, contando el bit establecido. La peor complejidad de tiempo será O (log) cuando cada bit esté configurado.
fuente