2011-10-05 10 views
15

En lugar de solo el bit más bajo establecido, quiero encontrar la posición del n el bit más bajo establecido. (Estoy NO hablando de valor en la posición de bit n º)Encontrar nth bit SET en un int

Por ejemplo, digamos que tengo:
0000 1101 1000 0100 1100 1000 1010 0000

Y quiero encontrar el cuarto bit que se establece. Entonces quiero que vuelva:
0000 0000 0000 0000 0100 0000 0000 0000

Si popcnt(v) < n, que tendría sentido si esta función devuelve 0, pero cualquier comportamiento para este caso es aceptable para mí.

Estoy buscando algo más rápido que un bucle si es posible.

+0

¿Usted está pidiendo un método general que se puede aplicar para darle una forma de calcular el enésimo bit más bajo para cualquier constante n, o necesita que funcione para cualquier n dada en tiempo de ejecución? Basado en el patrón de reducción de máscara de este tipo de ataques, dudo seriamente que haya una manera elegante de hacer lo último sin una construcción de bucle. –

+1

Sí, proporciona tanto v como n en tiempo de ejecución. Tampoco podía pensar en ninguna forma de hacerlo sin bucles. Es difícil dividir el problema, pero no estoy seguro de que sea imposible superarlo. – VoidStar

Respuesta

10

Resulta que de hecho es posible hacerlo sin bucles. Es más rápido precomputar la versión (por lo menos) de 8 bits de este problema. Por supuesto, estas tablas usan espacio de caché, pero aún debería haber una aceleración neta en prácticamente todos los escenarios modernos de PC. En este código, n = 0 devuelve el bit menos fijar, n = 1 es segundo-a-menos, etc.

Solución con __popcnt

Hay una solución usando el __popcnt intrínseca (que necesita __popcnt ser extremadamente rápido o cualquier ganancia de rendimiento sobre una simple solución de bucle será discutible. Afortunadamente, la mayoría de los procesadores de era SSE4 + lo soportan).

// lookup table for sub-problem: 8-bit v 
byte PRECOMP[256][8] = { .... } // PRECOMP[v][n] for v < 256 and n < 8 

ulong nthSetBit(ulong v, ulong n) { 
    ulong p = __popcnt(v & 0xFFFF); 
    ulong shift = 0; 
    if (p <= n) { 
     v >>= 16; 
     shift += 16; 
     n -= p; 
    } 
    p = __popcnt(v & 0xFF); 
    if (p <= n) { 
     shift += 8; 
     v >>= 8; 
     n -= p; 
    } 

    if (n >= 8) return 0; // optional safety, in case n > # of set bits 
    return PRECOMP[v & 0xFF][n] << shift; 
} 

Esto ilustra cómo funciona el enfoque de dividir y conquistar.

Solución general

También hay una solución para architectures- "general" sin __popcnt. Se puede hacer procesando en fragmentos de 8 bits. Es necesario un mayor tabla de búsqueda que le indica la POPCNT de un byte:

byte PRECOMP[256][8] = { .... } // PRECOMP[v][n] for v<256 and n < 8 
byte POPCNT[256] = { ... } // POPCNT[v] is the number of set bits in v. (v < 256) 

ulong nthSetBit(ulong v, ulong n) { 
    ulong p = POPCNT[v & 0xFF]; 
    ulong shift = 0; 
    if (p <= n) { 
     n -= p; 
     v >>= 8; 
     shift += 8; 
     p = POPCNT[v & 0xFF]; 
     if (p <= n) { 
      n -= p; 
      shift += 8; 
      v >>= 8; 
      p = POPCNT[v & 0xFF]; 
      if (p <= n) { 
       n -= p; 
       shift += 8; 
       v >>= 8; 
      } 
     } 
    } 

    if (n >= 8) return 0; // optional safety, in case n > # of set bits 
    return PRECOMP[v & 0xFF][n] << shift; 
} 

Esto podría, por supuesto, hacerse con un bucle, pero la forma desenrollada es más rápido y la forma inusual del lazo haría que poco probable que el compilador pueda desenrollarlo automáticamente.

+0

¡Bien hecho! ¿Tiene ganas de publicar algunas estadísticas de tiempo de ejecución de los diferentes métodos en este hilo? –

+0

¿Es posible hacer esto sin ninguna condición? – markt1964

+1

