ทำไมถ้า (n & -n) == n แล้ว n เป็นเลขยกกำลัง 2?


84

บรรทัดที่ 294 ของแหล่งที่มา java.util.Randomกล่าวว่า

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

ทำไมถึงเป็นแบบนี้?


2
แท็กใหม่ควรเป็นคำใบ้ :)
bzlm

10
นี่คือคำตอบ: stackoverflow.com/questions/600293/…
Jacob Mattison

2
ทำตามบิต อนึ่งมันยังนับศูนย์เป็นกำลังสองด้วย สูตร(n & (n - 1)) == 0นี้ยังใช้งานได้ (จะลบบิตลำดับต่ำสุดหากไม่มีบิตเหลืออยู่แสดงว่ามีการตั้งค่าสูงสุด 1 บิตตั้งแต่แรก)
harold

3
ใช่ฉันขอสารภาพผิดในการใช้รหัสดังกล่าว มีกลเม็ดมากมายเช่นนี้ที่คุณสามารถเล่นได้ตราบใดที่คุณรู้ว่าคุณกำลังจัดการกับเลขคณิตเสริมของ 2 และยังคงตระหนักถึงการแปลงต่างๆและข้อผิดพลาดที่มากเกินไป สำหรับเครดิตพิเศษให้หาวิธีปัดเศษขึ้นเป็นกำลังสองที่สูงขึ้นถัดไปหรืออาจเป็นกำลังสอง - 1 - สิ่งที่ต้องทำด้วยความถี่ที่น่าประหลาดใจในบางไตรมาส
Hot Licks

1
เดี๋ยวก่อนทุกคนอ่านจากแหล่ง java.util.Random สมัยนี้หรือเปล่า? (ฉันอ่านเมื่อสองสามเดือนก่อนและฉันจำคำถามสองสามข้อเกี่ยวกับเรื่องนี้ใน SO ตั้งแต่นั้นมา)
Mateen Ulhaq

คำตอบ:


48

คำอธิบายไม่ถูกต้องทั้งหมดเพราะ(0 & -0) == 0แต่ 0 ไม่ใช่กำลังสอง วิธีที่ดีกว่าที่จะพูดคือ

((n & -n) == n) เมื่อ n เป็นเลขยกกำลังสองหรือลบของกำลังสองหรือศูนย์

ถ้า n เป็นเลขยกกำลังสอง n ในเลขฐานสองจะเป็น 1 ตัวเดียวตามด้วยเลขศูนย์ -n ในส่วนเติมเต็มของสองคือผกผัน + 1 ดังนั้นบิตจึงเรียงตัวกัน

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

หากต้องการดูว่าเหตุใดจึงทำงานนี้ให้พิจารณาส่วนเติมเต็มสองส่วนเป็นผกผัน + 1 -n == ~n + 1

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

เนื่องจากคุณพกติดตัวไปตลอดเมื่อเพิ่มหนึ่งเพื่อให้ได้ส่วนเสริมของทั้งสอง

ถ้า n เป็นสิ่งอื่นที่ไม่ใช่กำลังของสอง would ผลลัพธ์จะหายไปเล็กน้อยเพราะส่วนเติมเต็มของทั้งสองจะไม่มีบิตที่ตั้งไว้สูงสุดเนื่องจากการพกพานั้น

† - หรือศูนย์หรือลบของกำลังสอง ... ตามที่อธิบายไว้ด้านบน


และมีเคล็ดลับในการแยก 1 บิตที่มีนัยสำคัญน้อยที่สุด
Hot Licks

2
ในฐานะที่เป็น(0 & -0) == 0, คำสั่งทันที preceedingif (n <= 0) throw ...คือ หมายความว่าตัวเลขที่อยู่ระหว่างการทดสอบจะไม่เป็น 0 (หรือลบ) ณ จุดนั้น
ผู้ใช้

1
@ ไมเคิลค่อนข้างถูกต้อง ฉันกำลังตอบคำถามในหัวข้อไม่วิพากษ์วิจารณ์Random.javaที่ฉันยังไม่ได้อ่าน
Mike Samuel

1
@ ไมค์ฉันตระหนักดีว่า อย่างไรก็ตามฉันไม่คิดว่าข้อความในความคิดเห็นในรหัส (ซึ่งรวมอยู่ในคำถามและเป็นพื้นฐานสำหรับคำถามในชื่อเรื่อง) ค่อนข้างยืนอยู่ในตัวเองเมื่อไม่เห็นในบริบทของข้อกำหนดเบื้องต้นที่กำหนดไว้ก่อนหน้านี้ ลงในโค้ด หากคุณดูเฉพาะคำถามที่โพสต์ไว้ที่นี่เราไม่รู้ด้วยซ้ำว่าประเภทnคืออะไร ฉันไม่ได้ตรวจสอบสมมติฐานนี้ แต่สงสัยว่าdoubleจะมีพฤติกรรมแบบเดียวกัน
ผู้ใช้

3
@ ไมเคิลเราสามารถกำหนดขอบเขตที่ดีในประเภทของnเนื่องจากคำถามนี้มีแท็ก "java" &ไม่ได้กำหนดไว้บนdoubleหรือfloatใน Java กำหนดไว้เฉพาะประเภทจำนวนเต็มและบูลีนเท่านั้น เนื่องจาก-ไม่ได้กำหนดไว้สำหรับบูลีนเราสามารถสรุปได้อย่างปลอดภัยว่าnเป็นอินทิกรัล
Mike Samuel

