Tek bir çarpma ile bitleri ayıklama


301

Bir kullanılan ilginç bir tekniği görünce cevap için başka bir soru , ve biraz daha iyi anlamak istiyorum.

Bize işaretsiz bir 64 bit tam sayı verildi ve aşağıdaki bitlerle ilgileniyoruz:

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

Özellikle, bunları ilk sekiz konuma taşımak istiyoruz, şöyle:

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

Belirtilen bitlerin değerini umursamıyoruz .ve bunların korunması gerekmiyor.

Solüsyon istenmeyen edilen bitler ve sonra sonucu çoğalmaya oldu 0x2040810204081. Bu, ortaya çıktığı gibi, hile yapar.

Bu yöntem ne kadar geneldir? Bu teknik herhangi bir bit alt kümesini çıkarmak için kullanılabilir mi? Değilse, yöntemin belirli bir bit kümesi için işe yarayıp yaramadığını nasıl anlayabiliriz?

Son olarak, verilen bitleri ayıklamak için (a?) Doğru çarpanını bulmak nasıl olur?


29
Bunu ilginç bulduysanız, bu listeye bir göz atın: graphics.stanford.edu/~seander/bithacks.html Birçoğu (ab) ilginç sonuçlar elde etmek için daha geniş bir tamsayı çarpma / bölme kullanır. ("4 işlemli bir bayttaki bitleri tersine çevirin" bölümü, yeterli alanınız olmadığında ve iki kez maskelemek / çarpmak gerektiğinde bit kaydırma / çarpma hilesi ile nasıl başa çıkılacağını gösterir)
viraptor

@viraptor: Mükemmel nokta. Bu yöntemin sınırlamalarını anlarsanız, bit manipülasyonu ile ilgili çok şey başarmak için çarpmayı gerçekten kullanabilirsiniz.
Ocak'ta Expedito

9
Çok ilginç bir şekilde AVX2'de (ne yazık ki henüz mevcut değil) tam olarak tanımladığınız işlemi yapan bir talimat var: software.intel.com/sites/products/documentation/studio/composer/…
JPvdMerwe

3
Zekice bit-twiddling algoritmaları aramak için başka bir yer MIT HAKMEM
Barmar

1
"Hacker's Delight" bağlantısı
Salles

Yanıtlar:


235

Çok ilginç bir soru ve zekice.

Tek bir baytın manipüle edilmesinin basit bir örneğine bakalım. Basitlik için imzasız 8 bit kullanma. Numaranızın olduğunu xxaxxbxxve istediğinizi düşünün ab000000.

Çözüm iki adımdan oluşuyordu: biraz maskeleme, ardından çarpma. Bit maskesi, ilginç bitleri sıfırlara dönüştüren basit bir AND işlemidir. Yukarıdaki durumda, maskeniz 00100100ve sonuç olacaktır 00a00b00.

Şimdi zor kısmı: bunu dönüştürmek ab.......

Çarpma, bir grup kaydırma ve ekleme işlemidir. Anahtar, taşmanın, ihtiyacımız olmayan bitleri "kaydırması" na izin vermek ve istediklerimizi doğru yere koymaktır.

4 ( 00000100) ile çarpma, 2 ile kalan her şeyi değiştirir ve sizi alır a00b0000. Almak için byukarı taşımak için biz + 4 (b yukarı taşımak için) (doğru yerde a tutmak için) 1 ile çarpmak gerekir. Bu toplam 5'tir ve önceki 4'le birleştiğinde sihirli bir sayı olan 20 veya alırız 00010100. Orijinal 00a00b00maskelemeden sonraydı; çarpma şunu verir:

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

Bu yaklaşımdan daha büyük sayılara ve daha fazla bite genişletebilirsiniz.

Sorduğunuz sorulardan biri "bu, herhangi bir sayıda bitle yapılabilir mi?" Birkaç maskeleme işlemine veya çarpma işlemine izin vermedikçe, cevabın "hayır" olduğunu düşünüyorum. Sorun "çarpışmalar" meselesidir - örneğin, yukarıdaki problemdeki "başıboş b". Bunu benzer bir sayıya yapmamız gerektiğini düşünün xaxxbxxcx. Önceki yaklaşımın ardından {x 2, x {1 + 4 + 16}} = x 42'ye (oooh - her şeyin cevabına!) İhtiyacımız olduğunu düşünürdünüz. Sonuç:

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

