Wyciąganie bitów z pojedynczym mnożeniem

Widziałem ciekawą technikę zastosowaną w Odpowiedzina inne pytanie i chciałbym je trochę lepiej zrozumieć.

Otrzymujemy niepodpisaną 64-bitową liczbę całkowitą i interesują nas następujące bity:

1.......2.......3.......4.......5.......6.......7.......8.......

W szczególności chcielibyśmy przenieść ich na osiem najlepszych pozycji, tak:

12345678........................................................

Nie zależy nam na wartości bitów wskazanych przez . i nie muszą one być zachowane.

Rozwiązaniem było zamaskowanie z niechcianych bitów i pomnóż wynik przez 0x2040810204081. To, jak się okazuje, wystarczy.

Jak ogólna jest ta metoda? Czy ta technika może być użyta do wyodrębnienia dowolnego podzbioru bitów? Jeśli nie, w jaki sposób można dowiedzieć się, czy metoda działa dla określonego zestawu bitów?

Wreszcie, jak można znaleźć (a?) poprawny mnożnik do wyodrębnienia podanych bitów?

Author: Community, 2013-01-27

5 answers

Bardzo ciekawe pytanie i sprytna sztuczka.

Spójrzmy na prosty przykład manipulowania pojedynczym bajtem. Używanie unsigned 8 bit dla uproszczenia. Wyobraź sobie, że Twój numer to xxaxxbxx i chcesz ab000000.

Rozwiązanie składało się z dwóch etapów: bitowego maskowania, a następnie mnożenia. Maska bitowa jest prostą i operacją, która zamienia nieciekawe bity na zera. W powyższym przypadku Twoją maską będzie 00100100 i wynik 00a00b00.

Teraz trudna część: zamieniając to na ab.......

Mnożenie to kilka operacji shift-and-add. Kluczem jest umożliwienie przepełnienia "przesunięcia" niepotrzebnych bitów i umieszczenie tych, które chcemy we właściwym miejscu.

Mnożenie przez 4 (00000100) przesunie wszystko co pozostało o 2 i doprowadzi Cię do a00b0000 . Aby uzyskać b aby przesunąć się w górę, musimy pomnożyć przez 1 (aby utrzymać a w odpowiednim miejscu) + 4 (Aby przesunąć b w górę). Suma ta wynosi 5, a w połączeniu z wcześniejszą 4 otrzymujemy magiczną liczbę 20, lub 00010100. Oryginał był 00a00b00 po maskowaniu; mnożenie daje:

000000a00b000000
00000000a00b0000 +
----------------
000000a0ab0b0000
xxxxxxxxab......

Z tego podejścia można rozszerzyć na większe liczby i więcej bitów.

Jedno z pytań, które zadałeś, brzmiało " Czy można to zrobić za pomocą dowolnej liczby bitów?"Myślę, że odpowiedź brzmi "nie", chyba że pozwolisz na kilka operacji maskowania lub kilka mnożenia. Problemem jest kwestia "kolizji" - na przykład "zbłąkanego b" w powyższym problemie. Wyobraź sobie, że musimy to zrobić z taką liczbą jak xaxxbxxcx. Po wcześniejszym podejściu, można by pomyśleć, że potrzebujemy {x 2, x {1 + 4 + 16}} = x 42 (oooh-odpowiedź na wszystko!). Wynik:

00000000a00b00c00
000000a00b00c0000
0000a00b00c000000
-----------------
0000a0ababcbc0c00
xxxxxxxxabc......

Jak widzisz, to nadal działa, ale"tylko". Klucz jest to, że jest "wystarczająco dużo miejsca" między bitami, które chcemy, że możemy wycisnąć wszystko. Nie mogłem dodać czwartego bitu d zaraz po c, ponieważ dostałbym przypadki, w których dostaję c+d, bity mogą przenosić, ...

