ตัวดำเนินการเปลี่ยนแปลงระดับบิต (bit-shift) คืออะไรและทำงานอย่างไร


1382

ฉันพยายามเรียนรู้ C ในเวลาว่างและภาษาอื่น ๆ (C #, Java และอื่น ๆ ) มีแนวคิดเดียวกัน (และบ่อยครั้งที่ผู้ให้บริการรายเดียวกัน) ...

สิ่งที่ฉันสงสัยคือในระดับหลักสิ่งที่ไม่บิตขยับ ( <<, >>, >>>) ทำสิ่งที่เป็นปัญหาก็สามารถช่วยแก้ปัญหาและสิ่งที่ gotchas แฝงตัวอยู่รอบโค้งหรือไม่ ในคำอื่น ๆ คู่มือเริ่มต้นที่แน่นอนในการขยับเล็กน้อยในทุกความดีของมัน


2
เคสที่ใช้งานได้หรือใช้งานไม่ได้ซึ่งคุณจะใช้การยกระดับบิตใน 3GL มีน้อย
Troy DeMonbreun

15
หลังจากอ่านคำตอบเหล่านี้แล้วคุณอาจต้องการดูลิงค์เหล่านี้: graphics.stanford.edu/~seander/bithacks.html & jjj.de/bitwizardry/bitwizardrypage.html
กรงเล็บ

1
สิ่งสำคัญคือต้องทราบว่าการเลื่อนบิตเป็นเรื่องง่ายและรวดเร็วสำหรับคอมพิวเตอร์ที่ต้องทำ ด้วยการค้นหาวิธีที่จะใช้การเปลี่ยนบิตในโปรแกรมของคุณคุณสามารถลดการใช้หน่วยความจำและเวลาดำเนินการได้อย่างมาก
Hoytman

@Hoytman: แต่โปรดทราบว่าคอมไพเลอร์ที่ดีรู้ถึงเทคนิคต่าง ๆ เหล่านี้อยู่แล้ว
เซบาสเตียนมัค

คำตอบ:


1713

ตัวดำเนินการเปลี่ยนบิตทำสิ่งที่ชื่อของพวกเขามีความหมาย พวกเขาเปลี่ยนบิต ต่อไปนี้เป็นคำแนะนำสั้น ๆ (หรือไม่สั้น) เกี่ยวกับตัวดำเนินการกะที่แตกต่างกัน

ผู้ประกอบการ

  • >> เป็นตัวดำเนินการทางด้านขวาของเลขคณิต (หรือที่ลงชื่อ)
  • >>> เป็นตัวดำเนินการ shift ที่เหมาะสม (หรือไม่ได้ลงชื่อ) ตรรกะ
  • << เป็นโอเปอเรเตอร์กะซ้ายและตอบสนองความต้องการของการเลื่อนทั้งแบบลอจิคัลและเลขคณิต

ทั้งหมดของผู้ประกอบการเหล่านี้สามารถนำไปใช้กับค่าจำนวนเต็ม ( int, longอาจจะเป็นshortและbyteหรือchar) ในบางภาษาใช้ประกอบการเปลี่ยนไปใช้ประเภทข้อมูลใด ๆ ที่มีขนาดเล็กกว่าโดยอัตโนมัติปรับขนาดตัวถูกดำเนินการที่จะเป็นintint

โปรดทราบว่า<<<ไม่ใช่ตัวดำเนินการเนื่องจากจะซ้ำซ้อน

นอกจากนี้ทราบว่าC และ C ++ ไม่เห็นความแตกต่างระหว่างผู้ประกอบการเปลี่ยนแปลงทางด้านขวา พวกเขาให้บริการเฉพาะ>>ผู้ประกอบการและพฤติกรรมการเปลี่ยนด้านขวาเป็นการใช้งานที่กำหนดไว้สำหรับประเภทที่ลงนาม คำตอบที่เหลือใช้ตัวดำเนินการ C # / Java

(ในการใช้งาน C และ C ++ ที่สำคัญทั้งหมดรวมถึง GCC และ Clang / LLVM >>สำหรับประเภทที่ลงนามคือเลขคณิตรหัสบางอย่างถือว่านี่ แต่มันไม่ใช่สิ่งที่รับประกันมาตรฐาน แต่ก็ไม่ได้กำหนดแต่มาตรฐานนั้นต้องมีการนำไปใช้เพื่อกำหนด อีกวิธีหนึ่งอย่างไรก็ตามการเลื่อนซ้ายของตัวเลขที่มีเครื่องหมายลบนั้นเป็นพฤติกรรมที่ไม่ได้กำหนดไว้ (ล้นจำนวนเต็มที่ลงนามแล้ว) ดังนั้นหากคุณไม่จำเป็นต้องใช้การเลื่อนขวาแบบเลขคณิตคุณควรทำการเปลี่ยนบิตด้วยประเภทที่ไม่ได้ลงนาม)


เลื่อนไปทางซ้าย (<<)

จำนวนเต็มถูกเก็บไว้ในหน่วยความจำเป็นชุดของบิต ตัวอย่างเช่นหมายเลข 6 ที่จัดเก็บเป็น 32 บิตintจะเป็น:

00000000 00000000 00000000 00000110

การเลื่อนรูปแบบบิตนี้ไปทางซ้ายหนึ่งตำแหน่ง ( 6 << 1) จะส่งผลให้หมายเลข 12:

00000000 00000000 00000000 00001100

อย่างที่คุณเห็นตัวเลขได้เลื่อนไปทางซ้ายหนึ่งตำแหน่งและตัวเลขสุดท้ายทางด้านขวาเต็มไปด้วยศูนย์ นอกจากนี้คุณยังอาจจะทราบว่าการขยับซ้ายจะเทียบเท่ากับการคูณโดยอำนาจของ 2 ดังนั้น6 << 1จะเทียบเท่ากับ6 * 2และเทียบเท่ากับ6 << 3 6 * 8คอมไพเลอร์การปรับให้เหมาะสมที่ดีจะแทนที่การคูณด้วยการเลื่อนเมื่อทำได้

การขยับแบบไม่เป็นวงกลม

โปรดทราบว่าสิ่งเหล่านี้ไม่ใช่การเปลี่ยนแปลงแบบวงกลม เลื่อนค่านี้ไปทางซ้ายทีละตำแหน่ง ( 3,758,096,384 << 1):

11100000 00000000 00000000 00000000

ผลลัพธ์ใน 3,221,225,472:

11000000 00000000 00000000 00000000

ตัวเลขที่ได้รับการเลื่อน "ปิดท้าย" จะหายไป มันไม่ได้พันรอบ


ลอจิกกะขวา (>>>)

การเลื่อนขวาแบบลอจิคัลคือการสนทนาไปทางซ้าย แทนที่จะขยับบิตไปทางซ้ายพวกเขาก็เลื่อนไปทางขวา ตัวอย่างเช่นการขยับหมายเลข 12:

00000000 00000000 00000000 00001100

ไปทางขวาโดยตำแหน่งหนึ่ง ( 12 >>> 1) จะได้รับ 6 ดั้งเดิมคืนของเรา:

00000000 00000000 00000000 00000110

ดังนั้นเราจะเห็นว่าการขยับไปทางขวาเทียบเท่ากับการหารด้วยพลังของ 2

บิตที่หายไปจะหายไป

อย่างไรก็ตามกะไม่สามารถเรียกคืนบิต "ที่หายไป" ได้ ตัวอย่างเช่นถ้าเราเปลี่ยนรูปแบบนี้:

00111000 00000000 00000000 00000110

ทางซ้าย 4 ตำแหน่ง ( 939,524,102 << 4) เราได้รับ 2,147,483,744:

10000000 00000000 00000000 01100000

แล้วขยับกลับ ( (939,524,102 << 4) >>> 4) เราจะได้ 134,217,734:

00001000 00000000 00000000 00000110

เราไม่สามารถคืนค่าเดิมของเราได้เมื่อเราทำบิตหาย


การเลื่อนขวาของเลขคณิต (>>)

การเลื่อนขวาของเลขคณิตนั้นเหมือนกับการเลื่อนขวาแบบลอจิคัลยกเว้นการเว้นระยะห่างจากศูนย์มันจะมีบิตที่สำคัญที่สุด นี่เป็นเพราะบิตที่สำคัญที่สุดคือบิตเครื่องหมายหรือบิตที่แยกความแตกต่างระหว่างจำนวนบวกและลบ ด้วยการเติมด้วยบิตที่สำคัญที่สุดการเลื่อนขวาของเลขคณิตจะเป็นการรักษาเครื่องหมาย

ตัวอย่างเช่นหากเราตีความรูปแบบบิตนี้เป็นจำนวนลบ:

10000000 00000000 00000000 01100000

เรามีหมายเลข -2,147,483,552 การเลื่อนสิ่งนี้ไปทางขวา 4 ตำแหน่งพร้อมการเปลี่ยนแปลงทางคณิตศาสตร์ (-2,147,483,552 >> 4) จะทำให้เรา:

11111000 00000000 00000000 00000110

หรือหมายเลข -134,217,722

ดังนั้นเราจะเห็นว่าเราได้รักษาเครื่องหมายของตัวเลขติดลบของเราไว้โดยใช้การเลื่อนขวาแบบเลขคณิตแทนการเลื่อนขวาแบบตรรกะ และอีกครั้งเราจะเห็นว่าเรากำลังทำการหารด้วยอำนาจของ 2


304
คำตอบควรทำให้ชัดเจนยิ่งขึ้นว่านี่เป็นคำตอบเฉพาะของ Java ไม่มีโอเปอเรเตอร์ >>> ใน C / C ++ หรือ C # และไม่ว่า >> จะเผยแพร่สัญลักษณ์นั้นมีการใช้งานใน C / C ++ (gotcha ที่มีศักยภาพหรือไม่สำคัญ)
Michael Burr

56
คำตอบนั้นไม่ถูกต้องทั้งหมดในบริบทของภาษา C ไม่มีการแบ่งที่มีความหมายใน "คณิตศาสตร์" และ "ตรรกะ" กะใน C ใน C กะทำงานตามที่คาดไว้ในค่าที่ไม่ได้ลงนามและค่าบวกลงนาม - พวกเขาเพียงแค่เปลี่ยนบิต สำหรับค่าลบนั้นการเปลี่ยนแปลงด้านขวาคือการนำไปปฏิบัติ (กล่าวคือไม่มีอะไรสามารถพูดได้เกี่ยวกับสิ่งที่ทำโดยทั่วไป) และห้ามกะซ้ายเพียง - มันสร้างพฤติกรรมที่ไม่ได้กำหนด
AnTe

10
ออเดรย์มีความแตกต่างอย่างแน่นอนระหว่างการคำนวณทางคณิตศาสตร์และการเลื่อนตรรกะทางขวา C เพียงปล่อยให้การใช้งานตัวเลือกที่กำหนดไว้ และการเลื่อนซ้ายบนค่าลบนั้นไม่ได้ห้ามอย่างแน่นอน เลื่อน 0xff000000 ไปทางซ้ายหนึ่งบิตแล้วคุณจะได้รับ 0xfe000000
Derek Park

16
A good optimizing compiler will substitute shifts for multiplications when possible. อะไร? Bitshifts นั้นเป็นคำสั่งที่มีขนาดเร็วขึ้นเมื่อมันมาถึงการทำงานระดับต่ำของซีพียูคอมไพเลอร์การปรับให้เหมาะสมที่ดีจะทำสิ่งที่ตรงกันข้ามอย่างแน่นอนนั่นคือการเปลี่ยนการคูณทั่วไปด้วยพลังของสองเป็นบิตกะ
Mahn

55
@Mahn คุณกำลังอ่านมันย้อนกลับจากความตั้งใจของฉัน การแทนที่ Y สำหรับ X หมายถึงการแทนที่ X ด้วย Y. Y เป็นตัวทดแทนสำหรับ X ดังนั้นการเลื่อนเป็นการแทนที่การคูณ
Derek Park

209

สมมติว่าเรามีไบต์เดียว:

0110110

การใช้ bitshift ซ้ายเดียวทำให้เราได้รับ:

1101100

ศูนย์ซ้ายสุดถูกเลื่อนออกจากไบต์และศูนย์ใหม่ถูกผนวกเข้ากับท้ายสุดของไบต์

บิตไม่ได้โรลโอเวอร์; พวกเขาถูกทิ้ง ซึ่งหมายความว่าหากคุณออกจากการเปลี่ยน 1101100 แล้วเปลี่ยนไปทางขวาคุณจะไม่ได้ผลลัพธ์เดียวกัน

ขยับซ้ายโดย N คือเทียบเท่ากับการคูณด้วย 2 N

การเลื่อนไปทางขวาโดย N คือ (ถ้าคุณใช้ส่วนเติมเต็มของคน ) เท่ากับการหารด้วย 2 Nและการปัดเศษเป็นศูนย์

Bitshifting สามารถใช้สำหรับการคูณและการหารอย่างรวดเร็วอย่างไม่น่าเชื่อหากคุณทำงานด้วยกำลัง 2 คุณสามารถใช้งานกราฟิกระดับต่ำเกือบทั้งหมดเป็นประจำ

ตัวอย่างเช่นย้อนกลับไปในสมัยก่อนเราใช้โหมด 13h (320x200 256 สี) สำหรับเกม ในโหมด 13 ชม. หน่วยความจำวิดีโอถูกจัดวางตามลำดับต่อพิกเซล นั่นหมายถึงการคำนวณตำแหน่งของพิกเซลคุณจะใช้คณิตศาสตร์ต่อไปนี้:

memoryOffset = (row * 320) + column

ตอนนี้ย้อนกลับไปในวันและอายุความเร็วเป็นสิ่งสำคัญดังนั้นเราจะใช้บิตเปลี่ยนการดำเนินการนี้

อย่างไรก็ตาม 320 ไม่ใช่พลังของสองดังนั้นเพื่อให้ได้สิ่งนี้เราต้องค้นหาว่าอะไรคือพลังของสองที่รวมเข้าด้วยกันทำให้ 320:

(row * 320) = (row * 256) + (row * 64)

ตอนนี้เราสามารถแปลงสิ่งนั้นเป็นกะซ้าย:

(row * 320) = (row << 8) + (row << 6)

สำหรับผลลัพธ์สุดท้ายของ:

memoryOffset = ((row << 8) + (row << 6)) + column

ตอนนี้เราได้ออฟเซ็ตเหมือนเดิมยกเว้นการดำเนินการคูณที่มีราคาแพงเราใช้สองบิตลิฟท์ ... ใน x86 มันจะเป็นแบบนี้ (บันทึกย่อมันคงอยู่ตลอดไปตั้งแต่ฉันประกอบเสร็จ (หมายเหตุบรรณาธิการ: แก้ไขแล้ว) ข้อผิดพลาดสองสามและเพิ่มตัวอย่าง 32 บิต)):

