Neden (n & -n) == n ise n 2'nin kuvveti?


84

Java.util.Random kaynağı satır 294 diyor

if ((n & -n) == n) // i.e., n is a power of 2
    // rest of the code

Bu neden?


2
Yeni etiket bir ipucu olmalıdır. :)
bzlm


2
Bitleri takip edin. Bu arada, sıfırı da ikinin kuvveti olarak sayar. Formül (n & (n - 1)) == 0ayrıca çalışır (en düşük dereceden biti kaldırır, eğer kalan bit yoksa, o zaman ilk etapta en fazla 1 bit kümesi vardır).
harold

3
Evet, böyle bir kodu kullandığım için suçumu kabul ediyorum. 2'nin tamamlayıcı aritmetiği ile uğraştığınızı bildiğiniz ve çeşitli dönüştürme ve taşma tuzaklarının farkında olduğunuz sürece oynayabileceğiniz bunun gibi bir dizi numara vardır. Ekstra kredi için, bazı çeyreklerde şaşırtıcı sıklıkta yapılması gereken şeyleri bir sonraki yüksek güç olan ikiye veya belki ikiye - 1 - nasıl yuvarlayacağınızı belirleyin.
Hot Licks

1
Bekle, bugünlerde herkes java.util.Random kaynağından mı okuyor? (Bunu birkaç ay önce okudum ve o zamandan beri
SO'da

Yanıtlar:


48

Açıklama tamamen doğru değildir çünkü (0 & -0) == 00 ikinin kuvveti değildir. Bunu söylemenin daha iyi bir yolu

((n & -n) == n) n ikinin bir kuvveti veya ikinin bir kuvvetinin negatifi veya sıfır olduğunda.

Eğer n ikinin bir kuvveti ise, ikilik tabanda n tek bir 1 ve ardından sıfırlardır. -n ikinin tümleyeninde ters + 1'dir, böylece bitler bu şekilde sıralanır

 n      0000100...000
-n      1111100...000
 n & -n 0000100...000

Bunun neden işe yaradığını görmek için ikinin tümleyenini ters + 1 olarak düşünün, -n == ~n + 1

n          0000100...000
inverse n  1111011...111
                     + 1
two's comp 1111100...000

çünkü ikisinin tamamlayıcısını elde etmek için birini eklerken sonuna kadar taşırsınız.

Eğer n, ikinin kuvvetinden başka bir † olsaydı, sonuç biraz eksik olurdu çünkü ikinin tamamlayıcısı, bu taşıma nedeniyle en yüksek bit setine sahip olmayacaktı.

† - veya sıfır veya iki kuvvetinin negatifi ... yukarıda açıklandığı gibi.


Ve burada en önemsiz 1 biti izole etmek için bir numara var.
Hot Licks

2
Gelince (0 & -0) == 0, bir yukarıda verilen açıklamada ise if (n <= 0) throw .... Bu, test edilen sayının o noktada asla 0 (veya negatif) olmayacağı anlamına gelir.
kullanıcı

1
@Michael, çok doğru. Okumadığım eleştiri değil başlıktaki soruyu cevaplıyordum Random.java.
Mike Samuel

1
@Mike, bunun farkındayım; ancak, koddaki yorumdaki ifadenin (soruda yer alan ve başlıktaki sorunun temelini oluşturur), daha önce oluşturulan ön koşullar bağlamında görülmediğinde tamamen kendi başına durduğunu düşünmüyorum. ona kodda. Yalnızca burada yayınlanan soruya bakarsanız , türünün ne nolduğu hakkında hiçbir şey bilmiyoruz ; Bu varsayımı kontrol etmedim, ancak bir şekilde doublea'nın da aynı şekilde davranacağından şüpheliyim .
kullanıcı

3
@Michael, nBu soru "java" etiketine sahip olduğu için türüne oldukça iyi sınırlar koyabiliriz . Java'da veya içinde &tanımlanmamıştır . Yalnızca tam sayı türlerinde ve mantıksal değerde tanımlanır. Yana boolelerde için tanımlı değil, biz güvenle anlaması olabilir ayrılmaz bir parçasıdır. doublefloat-n
Mike Samuel

95

Çünkü 2'nin belirtilen bütünleyici, -nolduğu ~n+1.

Eğer n2 'lik bir güçtür, o zaman sadece bir bit kümesi vardır. Onun ~ndışındaki tüm bitler de öyle. 1 ekleyin ve özel biti tekrar ayarlayarak n & (that thing)eşit olmasını sağlayın n.

Bunun tersi de doğrudur çünkü 0 ve negatif sayılar, o Java kaynağındaki önceki satır tarafından dışlanmıştır. Birden nfazla bit setine sahipse, bunlardan biri bu tür en yüksek bittir. Bu biraz edecek değil ayarlanabilir +1bir alt net biraz orada çünkü onu "absorbe":

 n: 00001001000
~n: 11110110111
-n: 11110111000  // the first 0 bit "absorbed" the +1
        ^
        |
        (n & -n) fails to equal n at this bit.

13

Bunun neden doğru olduğunu görmek için değerlere bit eşlemler olarak bakmanız gerekir:

1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0

Yani sadece her iki alan da 1 ise 1 çıkacaktır.

Şimdi -n bir 2'nin tamamlayıcısı yapar. Tümünü olarak 0değiştirir 1ve 1 ekler.

7 = 00000111
-1 = NEG(7) + 1 = 11111000 + 1 = 11111001

ancak

8 = 00001000
-8 = 11110111 + 1 = 11111000 

00001000  (8)
11111000  (-8)
--------- &
00001000 = 8.

Sadece 2'nin kuvvetleri (n & -n)için n olacaktır .
Bunun nedeni, 2'nin gücünün uzun bir sıfır denizinde tek bir küme biti olarak temsil edilmesidir. Olumsuzlama, 1'lerin denizinde tam tersi, tek bir sıfır (1'in olduğu yerde) verecektir . 1 eklemek, düşük olanları sıfırın olduğu boşluğa kaydıracaktır.
Ve bitsel ve (&) 1'i tekrar filtreleyecektir.


8

İkinin tümleyen gösteriminde, ikinin üsleri ile ilgili benzersiz olan şey, bunların n = 2 ^ k olduğu k'inci bit haricinde tüm 0 bitlerden oluşmasıdır:

base 2    base 10
000001 =  1 
000010 =  2
000100 =  4
     ...

İkinin tümleyeninde negatif bir değer elde etmek için, tüm bitleri çevirir ve bir eklersiniz. İkinin üsleri için bu, solda pozitif değerde olan 1 bit dahil olmak üzere bir grup 1 ve sonra sağda bir grup 0 elde edeceğiniz anlamına gelir:

n   base 2  ~n      ~n+1 (-n)   n&-n  
1   000001  111110  111111      000001
2   000010  111101  111110      000010
4   000100  111011  111100      000100
8   001000  110111  111000      001000

2. ve 4. sütunun sonucunun 2. sütun ile aynı olacağını kolayca görebilirsiniz.

Bu çizelgede eksik olan diğer değerlere bakarsanız, bunun neden ikinin gücü dışında hiçbir şeyi tutmadığını görebilirsiniz:

n   base 2  ~n      ~n+1 (-n)   n&-n  
1   000001  111110  111111      000001
2   000010  111101  111110      000010
3   000011  111100  111101      000001
4   000100  111011  111100      000100
5   000101  111010  111011      000001
6   000110  111001  111010      000010
7   000111  111000  111001      000001
8   001000  110111  111000      001000

n & -n (n> 0 için) sadece 1 bit setine sahip olacaktır ve bu bit n'deki en az anlamlı set biti olacaktır. İkinin üssü olan tüm sayılar için, en az anlamlı olan küme biti tek ayarlı bittir. Diğer tüm sayılar için, sonuçta yalnızca en önemsiz olanı ayarlanacak birden fazla bit kümesi vardır.


4

Bu, 2'nin kuvvetlerinin ve ikisinin tamamlayıcısının özelliği .

Örneğin, 8 al:

8  = 0b00001000

-8 = 0b11111000

İkisinin tamamlayıcısını hesaplamak:

Starting:  0b00001000
Flip bits: 0b11110111  (one's complement)
Add one:   0b11111000  

AND 8    : 0b00001000

2'nin katları için, yalnızca bir bit ayarlanacaktır, bu nedenle ekleme , 2 n'nin n'inci bitinin ayarlanmasına neden olacaktır (biri n'inci biti taşımaya devam eder ). Sonra senAND iki sayıyı aldığınızda, orijinali geri alırsınız.

2'nin üsleri olmayan sayılar için, diğer bitler çevrilmeyecek ve böylece ANDorijinal sayıyı vermeyecektir.


4

Basitçe, eğer n 2'nin bir üssü ise, bu sadece bir bitin 1'e ayarlandığı ve diğerlerinin 0 olduğu anlamına gelir:

00000...00001 = 2 ^ 0
00000...00010 = 2 ^ 1
00000...00100 = 2 ^ 2
00000...01000 = 2 ^ 3
00000...10000 = 2 ^ 4

and so on ...

ve çünkü -n2'nin bir tamamlayıcısıdır n(bu, 1 olan tek bitin olduğu gibi kaldığı ve bu bitin sol tarafındaki bitlerin 1'e oturduğu anlamına gelir ki bu aslında önemli değildir, çünkü AND operatörünün sonucu &0 olacaktır. iki bitten biri sıfırdır):

000000...000010000...00000 <<< n
&
111111...111110000...00000 <<< -n
--------------------------
000000...000010000...00000 <<< n

0

Örnek olarak gösterilir:

Onaltılık 8 = 0x000008

Onaltılık olarak -8 = 0xFFFFF8

8 & -8 = 0x000008

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.