Vi una técnica interesante utilizada en una respuesta a otra pregunta , y me gustaría entenderla un poco mejor.
Se nos da un entero de 64 bits sin signo, y estamos interesados en los siguientes bits:
1.......2.......3.......4.......5.......6.......7.......8.......
Específicamente, nos gustaría moverlos a las ocho primeras posiciones, así:
12345678........................................................
No nos importa el valor de los bits indicados por .
, y no es necesario preservarlos.
La solución fue enmascarar los bits no deseados y multiplicar el resultado por 0x2040810204081
. Esto, como resulta, hace el truco.
¿Qué tan general es este método? ¿Se puede usar esta técnica para extraer algún subconjunto de bits? Si no es así, ¿cómo se puede determinar si el método funciona o no para un conjunto particular de bits?
Finalmente, ¿cómo se podría encontrar el (a?) Multiplicador correcto para extraer los bits dados?
Respuestas:
Pregunta muy interesante y truco inteligente.
Veamos un ejemplo simple de cómo manipular un solo byte. Uso de 8 bits sin signo para simplificar. Imagina que tu número es
xxaxxbxx
y quieresab000000
.La solución consistió en dos pasos: un poco de enmascaramiento, seguido de multiplicación. La máscara de bits es una operación AND simple que convierte bits poco interesantes en ceros. En el caso anterior, su máscara sería
00100100
y el resultado00a00b00
.Ahora la parte difícil: convertir eso en
ab......
.Una multiplicación es un montón de operaciones shift-and-add. La clave es permitir que el desbordamiento "aleje" los bits que no necesitamos y coloque los que queremos en el lugar correcto.
La multiplicación por 4 (
00000100
) desplazaría todo a la izquierda por 2 y lo llevaría aa00b0000
. Parab
subir, necesitamos multiplicar por 1 (para mantener la a en el lugar correcto) + 4 (para mover la b hacia arriba). Esta suma es 5, y combinada con las 4 anteriores obtenemos un número mágico de 20, o00010100
. El original fue00a00b00
después del enmascaramiento; la multiplicación da:Desde este enfoque puede extenderse a números más grandes y más bits.
Una de las preguntas que hizo fue "¿se puede hacer esto con cualquier número de bits?" Creo que la respuesta es "no", a menos que permita varias operaciones de enmascaramiento o varias multiplicaciones. El problema es la cuestión de las "colisiones", por ejemplo, la "calle b" en el problema anterior. Imagina que necesitamos hacer esto a un número como
xaxxbxxcx
. Siguiendo el enfoque anterior, pensaría que necesitamos {x 2, x {1 + 4 + 16}} = x 42 (oooh, ¡la respuesta a todo!). Resultado:Como puede ver, todavía funciona, pero "solo". La clave aquí es que hay "suficiente espacio" entre los bits que queremos para poder exprimir todo. No pude agregar un cuarto bit d justo después de c, porque obtendría instancias donde obtengo c + d, los bits podrían transportar, ...
Entonces, sin una prueba formal, respondería las partes más interesantes de su pregunta de la siguiente manera: "No, esto no funcionará para ningún número de bits. Para extraer N bits, necesita espacios (N-1) entre los bits que desea extraer o tener pasos adicionales de multiplicación de máscara "
La única excepción que se me ocurre para la regla "debe tener (N-1) ceros entre bits" es esta: si desea extraer dos bits adyacentes entre sí en el original, y desea mantenerlos en el mismo orden, entonces aún puedes hacerlo. Y para el propósito de la regla (N-1) cuentan como dos bits.
Hay otra idea, inspirada en la respuesta de @Ternary a continuación (vea mi comentario allí). Para cada bit interesante, solo necesita tantos ceros a la derecha como espacio para los bits que deben ir allí. Pero también, necesita tantos bits a la izquierda como bits de resultado a la izquierda. Entonces, si un bit b termina en la posición m de n, entonces necesita tener m-1 ceros a su izquierda y nm ceros a su derecha. Especialmente cuando los bits no están en el mismo orden en el número original que lo estarán después del reordenamiento, esta es una mejora importante para los criterios originales. Esto significa, por ejemplo, que una palabra de 16 bits
Se puede cambiar a
aunque solo haya un espacio entre e y b, dos entre d y c, tres entre los demás. ¿Qué pasó con N-1? En este caso, se
a...e
convierte en "un bloque": se multiplican por 1 para terminar en el lugar correcto y, por lo tanto, "obtuvimos e gratis". Lo mismo es cierto para byd (b necesita tres espacios a la derecha, d necesita los mismos tres a su izquierda). Entonces, cuando calculamos el número mágico, encontramos que hay duplicados:Claramente, si quisieras estos números en un orden diferente, tendrías que espaciarlos más. Podemos reformular la
(N-1)
regla: "Siempre funcionará si hay al menos (N-1) espacios entre bits; o, si se conoce el orden de los bits en el resultado final, si un bit b termina en la posición m de n, necesita tener m-1 ceros a su izquierda y nm ceros a su derecha ".@Ternary señaló que esta regla no funciona del todo, ya que puede haber un arrastre de bits agregando "justo a la derecha del área objetivo", es decir, cuando los bits que estamos buscando son todos. Continuando con el ejemplo que di arriba con los cinco bits apretados en una palabra de 16 bits: si comenzamos con
Por simplicidad, nombraré las posiciones de bit
ABCDEFGHIJKLMNOP
Las matemáticas que íbamos a hacer era
Hasta ahora, pensamos que nada por debajo de
abcde
(posicionesABCDE
) no importaría, pero de hecho, como se señaló @Ternary, sib=1, c=1, d=1
a continuación,(b+c)
en la posiciónG
causará un poco para llevar a la posiciónF
, lo que significa que(d+1)
en la posiciónF
llevará un poco enE
- y nuestra El resultado se echa a perder. Tenga en cuenta que el espacio a la derecha del bit de interés menos significativo (c
en este ejemplo) no importa, ya que la multiplicación causará relleno con ceros de beyone el bit menos significativo.Entonces necesitamos modificar nuestra regla (m-1) / (nm). Si hay más de un bit que tiene "exactamente (nm) bits no utilizados a la derecha (sin contar el último bit en el patrón -" c "en el ejemplo anterior), entonces debemos fortalecer la regla - y tenemos que ¡hazlo iterativamente!
Tenemos que mirar no solo el número de bits que cumplen con el criterio (nm), sino también los que están en (n-m + 1), etc. Llamemos a su número Q0 (exactamente
n-m
al siguiente bit), Q1 ( n-m + 1), hasta Q (N-1) (n-1). Entonces corremos el riesgo de llevar siSi observa esto, puede ver que si escribe una expresión matemática simple
y el resultado es
W > 2 * N
, a continuación, es necesario aumentar el criterio de RHS en un bit a(n-m+1)
. En este punto, la operación es segura mientrasW < 4
; si eso no funciona, aumente el criterio uno más, etc.Creo que seguir lo anterior te ayudará mucho a responder ...
fuente
Pregunta muy interesante de hecho. Estoy hablando con mis dos centavos, que es que, si logras establecer problemas como este en términos de lógica de primer orden sobre la teoría de vectores de bits, entonces los demostradores de teoremas son tus amigos y potencialmente pueden brindarte muy rápidamente. Respuestas a sus preguntas. Volvamos a plantear el problema como teorema:
"Existen algunas constantes de 64 bits 'máscara' y 'multiplicando' tales que, para todos los vectores de bits de 64 bits x, en la expresión y = (x & máscara) * multiplicando, tenemos que y.63 == x.63 , y.62 == x.55, y.61 == x.47, etc. "
Si esta oración es de hecho un teorema, entonces es cierto que algunos valores de las constantes 'máscara' y 'multiplicando' satisfacen esta propiedad. Entonces, expresemos esto en términos de algo que un probador de teoremas pueda entender, a saber, la entrada SMT-LIB 2:
Y ahora preguntemos al probador de teoremas Z3 si este es un teorema:
El resultado es:
¡Bingo! Reproduce el resultado dado en la publicación original en 0.06 segundos.
Mirando esto desde una perspectiva más general, podemos ver esto como una instancia de un problema de síntesis de programas de primer orden, que es un área de investigación naciente sobre la cual se han publicado pocos artículos. Una búsqueda de
"program synthesis" filetype:pdf
debería comenzar.fuente
Cada 1 bit en el multiplicador se usa para copiar uno de los bits en su posición correcta:
1
ya está en la posición correcta, así que multiplique por0x0000000000000001
.2
deben desplazarse las posiciones de 7 bits a la izquierda, por lo que multiplicamos por0x0000000000000080
(se establece el bit 7).3
deben desplazarse posiciones de 14 bits a la izquierda, por lo que multiplicamos por0x0000000000000400
(se establece el bit 14).8
deben desplazarse las posiciones de 49 bits hacia la izquierda, por lo que multiplicamos por0x0002000000000000
(se establece el bit 49).El multiplicador es la suma de los multiplicadores para los bits individuales.
Esto solo funciona porque los bits que se deben recopilar no están demasiado juntos, por lo que la multiplicación de bits que no pertenecen juntos en nuestro esquema cae más allá de los 64 bits o en la parte inferior de no importa.
Tenga en cuenta que los otros bits en el número original deben ser
0
. Esto se puede lograr enmascarándolos con una operación AND.fuente
(Nunca lo había visto antes. ¡Este truco es genial!)
Expandiré un poco la afirmación de Floris de que al extraer
n
bits necesitan-1
espacio entre los bits no consecutivos:Mi pensamiento inicial (veremos en un minuto cómo esto no funciona) fue que podría hacerlo mejor: si desea extraer
n
bits, tendrá una colisión al extraer / cambiar bitsi
si tiene a alguien (no -secutorio con biti
) en losi-1
bits anteriores on-i
posteriores.Daré algunos ejemplos para ilustrar:
...a..b...c...
Funciona (nadie en los 2 bits despuésa
, el bit antes y el bit despuésb
, y nadie está en los 2 bits antesc
):...a.b....c...
Falla porqueb
está en los 2 bits posterioresa
(y se coloca en el lugar de otra persona cuando cambiamosa
):...a...b.c...
Falla porqueb
está en los 2 bits anterioresc
(y es empujado al lugar de otra persona cuando cambiamosc
):...a...bc...d...
Funciona porque los bits consecutivos cambian juntos:Pero tenemos un problema. Si usamos en
n-i
lugar den-1
podríamos tener el siguiente escenario: ¿qué pasa si tenemos una colisión fuera de la parte que nos importa, algo que enmascararíamos al final, pero cuyos bits de transporte terminan interfiriendo en el importante rango sin máscara? ? (y nota: eln-1
requisito asegura que esto no suceda asegurándose de que losi-1
bits después de nuestro rango sin máscara estén claros cuando cambiamos eli
bit th)...a...b..c...d...
Posible falla en los bits de acarreo,c
están-1
despuésb
, pero cumple con losn-i
criterios:Entonces, ¿por qué no volvemos a ese
n-1
requisito de " pedacitos de espacio"? Porque podemos hacerlo mejor :...a....b..c...d..
No pasa lan-1
prueba de " bits de espacio", pero funciona para nuestro truco de extracción de bits:No puedo encontrar una buena manera de caracterizar estos campos que no tienen
n-1
espacio entre bits importantes, pero que aún funcionarían para nuestra operación. Sin embargo, dado que sabemos de antemano qué bits nos interesan, podemos verificar nuestro filtro para asegurarnos de que no experimentamos colisiones de bits de arrastre:Compare
(-1 AND mask) * shift
con el resultado esperado de todos,-1 << (64-n)
(para 64 bits sin signo)El cambio / multiplicación mágico para extraer nuestros bits funciona si y solo si los dos son iguales.
fuente
b
termina en posiciónm
den
, entonces necesita tenerm-1
ceros a su izquierda yn-m-1
ceros a su derecha. Especialmente cuando los bits no están en el mismo orden en el número original que lo estarán después del reordenamiento, esta es una mejora importante para los criterios originales. Esto es divertido.Además de las excelentes respuestas a esta pregunta tan interesante, podría ser útil saber que este truco de multiplicación bit a bit se conoce en la comunidad de ajedrez informático desde 2007, donde se conoce con el nombre de Magic BitBoards .
Muchos motores de ajedrez de computadora usan varios enteros de 64 bits (llamados tableros de bits) para representar los distintos conjuntos de piezas (1 bit por casilla ocupada). Suponga que una pieza deslizante (torre, alfil, reina) en un determinado cuadrado de origen puede moverse a la mayoría de los
K
cuadrados si no había piezas de bloqueo. El uso de bit a bit y de esosK
bits dispersos con el bitboard de cuadrados ocupados proporciona unaK
palabra específica de bit incorporada dentro de un entero de 64 bits.La multiplicación mágica se puede utilizar para asignar estos
K
bits dispersos a losK
bits más bajos de un entero de 64 bits. EstosK
bits inferiores se pueden usar para indexar una tabla de tableros de bits precalculados que representan los cuadrados permitidos a los que la pieza en su cuadrado de origen puede moverse realmente (cuidando el bloqueo de piezas, etc.)Un motor de ajedrez típico que usa este enfoque tiene 2 tablas (una para torres, una para obispos, reinas que usan la combinación de ambas) de 64 entradas (una por casilla de origen) que contienen dichos resultados precalculados. Tanto el motor de ajedrez de código abierto de más alta calificación ( Houdini ) como el de código abierto ( Stockfish ) utilizan actualmente este enfoque por su alto rendimiento.
La búsqueda de estos multiplicadores mágicos se realiza mediante una búsqueda exhaustiva (optimizada con puntos de corte iniciales) o con prueba y error (por ejemplo, probar muchos enteros aleatorios de 64 bits). No se han utilizado patrones de bits durante la generación de movimientos para los que no se pudo encontrar una constante mágica. Sin embargo, los efectos de acarreo a nivel de bit son típicamente necesarios cuando los bits a mapear tienen (casi) índices adyacentes.
AFAIK, el enfoque muy general de solución de SAT de @Syzygy no se ha utilizado en el ajedrez informático, y tampoco parece haber ninguna teoría formal sobre la existencia y la unicidad de tales constantes mágicas.
fuente