mov ax, 320; 2 cycles
mul word [row]; 22 CPU Cycles
mov di,ax; 2 cycles
add di, [column]; 2 cycles
; di = [row]*320 + [column]

; 16-bit addressing mode limitations:
; [di] is a valid addressing mode, but [ax] isn't, otherwise we could skip the last mov

ทั้งหมด: 28 รอบในสิ่งที่ซีพียูโบราณมีกำหนดเวลาเหล่านี้

Vrs

mov ax, [row]; 2 cycles
mov di, ax; 2
shl ax, 6;  2
shl di, 8;  2
add di, ax; 2    (320 = 256+64)
add di, [column]; 2
; di = [row]*(256+64) + [column]

12 รอบใน CPU ตัวเดียวกัน

ใช่เราจะทำงานอย่างหนักเพื่อกำจัด 16 รอบ CPU

ในโหมด 32 หรือ 64 บิตทั้งสองเวอร์ชันจะสั้นกว่าและเร็วกว่ามาก ซีพียูที่ใช้งานไม่ได้ตามคำสั่งที่ทันสมัยเช่น Intel Skylake (ดูhttp://agner.org/optimize/ ) มีฮาร์ดแวร์ที่รวดเร็วมากคูณ (เวลาแฝงต่ำและปริมาณงานสูง) ดังนั้นกำไรจึงน้อยลงมาก AMD Bulldozer-family นั้นช้ากว่านิดหน่อยโดยเฉพาะอย่างยิ่งสำหรับการคูณ 64 บิต บน CPU ของ Intel และ AMD Ryzen สองกะมีความหน่วงแฝงที่ต่ำกว่าเล็กน้อย แต่มีคำแนะนำมากกว่าการคูณ (ซึ่งอาจทำให้ปริมาณงานลดลง):

imul edi, [row], 320    ; 3 cycle latency from [row] being ready
add  edi, [column]      ; 1 cycle latency (from [column] and edi being ready).
; edi = [row]*(256+64) + [column],  in 4 cycles from [row] being ready.

เมื่อเทียบกับ

mov edi, [row]
shl edi, 6               ; row*64.   1 cycle latency
lea edi, [edi + edi*4]   ; row*(64 + 64*4).  1 cycle latency
add edi, [column]        ; 1 cycle latency from edi and [column] both being ready
; edi = [row]*(256+64) + [column],  in 3 cycles from [row] being ready.

คอมไพเลอร์จะทำเช่นนี้สำหรับคุณ: ดูวิธีการGCC, เสียงดังกราวและ Microsoft Visual C ++ ทั้งหมดใช้ SHIFT + return 320*row + col;ทุ่เมื่อการเพิ่มประสิทธิภาพ

สิ่งที่น่าสนใจที่สุดที่ควรทราบคือx86 มีคำสั่ง shift-and-add ( LEA)ที่สามารถทำการเลื่อนซ้ายและเล็ก ๆ ในเวลาเดียวกันโดยมีประสิทธิภาพเป็นaddคำสั่ง ARM มีประสิทธิภาพมากขึ้น: ตัวถูกดำเนินการหนึ่งคำสั่งสามารถเลื่อนไปทางซ้ายหรือขวาได้ฟรี ดังนั้นการปรับโดยการคอมไพล์ - เวลา - ค่าคงที่ที่รู้จักกันในชื่อ power-of-2 สามารถมีประสิทธิภาพมากกว่าการคูณ


ตกลงย้อนกลับไปในยุคปัจจุบัน ... สิ่งที่มีประโยชน์มากขึ้นในตอนนี้คือการใช้บิตยกระดับเพื่อเก็บค่า 8 บิตสองค่าในจำนวนเต็ม 16 บิต ตัวอย่างเช่นใน C #:

// Byte1: 11110000
// Byte2: 00001111

Int16 value = ((byte)(Byte1 >> 8) | Byte2));