Gördüğünüz gibi, hala çalışıyor, ama "sadece". Burada kilit nokta, istediğimiz bitler arasında her şeyi sıkıştırabilmemiz için "yeterli boşluk" olmasıdır. C'den hemen sonra dördüncü bit d ekleyemedim, çünkü c + d'yi aldığım örnekler alırdım, bitler taşıyabilir, ...

Resmi bir kanıt olmadan, sorunuzun daha ilginç kısımlarını şu şekilde cevaplarım: "Hayır, bu herhangi bir sayıda bit için işe yaramayacak. N bitlerini çıkarmak için, istediğiniz bitler arasında (N-1) boşluklara ihtiyacınız var ayıklamak veya ek maske-çarpma adımları var. "

"Bitler arasında (N-1) sıfırlar olmalıdır" kuralı için düşünebildiğim tek istisna şudur: orijinalde birbirine bitişik iki bit ayıklamak istiyorsanız, VE aynı sipariş, o zaman hala yapabilirsiniz. Ve (N-1) kuralının amacı için iki bit olarak sayılırlar.

Başka bir fikir daha var - aşağıdaki @Ternary'nin cevabından esinlenmiştir (oradaki yorumuma bakın). Her ilginç bit için, oraya gitmesi gereken bitler için alana ihtiyacınız olduğu için sağında sadece çok sayıda sıfır gerekir. Ama aynı zamanda, solda sonuç bitlerine sahip olduğu için solda çok sayıda bite ihtiyaç duyar. Eğer bir bit b n'nin m pozisyonunda biterse, solunda m-1 sıfır ve sağında nm sıfır olması gerekir. Özellikle bitler, yeniden sipariş verdikten sonraki orijinal sayı ile aynı sırada olmadığında, bu orijinal kriterlerde önemli bir gelişmedir. Bu, örneğin, 16 bitlik bir kelimenin

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

İçine kaydırılabilir

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

e ve b arasında sadece bir, d ile c arasında iki, diğerlerinde üç boşluk olmasına rağmen. N-1'e ne oldu ?? Bu durumda, a...e"bir blok" haline gelir - doğru yerde sonuçlandırmak için 1 ile çarpılırlar ve böylece "biz ücretsiz e aldık". Aynı durum b ve d için de geçerlidir (b sağda üç boşluğa, d solunda aynı üçe ihtiyaç duyar). Yani sihirli sayıyı hesapladığımızda, kopyalar olduğunu görüyoruz:

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

Açıkçası, bu sayıları farklı bir sırayla istiyorsanız, daha fazla boşluk bırakmanız gerekir. (N-1)Kuralı yeniden formüle edebiliriz : "Her zaman bitler arasında en az (N-1) boşluk varsa veya nihai sonuçtaki bitlerin sırası biliniyorsa, m bitinde m biti biterse her zaman işe yarayacaktır. n, solunda m-1 sıfır ve sağında nm sıfır olması gerekir. "

@Ternary, bu kuralın işe yaramadığına dikkat çekti, çünkü "sadece hedef alanın sağına" ekleyen bitlerden bir taşıma olabilir - yani, aradığımız bitlerin hepsi bu olduğunda. Yukarıda verdiğim örneğe 16 bitlik bir kelimeyle sıkıca paketlenmiş beş bitle devam ettim:

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

Basitlik için, bit konumlarını adlandıracağım ABCDEFGHIJKLMNOP

Yapacağımız matematik

ABCDEFGHIJKLMNOP

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

Şimdiye kadar, aşağıda abcde(pozisyonlar ABCDE) hiçbir şeyin önemli olmayacağını düşündük , ama aslında, @Ternary'nin işaret ettiği gibi, eğer b=1, c=1, d=1o zaman (b+c)pozisyon Gbiraz pozisyona taşınacaksa F, yani (d+1)pozisyon Fbiraz içine taşınacak E- ve bizim sonuç şımarık. En az anlamlı ilgi bitinin sağındaki alanın ( cbu örnekte) önemli olmadığına dikkat edin, çünkü çarpma beyondan sıfırlarla en az önemli bitin dolmasına neden olacaktır.

Bu yüzden (m-1) / (nm) kuralımızı değiştirmemiz gerekiyor. Eğer sağda "tam olarak (nm) kullanılmayan bitleri olan birden fazla bit varsa (desendeki son biti saymazsak - yukarıdaki örnekte" c "), o zaman kuralı güçlendirmeliyiz - ve tekrar tekrar yapın!

Biz (nm) kriterini karşılayan bit numarasından sadece bakmak zorunda değil, aynı zamanda (nm + 1), vb edelim çağrısı (tam sayıları Q0 altındadır olanlar n-mbir sonraki bit), Q1 ( n-m + 1), Q (N-1) (n-1) 'e kadar. Sonra biz taşıma riski varsa

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

