Widziałem ciekawą technikę zastosowaną w odpowiedzi na inne pytanie i chciałbym ją trochę lepiej zrozumieć.
Otrzymujemy 64-bitową liczbę całkowitą bez znaku i jesteśmy zainteresowani następującymi bitami:
1.......2.......3.......4.......5.......6.......7.......8.......
W szczególności chcielibyśmy przenieść je na najwyższe osiem pozycji, w ten sposób:
12345678........................................................
Nie obchodzi nas wartość bitów wskazanych przez .
i nie trzeba ich zachowywać.
Rozwiązanie było maskowania niepożądanych bitów i pomnożenie wyniku przez 0x2040810204081
. To, jak się okazuje, załatwia sprawę.
Jak ogólna jest ta metoda? Czy tę technikę można wykorzystać do wyodrębnienia dowolnego podzbioru bitów? Jeśli nie, w jaki sposób można ustalić, czy metoda działa dla określonego zestawu bitów?
Wreszcie, jak przejść do znalezienia (a?) Poprawnego mnożnika, aby wyodrębnić dane bity?
Odpowiedzi:
Bardzo interesujące pytanie i sprytna sztuczka.
Spójrzmy na prosty przykład manipulacji jednym bajtem. Używanie 8-bitowego bez znaku dla uproszczenia. Wyobraź sobie, że twój numer jest
xxaxxbxx
i chceszab000000
.Rozwiązanie składało się z dwóch kroków: trochę maskowania, a następnie pomnożenia. Maska bitowa to prosta operacja AND, która zamienia nieciekawe bity w zera. W powyższym przypadku twoja maska byłaby
00100100
i wynik00a00b00
.Teraz najtrudniejsza część: przekształcenie tego w
ab......
.Mnożenie to kilka operacji shift-and-add. Kluczem jest umożliwienie przelewowi „przesunięcia” bitów, których nie potrzebujemy i umieszczenia tych, które chcemy we właściwym miejscu.
Mnożenie przez 4 (
00000100
) powoduje przesunięcie wszystkiego, co pozostało o 2 i prowadzi doa00b0000
. Aby dostać sięb
w górę, musimy pomnożyć przez 1 (aby utrzymać a we właściwym miejscu) + 4 (aby przenieść b w górę). Suma ta wynosi 5 i w połączeniu z wcześniejszymi 4 otrzymujemy magiczną liczbę 20 lub00010100
. Oryginał był00a00b00
po zamaskowaniu; mnożenie daje:Dzięki takiemu podejściu możesz objąć większą liczbę i więcej bitów.
Jedno z zadanych przez Ciebie pytań brzmiało: „czy można tego dokonać za pomocą dowolnej liczby bitów?” Myślę, że odpowiedź brzmi „nie”, chyba że zezwolisz na kilka operacji maskowania lub kilka multiplikacji. Problemem jest kwestia „kolizji” - na przykład „bezpańskie b” w powyższym problemie. Wyobraź sobie, że musimy to zrobić z taką liczbą
xaxxbxxcx
. Zgodnie z wcześniejszym podejściem, pomyślałbyś, że potrzebujemy {x 2, x {1 + 4 + 16}} = x 42 (oooh - odpowiedź na wszystko!). Wynik:Jak widać, nadal działa, ale „tylko”. Kluczową kwestią jest tutaj to, że między „bitami” jest wystarczająco dużo miejsca, aby wszystko wycisnąć. Nie mogłem dodać czwartego bitu zaraz po c, ponieważ dostałbym przypadki, w których dostaję c + d, bity mogą przenosić ...
Więc bez formalnego dowodu odpowiedziałbym na bardziej interesujące części twojego pytania w następujący sposób: „Nie, to nie zadziała dla dowolnej liczby bitów. Aby wyodrębnić N bitów, potrzebujesz (N-1) spacji między bitami, które chcesz wyodrębnij lub wykonaj dodatkowe kroki pomnożenia maski ”.
Jedyny wyjątek, jaki mogę wymyślić w przypadku reguły „musi mieć (N-1) zera między bitami”, to: jeśli chcesz wyodrębnić dwa bity sąsiadujące ze sobą w oryginale ORAZ chcesz zachować je w w tej samej kolejności, nadal możesz to zrobić. Dla celów reguły (N-1) liczą się one 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ą tam przejść. Ale potrzebuje też tylu bitów po lewej, co bitów wynikowych po lewej. Więc jeśli bit b kończy się w pozycji m n, to musi mieć m-1 zera po lewej, a nm zera po prawej. Jest to ważne ulepszenie pierwotnych kryteriów, zwłaszcza gdy bity nie są w tej samej kolejności w oryginalnym numerze, co będą po ponownym uporządkowaniu. Oznacza to na przykład 16-bitowe słowo
Można zmienić na
nawet jeśli między e i b jest tylko jedna przestrzeń, dwie między d i c, trzy między pozostałymi. 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ę w odpowiednim miejscu, a więc „dostaliśmy e za darmo”. To samo dotyczy b i d (b potrzebuje trzech spacji w prawo, d potrzebuje tych samych trzech po lewej). Kiedy więc obliczamy magiczną liczbę, okazuje się, że istnieją duplikaty:Oczywiście, jeśli chcesz te liczby w innej kolejności, będziesz musiał je dalej rozmieścić. Możemy przeformułować
(N-1)
zasadę: „Zawsze zadziała, jeśli między bitami są przynajmniej (N-1) spacje; lub, jeśli znana jest kolejność bitów w wyniku końcowym, to jeśli bit b znajdzie się w pozycji m n, musi mieć zero m-1 po lewej stronie, a nm zero po prawej stronie. ”@Ternary zwrócił uwagę, że ta reguła nie do końca działa, ponieważ może wystąpić przeniesienie z bitów, dodając „tuż po prawej stronie obszaru docelowego” - mianowicie, gdy wszystkie szukane bity są jednymi. Kontynuując przykład podałem powyżej z pięcioma ciasno upakowanymi bitami w 16-bitowym słowie: jeśli zaczniemy
Dla uproszczenia wymienię pozycje bitów
ABCDEFGHIJKLMNOP
Matematyka, którą zamierzaliśmy zrobić, była
Do tej pory uważaliśmy, że cokolwiek poniżej
abcde
(pozycjeABCDE
) nie będzie miało znaczenia, ale w rzeczywistości, jak wskazał @Ternary, jeślib=1, c=1, d=1
wtedy(b+c)
pozycjaG
spowoduje trochę przeniesienia do pozycjiF
, co oznacza, że(d+1)
pozycjaF
spowoduje przeniesienie nieco do pozycjiE
- i nasze wynik jest zepsuty. Zwróć uwagę, że miejsce po prawej stronie najmniej znaczącego elementu zainteresowania (c
w tym przykładzie) nie ma znaczenia, ponieważ mnożenie spowoduje wypełnienie zerami od jednego najmniej znaczącego bitu.Musimy więc zmodyfikować naszą zasadę (m-1) / (nm). Jeśli jest więcej niż jeden bit, który ma „dokładnie (nm) nieużywane bity po prawej stronie (nie licząc ostatniego bitu we wzorze -„ c ”w powyższym przykładzie), musimy wzmocnić regułę - i musimy zrób to iteracyjnie!
Musimy spojrzeć nie tylko na liczbę bitów, które spełniają kryterium (nm), ale także na te, które są w (n-m + 1) itd. Nazwijmy ich liczbę Q0 (dokładnie
n-m
do następnego bitu), Q1 ( n-m + 1), do Q (N-1) (n-1). W takim razie ryzykujemy, że nieJeśli spojrzysz na to, możesz zauważyć, że jeśli napiszesz proste wyrażenie matematyczne
a wynik jest taki
W > 2 * N
, że musisz podnieść kryterium RHS o jeden bit do(n-m+1)
. W tym momencie operacja jest bezpieczna, dopókiW < 4
; jeśli to nie zadziała, zwiększ jeszcze jedno kryterium itp.Myślę, że postępowanie zgodnie z powyższym da ci długą drogę do odpowiedzi ...
źródło
Rzeczywiście bardzo interesujące pytanie. Wchodzę z moimi dwoma centami, to znaczy, że jeśli potrafisz określić takie problemy w zakresie logiki pierwszego rzędu w teorii bitvectora, to dowody twierdzeń są twoim przyjacielem i mogą potencjalnie zapewnić ci bardzo szybko odpowiedzi na twoje pytania. Ponownie określmy zadany problem jako twierdzenie:
„Istnieje kilka 64-bitowych stałych„ maska ”i„ multiplikacja ”, tak że dla wszystkich 64-bitowych wektorów bitowych x, w wyrażeniu y = (x & mask) * multiplicand, mamy to 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łaściwość. Sformułujmy to w kategoriach czegoś, co może zrozumieć przysłowie twierdzeń, a mianowicie danych wejściowych SMT-LIB 2:
A teraz zapytajmy przysłowie twierdzenia Z3, czy jest to twierdzenie:
Wynik to:
Bingo! Odtwarza 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 obszarem badań, na temat którego opublikowano niewiele artykułów. Poszukiwanie
"program synthesis" filetype:pdf
powinno zacząć.źródło
Każdy 1-bit w multiplikatorze służy do skopiowania jednego z bitów do jego właściwej pozycji:
1
jest już w prawidłowej pozycji, więc pomnóż przez0x0000000000000001
.2
należy przesunąć o 7 bitów w lewo, więc mnożymy przez0x0000000000000080
(bit 7 jest ustawiony).3
należy przesunąć o 14 bitów w lewo, więc mnożymy przez0x0000000000000400
(bit 14 jest ustawiony).8
należy przesunąć o 49 bitów w lewo, więc mnożymy przez0x0002000000000000
(bit 49 jest ustawiony).Mnożnik jest sumą mnożników dla poszczególnych bitów.
Działa to tylko dlatego, że bity, które mają być zebrane, nie są zbyt blisko siebie, tak że zwielokrotnienie bitów, które nie pasują do siebie w naszym schemacie, albo wykracza poza 64-bitowy, albo w dolnej części, na której nie ma znaczenia.
Zauważ, że pozostałe bity w pierwotnym numerze muszą być
0
. Można to osiągnąć poprzez maskowanie ich operacją AND.źródło
(Nigdy wcześniej tego nie widziałem. Ta sztuczka jest świetna!)
Rozbuduję trochę twierdzenie Florisa, że podczas wydobywania
n
bitów potrzebujeszn-1
odstępu między dowolnymi nieciągłymi bitami:Moją początkową myślą (za chwilę zobaczymy, jak to nie do końca działa) było to, że możesz zrobić lepiej: jeśli chcesz wyodrębnić
n
bity, będziesz mieć kolizję podczas wydobywania / przesuwania bitu,i
jeśli masz kogoś (nie -konsekwentne z bitemi
) wi-1
bitach poprzedzających lubn-i
bitach późniejszych.Podam kilka przykładów ilustrujących:
...a..b...c...
Działa (nikt w 2 bitach późnieja
, trochę wcześniej i trochę późniejb
i nikt nie jest w 2 bitach późniejc
):...a.b....c...
Nie działa, ponieważb
działa, znajduje się w 2 bitach późnieja
(i zostaje wciągnięty w miejsce kogoś innego, gdy się zmieniamya
):...a...b.c...
Nie działa, ponieważb
działa, znajduje się w 2 bitach poprzedzającychc
(i zostaje zepchnięty w miejsce kogoś innego podczas zmianyc
):...a...bc...d...
Działa, ponieważ kolejne bity przesuwają się razem:Ale mamy problem. Jeśli użyjemy
n-i
zamiastn-1
, moglibyśmy mieć następujący scenariusz: co gdybyśmy mieli kolizję poza częścią, na której nam zależy, coś, co chcielibyśmy ukryć na końcu, ale którego bity przenoszące ostatecznie zakłócają ważny, nie maskowany zakres ? (i uwaga:n-1
wymaganie upewnia się, że tak się nie stanie, upewniając się, żei-1
bity po naszym nie zamaskowanym zakresie są czyste, gdy zmieniamyi
th th)...a...b..c...d...
Potencjalna awaria bitów przenoszącychc
występujen-1
późniejb
, ale spełnian-i
kryteria:Dlaczego więc nie wrócimy do tego ”
n-1
wymogu kawałków przestrzeni”? Ponieważ możemy zrobić lepiej :...a....b..c...d..
Nie powiedzie się „n-1
test bitów przestrzeni”, ale działa na naszą sztuczkę wyodrębniania bitów:Nie mogę znaleźć dobrego sposobu na scharakteryzowanie tych pól, które tego nie robią ma
n-1
miejsca między ważnymi bitami, ale nadal działałyby dla naszej operacji. Ponieważ jednak wiemy z góry które bity jesteśmy zainteresowani, możemy sprawdzić nasz filtr, aby upewnić się, że nie występują kolizje bitów przenoszących:Porównaj
(-1 AND mask) * shift
z oczekiwanym wynikiem wszystkich,-1 << (64-n)
(dla wersji 64-bitowej bez znaku)Przesunięcie / pomnożenie magii w celu wyodrębnienia naszych bitów działa tylko wtedy, gdy oba są równe.
źródło
b
kończy się w pozycjim
on
, to musi miećm-1
zera do jego lewej strony in-m-1
zer na jej prawej stronie. Jest to ważne ulepszenie pierwotnych kryteriów, zwłaszcza gdy bity nie są w tej samej kolejności w oryginalnym numerze, co będą po ponownym uporządkowaniu. To jest fajne.Oprócz doskonałych odpowiedzi na to bardzo interesujące pytanie, warto wiedzieć, że ta sztuczka polegająca na multiplikacji bitowej jest znana w społeczności szachów komputerowych od 2007 roku, 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 sztuk (1 bit na zajmowany kwadrat). Załóżmy, że przesuwany element (wieża, biskup, królowa) na pewnym polu początkowym może przejść do co najwyżej
K
kwadratów, jeśli nie było żadnych blokujących elementów. UżywanieK
bitów -i tych rozproszonych bitów z bitboardem zajmowanych kwadratów daje określoneK
-bitowe słowo osadzone w 64-bitowej liczbie całkowitej.Magicznego mnożenia można użyć do odwzorowania tych rozproszonych
K
bitów na niższeK
bity 64-bitowej liczby całkowitej. Te niższeK
bity można następnie wykorzystać do indeksowania tabeli wstępnie obliczonych bitboardów, które reprezentują dozwolone kwadraty, do których może faktycznie przenieść się kawałek na jego pierwotnym kwadracie (dbając o blokowanie elementów itp.)Typowy silnik szachowy wykorzystujący to podejście ma 2 tabele (jeden dla wież, jeden dla biskupów, królowe używające kombinacji obu) z 64 wpisami (jeden dla kwadratu początkowego), które zawierają takie wstępnie obliczone wyniki. Zarówno najwyżej oceniane zamknięte źródło ( Houdini ), jak i silnik szachowy typu open source ( Sztokfisz ) obecnie stosują to podejście ze względu na bardzo wysoką wydajność.
Znalezienie tych magicznych mnożników odbywa się albo za pomocą wyczerpującego wyszukiwania (zoptymalizowanego z wczesnymi odcięciami), albo za pomocą prób i błędów (np. wielu losowych 64-bitowych liczb całkowitych). Podczas generowania ruchów nie użyto żadnych wzorów bitów, dla których nie można było znaleźć stałej magicznej. Jednak bitowe efekty przenoszenia są zwykle konieczne, gdy bity do mapowania mają (prawie) sąsiednie indeksy.
AFAIK, bardzo ogólne podejście do SAT-Solvera autorstwa @Syzygy, nie było używane w szachach komputerowych i nie wydaje się też, aby istniała formalna teoria dotycząca istnienia i wyjątkowości takich magicznych stałych.
źródło