// value = 000011111110000;

ใน C ++ คอมไพเลอร์ควรทำสิ่งนี้ให้คุณถ้าคุณใช้ a structกับสมาชิก 8-bit สองคน แต่ในทางปฏิบัติพวกเขาไม่ได้ทำเสมอ


8
การขยายสิ่งนี้ในโปรเซสเซอร์ของ Intel (และอื่น ๆ อีกมากมาย) มันเร็วกว่าที่จะทำสิ่งนี้: int c, d; c = d << 2; กว่านี้: c = 4 * d; บางครั้งแม้แต่ "c = d << 2 + d << 1" เร็วกว่า "c = 6 * d" !! ผมใช้เทคนิคเหล่านี้อย่างกว้างขวางสำหรับฟังก์ชั่นกราฟิกในยุค DOS ผมไม่คิดว่าพวกเขาจะมีประโยชน์เพื่อให้อีกต่อไป ...
โจ Pineda

5
@ James: ไม่มากทุกวันนี้มันค่อนข้างจะเป็นเฟิร์มแวร์ของการ์ดวิดีโอซึ่งมีรหัสเช่นนั้นที่จะถูกประหารโดย GPU มากกว่าซีพียู ดังนั้นในทางทฤษฎีคุณไม่จำเป็นต้องใช้โค้ดเช่นนี้ (หรือฟังก์ชั่นรูทเวทย์มนตร์ดำ - คาร์แม็ค) สำหรับฟังก์ชันกราฟิก :-)
Joe Pineda