Więc bez formalnego dowodu odpowiedziałbym bardziej interesujące części twojego pytania w następujący sposób: "nie, to nie zadziała dla żadnej liczby bitów. Aby wyodrębnić N bitów, potrzebujesz (N-1) spacji między bitami, które chcesz wyodrębnić, lub dodatkowych kroków mnożenia maski."

Jedyny wyjątek, jaki przychodzi mi do głowy dla reguły" must have (N-1) zer between bits" jest następujący: jeśli chcesz wyodrębnić dwa bity, które są obok siebie w oryginale i chcesz zachować je w tej samej kolejności, to nadal możesz to zrobić. Oraz w celu reguła (N-1) liczy się jako dwa bity.

Jest jeszcze jeden wgląd-zainspirowany odpowiedzią @Ternary poniżej(Zobacz mój komentarz tam). Dla każdego interesującego bitu potrzebujesz tylko tyle zer po prawej stronie, ile potrzebujesz miejsca na bity, które muszą się tam znaleźć. Ale także potrzebuje tyle bitów po lewej stronie, ile bitów wynikowych po lewej. Więc jeśli bit b kończy się w pozycji m N, to musi mieć zera m - 1 po lewej, a zera n-m po prawej. Zwłaszcza gdy bitów nie ma w ta sama kolejność w oryginalnym numerze, jak będą po ponownym zamówieniu, jest to ważna poprawa pierwotnych kryteriów. Oznacza to na przykład, że 16-bitowe słowo

a...e.b...d..c..

Można przenieść na

abcde...........

Chociaż jest tylko jedna przestrzeń między e i b, dwie między d i c, trzy między innymi. Co się stało z N-1?? W tym przypadku a...e staje się "jednym blokiem" - są one mnożone przez 1, aby znaleźć się we właściwym miejscu, a więc "mamy e za darmo". Na to samo dotyczy b I d (b potrzebuje trzech spacji po prawej, d potrzebuje tych samych trzech po lewej). Więc kiedy obliczamy liczbę magiczną, znajdujemy duplikaty: {]}

a: << 0  ( x 1    )
b: << 5  ( x 32   )
c: << 11 ( x 2048 )
d: << 5  ( x 32   )  !! duplicate
e: << 0  ( x 1    )  !! duplicate

Oczywiście, jeśli chcesz te liczby w innej kolejności, trzeba byłoby umieścić je dalej. Możemy przeformułować regułę (N-1): "będzie ona zawsze działać, jeśli pomiędzy bitami są co najmniej (N-1) odstępy; lub, jeśli kolejność bitów w wyniku końcowym jest znana, to jeśli bit b kończy się w pozycji m N, musi zer m - 1 po lewej, a zer n-m po prawej."

@ Ternary zauważył, że ta zasada nie do końca działa, ponieważ może być przeniesienie z bitów dodając "po prawej stronie Obszaru docelowego" - mianowicie, gdy bity, których szukamy, są wszystkie. Kontynuując powyższy przykład z pięcioma szczelnie upakowanymi bitami w 16-bitowym słowie: jeśli zaczniemy od

a...e.b...d..c..

Dla uproszczenia wymienię pozycje bitowe ABCDEFGHIJKLMNOP

The math we were going to do was

ABCDEFGHIJKLMNOP

a000e0b000d00c00
0b000d00c0000000
000d00c000000000
00c0000000000000 +
----------------
abcded(b+c)0c0d00c00

Do tej pory myśleliśmy, że cokolwiek poniżej abcde (pozycje ABCDE) nie będzie miało znaczenia, ale w rzeczywistości, jak zauważył @Ternary, jeśli b=1, c=1, d=1 to (b+c) w pozycji G spowoduje przeniesienie bitu do pozycji F, co oznacza, że (d+1) w pozycji F przeniesie trochę do E - a nasz wynik jest zepsuty. Zauważ, że Spacja na prawo od najmniej znaczącego bitu zainteresowania (c w tym przykładzie) nie ma znaczenia, ponieważ mnożenie spowoduje wypełnienie zerami od beyone najmniej znaczący bit.