Se implementa un algoritmo ligeramente más rápido en [it.unimi.dsi.bits.Fast.select] (http://grepcode.com/file/repo1.maven.org/maven2/it.unimi.dsi/dsiutils/2.0. 15/it/unimi/dsi/bits/Fast.java # 301). –

1

No puedo ver un método sin un bucle, lo que me viene a la mente sería;

int set = 0; 
int pos = 0; 
while(set < n) { 
    if((bits & 0x01) == 1) set++; 
    bits = bits >> 1; 
    pos++; 
} 

después de lo cual, pos sería mantener la posición del bit puesto enésimo de menor valor.

La única otra cosa que puedo pensar sería un enfoque de dividir y conquistar, que podría dar O (log (n)) en lugar de O (n) ... pero probablemente no.

Editar: ha dicho cualquier comportamiento, por lo que la no terminación está bien, ¿verdad? : P

1

Bit Twiddling Hacks

edición: no hay una respuesta concreta a este problema, pero no son todos los bloques buildign necesarios para resolverlo.

9

v-1 tiene un cero donde v tiene su bit "uno" menos significativo, mientras que todos los bits más significativos son iguales. Esto conduce a la siguiente función:

int ffsn(unsigned int v, int n) { 
    for (int i=0; i<n-1; i++) { 
     v &= v-1; // remove the least significant bit 
    } 
    return v & ~(v-1); // extract the least significant bit 
} 
+0

(¿qué significa 'f's en' ffsn() '?) (El tuyo puede ser más legible que' while (0 <--n) ') – greybeard

1
def bitN (l: Long, i: Int) : Long = { 
    def bitI (l: Long, i: Int) : Long = 
    if (i == 0) 1L else 
    2 * { 
     if (l % 2 == 0) bitI (l/2, i) else bitI (l /2, i-1) 
    } 
    bitI (l, i)/2 
} 

un método recursivo (en Scala). Disminuir i, la posición, si un módulo2 es 1. Mientras regresas, multiplicar por 2. Dado que la multiplicación se utiliza como última operación, no es cola recursiva, pero como los largos son de un tamaño conocido por adelantado, la pila máxima no es demasiado grande.

scala> n.toBinaryString.replaceAll ("(.{8})", "$1 ") 
res117: java.lang.String = 10110011 11101110 01011110 01111110 00111101 11100101 11101011 011000 

scala> bitN (n, 40) .toBinaryString.replaceAll ("(.{8})", "$1 ") 
res118: java.lang.String = 10000000 00000000 00000000 00000000 00000000 00000000 00000000 000000 
19

Hoy en día esto es muy fácil con PDEP desde BMI2 instruction set. Aquí está una versión de 64 bits con algunos ejemplos:

#include <cassert> 
#include <cstdint> 
#include <x86intrin.h> 

inline uint64_t nthset(uint64_t x, unsigned n) { 
    return _pdep_u64(1ULL << n, x); 
} 

int main() { 
    assert(nthset(0b0000'1101'1000'0100'1100'1000'1010'0000, 0) == 
        0b0000'0000'0000'0000'0000'0000'0010'0000); 
    assert(nthset(0b0000'1101'1000'0100'1100'1000'1010'0000, 1) == 
        0b0000'0000'0000'0000'0000'0000'1000'0000); 
    assert(nthset(0b0000'1101'1000'0100'1100'1000'1010'0000, 3) == 
        0b0000'0000'0000'0000'0100'0000'0000'0000); 
    assert(nthset(0b0000'1101'1000'0100'1100'1000'1010'0000, 9) == 
        0b0000'1000'0000'0000'0000'0000'0000'0000); 
    assert(nthset(0b0000'1101'1000'0100'1100'1000'1010'0000, 10) == 
        0b0000'0000'0000'0000'0000'0000'0000'0000); 
} 
+2

Cool, por lo que utiliza la cadena de bits original para los índices de depósito, y luego suministra una cadena de bits donde exactamente se establece el único bit n de orden inferior. –

+0

Muchas gracias por esto. ¡Es asombroso! ¿Cómo llegó esta idea? – alecco

+0

¡Muy bueno! Quizás valga la pena mencionar que la corrección de la solución se vuelve mucho más evidente cuando se consulta la descripción visual de PDEP/PEXT (se puede ver en la documentación de Intel) – damageboy

0

Sobre la base de la respuesta dada por Jukka Suomela, que utiliza una instrucción específica de la máquina que no necesariamente estén disponibles, también es posible escribir una función que hace exactamente lo mismo que _pdep_u64 sin ninguna dependencia de la máquina. Debe recorrer los bits establecidos en uno de los argumentos, pero todavía se puede describir como una función constexpr para C++ 11.

constexpr inline uint64_t deposit_bits(uint64_t x, uint64_t mask, uint64_t b, uint64_t res) { 
    return mask != 0 ? deposit_bits(x, mask & (mask - 1), b << 1, ((x & b) ? (res | (mask & (-mask))) : res)) : res; 
} 

constexpr inline uint64_t nthset(uint64_t x, unsigned n) { 
    return deposit_bits(1ULL << n, x, 1, 0); 
} 
Cuestiones relacionadas