3
@JoePineda @james ผู้เขียนคอมไพเลอร์ใช้มันอย่างแน่นอน ถ้าคุณเขียนc=4*dคุณจะได้รับกะ หากคุณเขียนk = (n<0)ที่สามารถทำได้ด้วยการเลื่อนเกินไป: k = (n>>31)&1เพื่อหลีกเลี่ยงสาขา บรรทัดล่างการปรับปรุงในความฉลาดของคอมไพเลอร์หมายความว่าตอนนี้มันไม่จำเป็นที่จะใช้เทคนิคเหล่านี้ในรหัส C และพวกเขาประนีประนอมการอ่านและพกพา ยังดีมากหากพวกเขารู้ว่าคุณกำลังเขียนเช่นรหัสเวกเตอร์ SSE; หรือสถานการณ์ใด ๆ ที่คุณต้องการอย่างรวดเร็วและมีเคล็ดลับที่คอมไพเลอร์ไม่ได้ใช้ (เช่นรหัส GPU)
greggo

2
อีกตัวอย่างที่ดีมากสิ่งที่พบคือการif(x >= 1 && x <= 9)ที่สามารถทำได้เช่นif( (unsigned)(x-1) <=(unsigned)(9-1)) การเปลี่ยนการทดสอบสองเงื่อนไขอย่างใดอย่างหนึ่งอาจจะเป็นข้อได้เปรียบที่ความเร็วขนาดใหญ่ โดยเฉพาะอย่างยิ่งเมื่ออนุญาตให้เรียกใช้งานแบบ predicated แทนสาขา ฉันใช้สิ่งนี้เป็นเวลาหลายปี (ซึ่งเป็นธรรม) จนกระทั่งฉันสังเกตเห็นเกี่ยวกับ abt 10 ปีที่แล้วว่าคอมไพเลอร์เริ่มทำการแปลงในเครื่องมือเพิ่มประสิทธิภาพแล้วฉันก็หยุด ยังดีที่จะทราบเนื่องจากมีสถานการณ์ที่คล้ายกันที่คอมไพเลอร์ไม่สามารถทำการแปลงสำหรับคุณ หรือถ้าคุณกำลังทำงานกับคอมไพเลอร์
greggo