Więc musimy zmodyfikować naszą regułę (m-1) / (n-m). Jeśli istnieje więcej niż jeden bit, który ma "dokładnie (N-m) nieużywane bity po prawej stronie (nie licząc ostatniego bitu we wzorze -" c " w powyższym przykładzie), to musimy wzmocnić regułę - i musimy to zrobić iteracyjnie!

Musimy przyjrzeć się nie tylko liczbie bitów spełniających kryterium (n-m), ale także tym, które są przy (n-m+1), itd. Nazwijmy ich numer Q0 (dokładnie n-m do następny bit), Q1 (n-m+1), aż do Q(N-1) (n-1). Wtedy ryzykujemy, jeśli

Q0 > 1
Q0 == 1 && Q1 >= 2
Q0 == 0 && Q1 >= 4
Q0 == 1 && Q1 > 1 && Q2 >=2
... 

Jeśli spojrzysz na to, zobaczysz, że jeśli napiszesz proste wyrażenie matematyczne

W = N * Q0 + (N - 1) * Q1 + ... + Q(N-1)

A wynikiem jest W > 2 * N, następnie należy zwiększyć kryterium RHS o jeden bit do (n-m+1). W tym momencie operacja jest Bezpieczna, dopóki W < 4; jeśli to nie zadziała, zwiększ kryterium o jeden, itp.

Myślę, że postępując zgodnie z powyższym, będziesz miał długą drogę do odpowiedzi...

 230
Author: Floris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-20 05:31:12

Bardzo interesujące pytanie. Jestem gonging z moich dwóch centów, czyli to, że, jeśli uda Ci się przedstawić problemy jak ten w kategoriach logiki pierwszego rzędu nad teorią bitvector, następnie theorem provers są twoim przyjacielem, i potencjalnie może dostarczyć bardzo szybkie odpowiedzi na pytania. Powtórzmy zadany problem jako twierdzenie:

"istnieje kilka 64-bitowych stałych 'mask' i 'multiplicand' takich, że dla wszystkich 64-bitowych bitwektorów x, w wyrażeniu y = (x & Maska) * multiplicand, mamy, że y.63 == x.63, y.62 == x.55, y. 61 = = x. 47, itd."

Jeśli to zdanie jest w rzeczywistości twierdzeniem, to prawdą jest, że niektóre wartości stałych "mask" i "multiplicand" spełniają tę własność. Tak więc ujmijmy to w kategoriach czegoś, co może zrozumieć twierdzenie prover, a mianowicie wejście SMT-LIB 2:

(set-logic BV)

(declare-const mask         (_ BitVec 64))
(declare-const multiplicand (_ BitVec 64))

(assert
  (forall ((x (_ BitVec 64)))
    (let ((y (bvmul (bvand mask x) multiplicand)))
      (and
        (= ((_ extract 63 63) x) ((_ extract 63 63) y))
        (= ((_ extract 55 55) x) ((_ extract 62 62) y))
        (= ((_ extract 47 47) x) ((_ extract 61 61) y))
        (= ((_ extract 39 39) x) ((_ extract 60 60) y))
        (= ((_ extract 31 31) x) ((_ extract 59 59) y))
        (= ((_ extract 23 23) x) ((_ extract 58 58) y))
        (= ((_ extract 15 15) x) ((_ extract 57 57) y))
        (= ((_ extract  7  7) x) ((_ extract 56 56) y))
      )
    )
  )
)

(check-sat)
(get-model)

A teraz zapytajmy twierdzenie prover Z3, czy jest to twierdzenie:

z3.exe /m /smt2 ExtractBitsThroughAndWithMultiplication.smt2

Wynik jest następujący:

sat
(model
  (define-fun mask () (_ BitVec 64)
    #x8080808080808080)
  (define-fun multiplicand () (_ BitVec 64)
    #x0002040810204081)
)
Bingo! Rozmnaża się wynik podany w oryginalnym poście w 0.06 sekundy.

Patrząc na to z bardziej ogólnej perspektywy, możemy postrzegać to jako przykład problemu syntezy programu pierwszego rzędu, który jest rodzącym się obszarem badań, na temat którego opublikowano kilka artykułów. Wyszukiwanie {[3] } powinno zacząć.

 152
Author: Syzygy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-20 13:09:36

Każdy 1-bit w mnożniku jest używany do skopiowania jednego z bitów do jego poprawnej pozycji:

  • 1 jest już w prawidłowej pozycji, więc pomnóż przez 0x0000000000000001.
  • 2 musi być przesunięte o 7 bitów w lewo, więc mnożymy przez 0x0000000000000080 (bit 7 jest ustawiony).
  • 3 musi być przesunięte o 14 bitów w lewo, więc mnożymy przez 0x0000000000000400 (Bit 14 jest ustawiony).
  • i tak dalej do
  • 8 muszą być przesunięte o 49 bitów w lewo, więc mnożymy by 0x0002000000000000 (bit 49 jest ustawiony).

Mnożnik jest sumą mnożników dla poszczególnych bitów.

Działa to tylko dlatego, że bity do zebrania nie są zbyt blisko siebie, więc mnożenie bitów, które nie należą do siebie w naszym schemacie, albo wychodzi poza 64 bit lub w dolnej części don't care.

Zauważ, że pozostałe bity w numerze oryginalnym muszą być 0. Można to osiągnąć poprzez maskowanie ich za pomocą operacji AND.

 84
Author: starblue,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-29 21:14:52

(nigdy wcześniej tego nie widziałem. Ta sztuczka jest świetna!)

Rozwiń trochę twierdzenie Florisa, że podczas ekstrakcji n bitów potrzebujesz n-1 przestrzeni między dowolnymi nie kolejnymi bitami:

Moja początkowa myśl (zobaczymy za chwilę, Jak to nie do końca działa) była taka, że możesz zrobić lepiej: jeśli chcesz wyodrębnić n bity, będziesz miał kolizję podczas wyodrębniania / przesuwania bitu i Jeśli masz kogoś (nie-kolejne z bitem i) w i-1 bitach poprzedzających lub n-i bitów kolejnych.

Podam kilka przykładów do zilustrowania:

...a..b...c... Działa (nikt w 2 bitach po a, bit przed i bit po b, i nikt nie jest w 2 bitach przed c):

  a00b000c
+ 0b000c00
+ 00c00000
= abc.....

...a.b....c... zawiedzie, Ponieważ {[15] } jest w 2 bitach po a (i zostaje wciągnięty w czyjeś miejsce, gdy przesuniemy a):

  a0b0000c
+ 0b0000c0
+ 00c00000
= abX.....

...a...b.c... nie powiedzie się, ponieważ b jest w 2 bitach poprzedzających c (i zostaje zepchnięty w czyjeś miejsce, gdy we shift c):

  a000b0c0
+ 0b0c0000
+ b0c00000
= Xbc.....

...a...bc...d... działa, ponieważ kolejne bity przesuwają się razem:

  a000bc000d
+ 0bc000d000
+ 000d000000
= abcd000000

Ale mamy problem. jeśli użyjemy n-i zamiast n-1, możemy mieć następujący scenariusz: co jeśli mamy kolizję poza częścią, na której nam zależy, coś, co zamaskujemy na końcu, ale czyje bity nośne kończą się ingerencją w ważny, niezamaskowany zakres? (i uwaga: wymóg n-1 upewnia się, że tak się nie stanie, upewniając się, że i-1 bity po naszym Nie maskowanym zakresie są jasne, gdy przesuniemy i th bit)

...a...b..c...d... potencjalna awaria na nośnikach, c jest w n-1 po b, ale spełnia n-i kryteria:

  a000b00c000d
+ 0b00c000d000
+ 00c000d00000
+ 000d00000000
= abcdX.......

Więc może wrócimy do tego wymogu "n-1 bitów przestrzeni"? ponieważ możemy zrobić lepiej :

...a....b..c...d.. nie powiodło się test "n-1 bitów przestrzeni", ale Działa dla naszej sztuczki wydobywania bitów:

+ a0000b00c000d00
+ 0b00c000d000000
+ 00c000d00000000
+ 000d00000000000
= abcd...0X......

Nie mogę wymyślić dobrego sposobu na scharakteryzuj te pola, które nie mają n-1 przestrzeni między ważnymi bitami, ale nadal będą działać dla naszej operacji. Jednak, ponieważ wiemy z wyprzedzeniem które bity nas interesują, możemy sprawdzić nasz filtr, aby upewnić się, że nie doświadczamy kolizji carry-bitowych: {]} W przeciwieństwie do innych systemów, nie można ich używać w systemach Windows, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X, Mac OS X]}