Buna bakarsanız, basit bir matematiksel ifade yazarsanız,

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

ve sonuç W > 2 * N, o zaman RHS ölçütünü bir kat artırmanız gerekir (n-m+1). Bu noktada operasyon W < 4; bu işe yaramazsa kriteri bir kez daha artırın vb.

Yukarıdakileri takip etmenin cevabınıza uzun bir yol alacağını düşünüyorum ...


1
Harika. Bir başka ince sorun: m-1 / nm testi, taşıma bitleri nedeniyle bir süre başarısız oluyor. Deneyin ... b..c ... d - her ikisi de 1 ise, clobbers d (!)
Ternary

1
upshot: n-1 bitlik alan (iea ... b..c ... d) çalışması gereken yapılandırmaları yasaklar ve m-1 / nm çalışmayanlara izin verir (a ... b..c ... d). Hangisinin işe yarayıp hangilerinin işe yaramayacağını tanımlamak için basit bir yol bulamadım.
Üçlü

İyisin! Taşıma problemi, her bir bitin sağında "koruma" olarak biraz daha fazla alana ihtiyacımız olduğu anlamına gelir. İlk bakışta, tam olarak sağda minimum nm'ye sahip en az iki bit varsa, alanı 1 arttırmanız gerekir. Daha genel olarak, P gibi bitler varsa, log2 (P) ek bitlerine ihtiyacınız vardır. asgari (mn) olan herhangi birinin hakkı. Sana doğru görünüyor mu?
Floris

Son yorum çok basitti. En son düzenlenen cevabımın log2 (P) 'nin doğru bir yaklaşım olmadığını gösteriyor. @ Ternary'nin kendi cevabı (aşağıda), garantili bir çözümünüz yoksa belirli bir bit kombinasyonunu nasıl söyleyebileceğinizi zarif bir şekilde gösterir - yukarıdaki çalışmanın biraz daha ayrıntılı olduğuna inanıyorum.
Floris

1
Muhtemelen bir tesadüf, ama bu cevap upvotes sayısı 127'ye ulaştığında kabul edildi. Şimdiye kadar okuduysan, benimle gülümseyeceksin ...
Floris

154

Gerçekten çok ilginç bir soru. İki sentime giriyorum, yani bitvector teorisi üzerinde birinci dereceden mantık açısından bu gibi sorunları belirlemeyi başarabilirseniz, teorem kanıtlayıcıları arkadaşınızdır ve size çok hızlı bir şekilde sağlayabilir sorularınıza cevaplar. Bir teorem olarak sorulan sorunu yeniden belirtelim:

"Bazı 64-bit sabitler 'mask' ve 'multiplicand' var, öyle ki, tüm 64-bit bitvector'lar için, y = (x & mask) * multiplicand ifadesinde, y.63 == x.63 , y.62 == x.55, y.61 == x.47 vb. "

Bu cümle aslında bir teorem ise, 'mask' ve 'multiplicand' sabitlerinin bazı değerlerinin bu özelliği karşıladığı doğrudur. Öyleyse bunu bir teorem uzmanının anlayabileceği bir şey, yani SMT-LIB 2 girişi olarak ifade edelim:

(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)

Ve şimdi teorem kanıtlayıcı Z3'e bunun bir teorem olup olmadığını soralım:

z3.exe /m /smt2 ExtractBitsThroughAndWithMultiplication.smt2

Sonuç:

sat
(model
  (define-fun mask () (_ BitVec 64)
    #x8080808080808080)
  (define-fun multiplicand () (_ BitVec 64)
    #x0002040810204081)
)

Bingo! Orijinal yazıda verilen sonucu 0,06 saniyede yeniden üretir.

Buna daha genel bir perspektiften baktığımızda, bunu, birkaç makalenin yayınlandığı yeni bir araştırma alanı olan birinci dereceden bir program sentezi sorununun bir örneği olarak görebiliriz. İçin bir arama "program synthesis" filetype:pdfbaşlamanız gerekir.


2
Etkilendim! "Bitvector teorisi üzerindeki birinci dereceden mantığın" insanların incelediği gerçek bir konu olduğunu bile bilmiyordum - bu ilginç sonuçlar verebilir. Bunu paylaştığın için çok teşekkürler.
Floris

@AndrewBacker: Birisi beni bu "iş olarak SO" olarak adlandırılan şeyde hangi noktada olduğunu aydınlatabilir mi? Yani, hiçbir şey ödemiyor . Yalnız SO temsilcisi üzerinde yaşayamazsınız. Belki röportajlarda size puan verebilir. Olabilir. Eğer işyeri SO temsilcisinin değerini tanıyacak kadar iyiyse ve bu verilen bir şey değilse ...
Monica'yı yeniden eski haline getirin

3
Elbette. SO aynı zamanda birçok insan için bir oyun (puanları olan bir şey). Sadece insan doğası, / r / new'de avlanma gibi, böylece ilk yorumu gönderebilir ve karma alabilirsiniz. Cevaplar hala iyi olduğu sürece bu konuda kötü bir şey yok. Birisinin gerçekten fark ettiğini fark ettiği zaman birinin zamanını ve çabasını değerlendirebildiğim için çok mutluyum. Teşvik iyi şeylerdir :) Ve ... bu gerçekten eski bir yorumdu ve hala doğruydu. Nasıl net olmadığını göremiyorum.
Andrew Backer

88

Çarpandaki her 1 bitlik bitlerden birini doğru konumuna kopyalamak için kullanılır:

  • 1zaten doğru konumda, bu nedenle ile çarpın 0x0000000000000001.
  • 27 bit konumların sola kaydırılması gerekir, bu nedenle çarpıyoruz 0x0000000000000080(bit 7 ayarlanmıştır).
  • 314 bit konumların sola kaydırılması gerekir, bu nedenle çarpıyoruz 0x0000000000000400(bit 14 ayarlanmıştır).
  • ve benzeri
  • 849 bit konumların sola kaydırılması gerekir, bu nedenle çarpıyoruz 0x0002000000000000(bit 49 ayarlanmıştır).

Çarpan, her bir bit için çarpanların toplamıdır.

Bu sadece çalışır çünkü toplanacak bitler birbirine çok yakın değildir, böylece şemamızda bir araya gelmeyen bitlerin çarpımı ya 64 bitin ötesine ya da alt bakımsız bölüme düşer.

Orijinal sayıdaki diğer bitlerin olması gerektiğini unutmayın 0. Bu, bir AND işlemiyle maskeleyerek elde edilebilir.


2
Harika bir açıklama! Kısa cevabınız, "sihirli sayının" değerini hızlı bir şekilde bulmayı mümkün kıldı.
Ocak'ta Expedito

4
Bu gerçekten en iyi cevap, ama önce @ floris'in cevabını (ilk yarısını) okumadan çok yararlı olmazdı.
Andrew Backer

29

(Daha önce hiç görmemiştim. Bu numara harika!)

Floris'in nbitleri çıkarırken n-1ardışık olmayan bitler arasında boşluk olması gerektiği iddiasını biraz genişleteceğim :

İlk düşüncem (bunun nasıl işe yaramadığını bir dakika içinde göreceğiz) daha iyisini yapabileceğinizdi: Bitleri ayıklamak istiyorsanız n, ieğer varsa ( - bit ile ardışık i)i-1 önceki bit veya n-idaha sonraki bit.

Açıklamak için birkaç örnek vereceğim:

...a..b...c...İşler (2 bit içindeki kimse a, önceki bit ve bit sonrası bhiç kimse ve önceki 2 bit içindeki kimse yok c):

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

...a.b....c...Başarısız olduğu biçin 2 bit sonra a(ve biz vardığımızda başkasının yerine çekilir a):

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

...a...b.c...Başarısız çünkü bönceki 2 bitte c(ve biz vardığımızda başkasının yerine itiliyor c):

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

...a...bc...d... Ardışık bitler birlikte değiştiği için çalışır:

  a000bc000d
+ 0bc000d000
+ 000d000000
= abcd000000

Ama bir sorunumuz var. n-iBunun yerine kullanırsak n-1şu senaryoyu kullanabiliriz: önemsediğimiz parçanın dışında bir çarpışma olursa, sonunda maskeleyeceğimiz, ancak taşıma bitleri önemli maskelenmemiş aralığa müdahale eden bir şey ? (ve not: n-1gereksinim i-1, ith bitini değiştirdiğimizde maskelenmemiş aralığımızdan sonraki bitlerin net olduğundan emin olarak bunun gerçekleşmemesini sağlar )

...a...b..c...d...Carry-bit Potansiyel yetmezliği, colduğu n-1sonra b, ancak tatmin n-ikriterleri:

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

Öyleyse neden bu " n-1alan bitleri" gereksinimine geri dönmüyoruz ? Çünkü daha iyisini yapabiliriz :

...a....b..c...d.. " n-1Alan bitleri" testinde başarısız olur , ancak bit çıkarma hilemiz için çalışır :

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

Ben bu alanları karakterize etmek için iyi bir yol ile gelip olamaz yok olması n-1bizim operasyon için çalışacak hala önemli bit arasındaki boşluğu ancak. Ancak, hangi bitlerle ilgilendiğimizi önceden bildiğimiz için, taşıma biti çarpışmalarını yaşamadığımızdan emin olmak için filtremizi kontrol edebiliriz:

(-1 AND mask) * shiftBeklenen hepsi sonuçla karşılaştır -1 << (64-n)(64 bit imzasız için)

Bitlerimizi çıkarmak için sihirli kaydırma / çarpma, sadece ikisi eşitse çalışır.


Beğendim - her bit için, oraya gitmeniz gereken bitler için alana ihtiyacınız olduğu için sadece sağında çok sayıda sıfırın olması gerektiği konusunda haklısınız. Ama aynı zamanda , solda sonuç bitlerine sahip olduğu için solda çok sayıda bite ihtiyaç duyar. Biraz Yani eğer bpozisyonunda biter marasında n, o zaman olması gerekir m-1onun solunda sıfır ve n-m-1onun sağında sıfır. Özellikle bitler, yeniden sipariş verildikten sonraki orijinal sayı ile aynı sırada olmadığında, bu orijinal kriterlerde önemli bir gelişmedir. Bu eğlenceli.
Floris

13

Bu çok ilginç soruya zaten mükemmel cevaplara ek olarak, bu bitsel çarpma hilesinin 2007'den beri Magic BitBoards adı altında olduğu bilgisayar satranç topluluğunda bilindiğini bilmek yararlı olabilir .

Birçok bilgisayar satranç motoru, çeşitli parça setlerini (işgal edilen kare başına 1 bit) temsil etmek için birkaç 64 bit tam sayı (bitboard olarak adlandırılır) kullanır. Belirli bir başlangıç ​​meydanındaki kayan bir parçanın (kale, piskopos, kraliçe), Kengelleme parçası yoksa en fazla kareye gidebileceğini varsayın . Kİşlenmiş karelerin bitboarduyla bitsel ve dağınık bitlerin kullanılması, K64 bitlik bir tam sayıya gömülü belirli bir bitlik sözcük verir .

Sihirli çarpma, bu dağınık Kbitleri K64 bitlik bir tam sayının alt bitlerine eşlemek için kullanılabilir . Bu alt Kbitler daha sonra, başlangıçtaki karedeki parçanın gerçekten hareket edebileceği izin verilen kareleri temsil eden önceden hesaplanmış bitboardların bir tablosunu endekslemek için kullanılabilir (bloklama parçalarının bakımı vb.)

Bu yaklaşımı kullanan tipik bir satranç motoru, önceden hesaplanmış sonuçları içeren 64 girişten (başlangıç ​​kare başına bir) olmak üzere 2 tabloya (bir tanesi piskoposlar, bir piskoposlar, her ikisinin kombinasyonunu kullanan kraliçeler) sahiptir. Hem en yüksek puan alan kapalı kaynak ( Houdini ) hem de açık kaynak satranç motoru ( Stockish ) şu anda çok yüksek performansı için bu yaklaşımı kullanmaktadır.

Bu sihirli çarpanları bulmak, kapsamlı bir arama (erken kesintilerle optimize edilmiş) veya deneme ve erorr ile yapılır. (örneğin, rastgele 64 bit tamsayıların ) kullanılarak yapılır. Hareket üretimi sırasında hiçbir sihirli sabit bulunamayan bit kalıpları kullanılmamıştır. Bununla birlikte, eşlenecek bitler (neredeyse) bitişik endekslere sahip olduğunda, bitsel taşıma etkileri tipik olarak gereklidir.

@Syzygy'nin çok genel SAT çözücü yaklaşımı olan AFAIK, bilgisayar satrancında kullanılmadı ve bu sihirli sabitlerin varlığı ve tekliği ile ilgili herhangi bir resmi teori de görünmüyor.


Tam gelişmiş resmi CS geçmişine sahip olan herkesin bu sorunu gördükten sonra doğrudan SAT yaklaşımına atlayacağını düşünürdüm. Belki de CS halkı satrancı ilgisiz bulur? :(
Monica'yı

@KubaOber Çoğunlukla başka bir yol var: bilgisayar satrancı, C veya montajda program yapan ve her türlü soyutlamadan (C ++, şablonlar, OO) nefret eden bit-twiddlers tarafından yönetiliyor. Sanırım gerçek CS çocuklar korkutuyor :-)
TemplateRex
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.