3
มีเหตุผลว่า "byte" ของคุณเพียง 7 บิตหรือไม่?
Mason Watmough

104

การใช้งานระดับบิตรวมถึงการเลื่อนบิตเป็นพื้นฐานของฮาร์ดแวร์ระดับต่ำหรือการเขียนโปรแกรมแบบฝัง หากคุณอ่านข้อกำหนดสำหรับอุปกรณ์หรือแม้กระทั่งบางรูปแบบไฟล์ไบนารีคุณจะเห็นไบต์คำและ dwords แยกออกเป็นบิตฟิลด์ที่ไม่มีการจัดแนวไบต์ซึ่งมีค่าที่น่าสนใจมากมาย การเข้าถึงบิตฟิลด์เหล่านี้สำหรับการอ่าน / เขียนเป็นการใช้งานทั่วไป

ตัวอย่างจริงที่เรียบง่ายในการเขียนโปรแกรมกราฟิกคือพิกเซล 16 บิตจะแสดงดังนี้

  bit | 15| 14| 13| 12| 11| 10| 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1  | 0 |
      |       Blue        |         Green         |       Red          |

ในการรับค่าสีเขียวคุณต้องทำสิ่งนี้:

 #define GREEN_MASK  0x7E0
 #define GREEN_OFFSET  5

 // Read green
 uint16_t green = (pixel & GREEN_MASK) >> GREEN_OFFSET;