95

เพราะในส่วนเสริมของ 2 -nคือ~n+1.

ถ้าnเป็นเลขยกกำลัง 2 ก็จะมีเพียงชุดบิตเดียว ดังนั้นจึง~nมีการตั้งค่าบิตทั้งหมดยกเว้นบิตนั้น เพิ่ม 1 และคุณตั้งค่าบิตพิเศษอีกครั้งเพื่อให้มั่นใจว่าจะมีค่าเท่ากับn & (that thing)n

การสนทนายังเป็นจริงเนื่องจาก 0 และจำนวนลบถูกตัดออกโดยบรรทัดก่อนหน้าในซอร์ส Java นั้น หากnมีการตั้งค่ามากกว่าหนึ่งบิตหนึ่งในนั้นคือบิตที่สูงที่สุด บิตนี้จะไม่ถูกตั้งค่า+1เนื่องจากมีบิตที่ชัดเจนต่ำกว่าเพื่อ "ดูดซับ":

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

13

คุณต้องดูค่าเป็นบิตแมปเพื่อดูว่าเหตุใดจึงเป็นจริง:

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

ดังนั้นถ้าทั้งสองช่องเป็น 1 จะได้ 1 ออกมา

ตอนนี้ -n เติมเต็ม 2 มันเปลี่ยนแปลงทั้งหมด0ไป1และจะเพิ่ม 1

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

อย่างไรก็ตาม

8 = 00001000
-8 = 11110111 + 1 = 11111000 

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

สำหรับพาวเวอร์ 2 เท่านั้นที่จะ(n & -n)เป็น n
เนื่องจากกำลังของ 2 แสดงเป็นบิตเซตเดียวในทะเลยาวของศูนย์ การปฏิเสธจะให้ผลตรงกันข้ามคือศูนย์เดียว(ในจุดที่ 1 เคยเป็น)ในทะเล 1 การเพิ่ม 1 จะเลื่อนตัวล่างเข้าไปในช่องว่างที่ศูนย์อยู่
และบิตและ (&) จะกรอง 1 อีกครั้ง


8

ในการแทนค่าสองส่วนสิ่งที่ไม่ซ้ำกันเกี่ยวกับพลังของสองคือประกอบด้วย 0 บิตทั้งหมดยกเว้นบิต k โดยที่ n = 2 ^ k:

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

ในการรับค่าลบในส่วนเสริมของสองคุณพลิกบิตทั้งหมดแล้วบวกหนึ่ง สำหรับพาวเวอร์ของสองนั่นหมายความว่าคุณจะได้ 1s จำนวนหนึ่งทางด้านซ้ายจนถึงและรวม 1 บิตที่อยู่ในค่าบวกจากนั้นจึงมี 0 จำนวนหนึ่งทางด้านขวา:

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 และ 4 จะเหมือนกับคอลัมน์ 2

หากคุณดูค่าอื่น ๆ ที่ขาดหายไปจากแผนภูมินี้คุณจะเห็นว่าเหตุใดจึงไม่ถือเป็นสิ่งใดนอกจากพลังของสอง:

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) จะมีชุด 1 บิตเท่านั้นและบิตนั้นจะเป็นบิตชุดที่มีนัยสำคัญน้อยที่สุดใน n สำหรับตัวเลขทั้งหมดที่เป็นพาวเวอร์ของสองบิตชุดที่มีนัยสำคัญน้อยที่สุดคือบิตชุดเดียว สำหรับตัวเลขอื่น ๆ ทั้งหมดจะมีการตั้งค่ามากกว่าหนึ่งบิตซึ่งจะกำหนดเฉพาะค่านัยสำคัญน้อยที่สุดในผลลัพธ์


4

มันทรัพย์สินของอำนาจของ 2 ของพวกเขาและเติมเต็มสอง

ตัวอย่างเช่นใช้เวลา 8:

8  = 0b00001000

-8 = 0b11111000

การคำนวณส่วนเติมเต็มของทั้งสอง:

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

AND 8    : 0b00001000

สำหรับอำนาจของ 2 เพียงหนึ่งบิตจะถูกตั้งค่าเพื่อเพิ่มจะทำให้ n THบิตของ 2 nจะเป็นชุด (หนึ่งช่วยให้การดำเนินการกับ n THบิต) จากนั้นเมื่อคุณได้ANDตัวเลขสองตัวคุณจะได้รับต้นฉบับกลับคืนมา

สำหรับตัวเลขที่ไม่ใช่พาวเวอร์ของ 2 บิตอื่น ๆ จะไม่พลิกดังนั้นจึงANDไม่ให้จำนวนเดิม


4

ถ้า n เป็นเลขยกกำลัง 2 นั่นหมายความว่าบิตเดียวถูกตั้งค่าเป็น 1 และค่าอื่น ๆ เป็น 0:

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

and so on ...

และเนื่องจาก-nเป็นส่วนเติมเต็มของ 2 n(นั่นหมายถึงบิตเดียวที่เป็น 1 ยังคงอยู่และบิตทางด้านซ้ายของบิตนั้นจะนั่งเป็น 1 ซึ่งจริงๆแล้วไม่สำคัญเนื่องจากผลลัพธ์ของตัวดำเนินการ AND &จะเป็น 0 ถ้า หนึ่งในสองบิตเป็นศูนย์):

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.