Magiczne przesunięcie / mnożenie, aby wyodrębnić nasze bity, działa wtedy i tylko wtedy, gdy oba są równe.

 29
Author: Ternary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-28 01:30:01

Oprócz doskonałych odpowiedzi na to bardzo interesujące pytanie, warto wiedzieć, że ta sztuczka z mnożeniem bitowym jest znana w społeczności szachów komputerowych od 2007 roku, gdzie występuje pod nazwą Magic BitBoards.

Wiele komputerowych silników szachowych używa kilku 64-bitowych liczb całkowitych (zwanych bitboardami) do reprezentowania różnych zestawów elementów (1 bit na zajmowany kwadrat). Załóżmy, że element ślizgowy (Wieża, biskup, królowa) na pewnym kwadrat początkowy może przesuwać się do co najwyżej K kwadratów, jeśli nie ma elementów blokujących. Użycie bitowego-i tych rozproszonych K bitów z płytą bitową zajętych kwadratów daje określone K-bitowe słowo osadzone w 64-bitowej liczbie całkowitej.

Magiczne mnożenie może być użyte do odwzorowania tych rozproszonych K bitów na dolne K bity 64-bitowej liczby całkowitej. Te dolne bity K mogą być następnie używane do indeksowania tabeli wstępnie obliczonych płyt bitowych, które reprezentują dozwolone kwadraty, które kawałek na jego początkowy kwadrat może rzeczywiście przenieść się do (dbanie o bloki itp.)

Typowy silnik szachowy wykorzystujący to podejście ma 2 tabele (jedna dla wieżowców, jedna dla biskupów, królowych wykorzystujących kombinację obu) z 64 wpisami (po jednym na kwadrat początkowy), które zawierają takie wstępnie obliczone wyniki. Zarówno Najwyżej oceniane zamknięte źródło (Houdini) i open source chess engine (Stockfish) obecnie stosuje się to podejście do jego bardzo wysokiej wydajność.

Znalezienie tych magicznych mnożników odbywa się albo za pomocą wyczerpujące wyszukiwanie (zoptymalizowane z wczesnymi odcinkami) lub z trial i eror (np. próba wielu losowych 64-bitowych liczb całkowitych). Nie było wzorców bitowych używanych podczas generowania ruchu, dla których nie można było znaleźć stałej magicznej. Jednak bitowe efekty przenoszenia są zwykle konieczne, gdy bity, które mają być odwzorowane, mają (prawie) sąsiadujące indeksy.

AFAIK, bardzo ogólny SAT-solver approachy by @ Syzygy nie był używany w szachach komputerowych, nie ma też żadnej formalnej teorii dotyczącej istnienia i wyjątkowości takich stałych magicznych.

 12
Author: TemplateRex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-14 12:00:35