คำอธิบาย

ในการรับค่าของสีเขียวเท่านั้นซึ่งเริ่มที่ offset 5 และสิ้นสุดที่ 10 (เช่น 6 บิตยาว) คุณต้องใช้มาสก์ (บิต) ซึ่งเมื่อใช้กับพิกเซล 16 บิตทั้งหมดจะให้ผล เฉพาะบิตที่เราสนใจ

#define GREEN_MASK  0x7E0

มาสก์ที่เหมาะสมคือ 0x7E0 ซึ่งเป็นเลขฐานสองคือ 0000011111100000 (ซึ่งเป็น 2559 ในรูปทศนิยม)

uint16_t green = (pixel & GREEN_MASK) ...;

ในการใช้หน้ากากคุณใช้ตัวดำเนินการและ (&)

uint16_t green = (pixel & GREEN_MASK) >> GREEN_OFFSET;

หลังจากใช้หน้ากากคุณจะได้ตัวเลข 16 บิตซึ่งเป็นตัวเลข 11 บิตจริง ๆ เนื่องจาก MSB อยู่ในบิตที่ 11 จริง ๆ แล้วสีเขียวมีความยาวเพียง 6 บิตดังนั้นเราจำเป็นต้องลดขนาดลงโดยใช้กะขวา (11 - 6 = 5) ดังนั้นการใช้ 5 เป็นออฟเซ็ต ( #define GREEN_OFFSET 5)

สิ่งที่พบได้ทั่วไปก็คือการใช้บิตกะสำหรับการคูณอย่างรวดเร็วและการหารด้วยพลังของ 2:

 i <<= x;  // i *= 2^x;
 i >>= y;  // i /= 2^y;

1
0x7e0 เหมือนกับ 11111100000 ซึ่งเป็น 2559 ในรูปของทศนิยม
Saheed

50

Bit Masking & Shifting

การเปลี่ยนบิตมักใช้ในการเขียนโปรแกรมกราฟิกระดับต่ำ ตัวอย่างเช่นค่าสีพิกเซลที่กำหนดให้เข้ารหัสเป็นคำแบบ 32 บิต

 Pixel-Color Value in Hex:    B9B9B900
 Pixel-Color Value in Binary: 10111001  10111001  10111001  00000000

เพื่อความเข้าใจที่ดีขึ้นค่าเลขฐานสองเดียวกันที่ติดป้ายว่าส่วนใดเป็นตัวแทนส่วนสีใด

                                 Red     Green     Blue       Alpha
 Pixel-Color Value in Binary: 10111001  10111001  10111001  00000000

ตัวอย่างเช่นเราต้องการได้ค่าสีเขียวของสีของพิกเซลนี้ เราสามารถได้รับค่าว่าด้วยการกำบังและขยับ

หน้ากากของเรา:

                  Red      Green      Blue      Alpha
 color :        10111001  10111001  10111001  00000000
 green_mask  :  00000000  11111111  00000000  00000000

 masked_color = color & green_mask

 masked_color:  00000000  10111001  00000000  00000000

ตัว&ดำเนินการเชิงตรรกะทำให้มั่นใจได้ว่าเฉพาะค่าที่เก็บหน้ากากไว้เท่านั้น สิ่งสุดท้ายที่ตอนนี้เราต้องทำคือการได้รับค่าจำนวนเต็มถูกต้องโดยการขยับบิตทุกคนไปทางขวาโดย 16 ตำแหน่ง(ตรรกะกะขวา)

 green_value = masked_color >>> 16

อีกครั้ง, เรามีจำนวนเต็มแทนจำนวนสีเขียวในสีของพิกเซล:

 Pixels-Green Value in Hex:     000000B9
 Pixels-Green Value in Binary:  00000000 00000000 00000000 10111001
 Pixels-Green Value in Decimal: 185

นี้มักจะใช้สำหรับการเข้ารหัสหรือถอดรหัสรูปแบบภาพเหมือนjpg, pngฯลฯ


การส่งต้นฉบับของคุณออกมาง่ายกว่าหรือไม่พูด 32 บิต cl_uint เหมือนกับ cl_uchar4 และเข้าถึง byte ที่คุณต้องการโดยตรงเป็น * .s2
David H Parry

27

หนึ่ง gotcha คือการใช้งานต่อไปนี้ขึ้นอยู่กับ (ตามมาตรฐาน ANSI):

char x = -1;
x >> 1;

x สามารถเป็น 127 (01111111) หรือยัง -1 (11111111)

ในทางปฏิบัติมันมักจะเป็นหลัง


4
ถ้าฉันจำได้อย่างถูกต้องมาตรฐาน ANSI C บอกว่านี่ขึ้นอยู่กับการนำไปใช้จริงดังนั้นคุณต้องตรวจสอบเอกสารประกอบของคอมไพเลอร์เพื่อดูว่ามีการนำไปใช้อย่างไรหากคุณต้องการเปลี่ยนจำนวนเต็มลงนามในรหัสของคุณ
Joe Pineda

ใช่ฉันแค่อยากจะเน้นมาตรฐาน ANSI ของตัวเองบอกว่ามันไม่ใช่กรณีที่ผู้ขายไม่ได้ทำตามมาตรฐานหรือมาตรฐานไม่ได้พูดอะไรเกี่ยวกับกรณีส่วนนี้
Joe Pineda

22

ฉันกำลังเขียนเคล็ดลับและลูกเล่นเท่านั้น มันอาจมีประโยชน์ในการทดสอบและการสอบ

  1. n = n*2: n = n<<1
  2. n = n/2: n = n>>1
  3. กำลังตรวจสอบว่า n คือพลังของ 2 (1,2,4,8, ... ): ตรวจสอบ !(n & (n-1))
  4. การเดินทางx THบิตn:n |= (1 << x)
  5. ตรวจสอบว่า x เป็นเลขคู่หรือคี่: x&1 == 0(คู่)
  6. สลับn THบิตของ x:x ^ (1<<n)

ต้องมีอีกไม่กี่อย่างที่คุณรู้ตอนนี้?
ryyker

@ryyker ฉันได้เพิ่มอีกไม่กี่ ฉันจะพยายามอัปเดตต่อไปเรื่อย ๆ :)
Ravi Prakash

ทำดัชนี x และ n 0 หรือไม่
reggaeguitar

โฆษณา 5: เกิดอะไรขึ้นถ้ามันเป็นจำนวนลบ?
Peter Mortensen

ดังนั้นเราสามารถสรุป 2 ในไบนารี่ได้เป็น 10 ในทศนิยม? และการเลื่อนบิตเป็นเหมือนการเพิ่มหรือลดจำนวนอีกหนึ่งหลังตัวเลขอื่นในทศนิยม?
Willy satrio nugroho

8

โปรดทราบว่าในการนำ Java ไปใช้จำนวนบิตที่จะเลื่อนจะถูกแก้ไขตามขนาดของแหล่งที่มา

ตัวอย่างเช่น:

(long) 4 >> 65

เท่ากับ 2 คุณอาจคาดหวังว่าการเลื่อนบิตไปทางขวา 65 เท่าจะทำให้ทุกอย่างเป็นศูนย์ แต่จริง ๆ แล้วเทียบเท่ากับ:

(long) 4 >> (65 % 64)

สิ่งนี้เป็นจริงสำหรับ <<, >> และ >>> ฉันไม่ได้ลองในภาษาอื่น


ฮืมน่าสนใจ! ใน C นี้เป็นเทคนิคที่ไม่ได้กำหนดพฤติกรรม gcc 5.4.0ให้คำเตือน แต่ให้2สำหรับ 5 >> 65; เช่นกัน
pizzapants184

2

การใช้งาน / การจัดการบิตที่มีประโยชน์ใน Python

ฉันใช้คำตอบของ Ravi Prakashใน Python

# Basic bit operations
# Integer to binary
print(bin(10))

# Binary to integer
print(int('1010', 2))

# Multiplying x with 2 .... x**2 == x << 1
print(200 << 1)

# Dividing x with 2 .... x/2 == x >> 1
print(200 >> 1)

# Modulo x with 2 .... x % 2 == x & 1
if 20 & 1 == 0:
    print("20 is a even number")

# Check if n is power of 2: check !(n & (n-1))
print(not(33 & (33-1)))

# Getting xth bit of n: (n >> x) & 1
print((10 >> 2) & 1) # Bin of 10 == 1010 and second bit is 0

# Toggle nth bit of x : x^(1 << n)
# take bin(10) == 1010 and toggling second bit in bin(10) we get 1110 === bin(14)
print(10^(1 << 2))

-3

โปรดทราบว่ามี PHP เวอร์ชัน 32 บิตเท่านั้นที่พร้อมใช้งานบนแพลตฟอร์ม Windows

จากนั้นหากคุณเลื่อนตัวอย่าง << หรือ >> มากกว่า 31 บิตผลลัพธ์จะไม่สามารถคาดการณ์ได้ โดยปกติแล้วหมายเลขเดิมแทนที่จะเป็นศูนย์จะถูกส่งกลับและอาจเป็นข้อผิดพลาดที่ยุ่งยากมาก

แน่นอนว่าถ้าคุณใช้ PHP (Unix) เวอร์ชั่น 64 บิตคุณควรหลีกเลี่ยงการขยับมากกว่า 63 บิต อย่างไรก็ตามตัวอย่างเช่น MySQL ใช้ BIGINT 64 บิตดังนั้นจึงไม่มีปัญหาความเข้ากันได้

อัปเดต: จาก PHP 7 Windows โครงสร้าง PHP สามารถใช้จำนวนเต็ม 64 บิตได้ในที่สุด: ขนาดของจำนวนเต็มขึ้นอยู่กับแพลตฟอร์มแม้ว่าค่าสูงสุดประมาณสองพันล้านเป็นค่าปกติ (ที่ลงนาม 32 บิต) โดยทั่วไปแล้วแพลตฟอร์ม 64 บิตจะมีค่าสูงสุดประมาณ 9E18 ยกเว้นใน Windows ก่อน PHP 7 ซึ่งเป็น 32 บิตเสมอ

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