คุณอาจใช้เวลาทั้งชีวิตในการสร้างแอป Rails และไม่จำเป็นต้องใช้โอเปอเรเตอร์ระดับบิตเลย หากคุณเพิ่งเริ่มเขียนโปรแกรม คุณอาจไม่รู้ด้วยซ้ำว่า "ระดับบิต" หมายถึงอะไร
แต่ทันทีที่คุณสนใจระบบที่ประสิทธิภาพเป็นสิ่งสำคัญและทรัพยากรมีจำกัด การดำเนินการระดับบิตก็มีความสำคัญ มีการใช้อย่างกว้างขวางกับสิ่งต่างๆ เช่น โปรโตคอลเครือข่าย การเข้ารหัส การอนุญาตไฟล์ Unix และระบบฝังตัว
นอกจากนี้ เพื่อให้เข้าใจจริง ๆ ว่าคอมพิวเตอร์ทำการบวกเลขสองตัวอย่างไร คุณต้องเข้าใจการดำเนินการระดับบิต
เนื่องจาก Ruby และภาษาอื่นๆ มากมายมีการสนับสนุนแบบเนทีฟ พวกเขาจึงเป็นเครื่องมือที่ยอดเยี่ยมในการเพิ่มลงในกล่องเครื่องมือของคุณ แม้ว่าจะเป็นเพียงการทำความเข้าใจว่าจะเกิดอะไรขึ้นหากคุณพบพวกเขาในอนาคต
ในบทความนี้ เราจะพูดถึงว่าการดำเนินการระดับบิตคืออะไร ตัวอย่างบางส่วนของตำแหน่งที่สามารถนำไปใช้ และวิธีที่เราจะใช้ประโยชน์ได้ดีที่สุดใน Ruby เริ่มกันเลย
การดำเนินการระดับบิตคืออะไร
ที่ระดับต่ำสุดในคอมพิวเตอร์ทุกเครื่อง เรามีเพียง 1 และ 0 หรือที่เรียกว่าบิตเท่านั้น การดำเนินการอื่นใดที่เราใช้ในภาษา Ruby หรือภาษาโปรแกรมอื่น ๆ จะถูกจัดเก็บเป็น 1 และ 0 แต่ซอฟต์แวร์ในคอมพิวเตอร์ของเราจะแปลงไปมาระหว่างสิ่งที่เราเห็นกับสิ่งที่จัดเก็บจริงได้อย่างมีประสิทธิภาพ ตัวอย่างเช่น เราเก็บสตริง "hello" เป็นลูกโซ่ของ 1 และ 0
การดำเนินการระดับบิตทำให้เราสามารถเข้าถึงบิตเหล่านั้นได้โดยตรง ซึ่งมักจะใช้แทนตัวเลข และทำ "บางอย่าง" กับบิตเหล่านั้น การดำเนินการบางอย่างจะช่วยเราในการใช้คุณลักษณะต่างๆ ในซอฟต์แวร์ของเราในลักษณะที่หรูหราและมีประสิทธิภาพยิ่งขึ้น
มีสองประเด็นสำคัญที่ควรเน้นเกี่ยวกับการดำเนินการระดับบิตในตอนนี้:มีประสิทธิภาพอย่างมากในการจัดเก็บข้อมูล เนื่องจากเราใช้บิตโดยตรง และดำเนินการได้รวดเร็วมาก
ลักษณะการทำงานพื้นฐานคือการมีชุดของบิตและนำตัวดำเนินการไปใช้กับมัน คุณยังสามารถใช้ตัวดำเนินการกับบิตสองชุดได้ มาดูการดำเนินงานกันบ้าง:
ไม่ใช่การทำงานระดับบิต
นี่เป็นการดำเนินการแบบเอกพจน์ ซึ่งหมายความว่าจะใช้กับชุดบิตเท่านั้น และง่ายพอๆ กับการแทนที่ 1s ด้วย 0 และในทางกลับกัน มันถูกแสดงด้วยสัญลักษณ์:~
.
~101000 = 010111
และการทำงานระดับบิต
AND เป็นการดำเนินการที่ใช้กับบิตสองชุด สัญลักษณ์ของมันคือ &
และเป็นไปตามตรรกะนี้:
1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0
ดังนั้นเมื่อเรามีชุดบิตที่มีความยาวเท่ากันสองชุด ผลลัพธ์ก็แค่ใช้ตรรกะนี้กับแต่ละคู่:
0110 AND
0111
-----
0110
ใน Ruby:
25.to_s(2) # 11001
30.to_s(2) # 11110
(25 & 30).to_s(2) # 11000
หรือการทำงานระดับบิต
คล้ายกับการดำเนินการ AND การดำเนินการอื่นที่ใช้กับบิตสองชุดด้วยคือ OR ระดับบิต สัญลักษณ์ของมันคือ |
และเป็นไปตามตรรกะนี้:
1 | 1 = 1
1 | 0 = 1
0 | 1 = 1
0 | 0 = 0
ถ้าเรามี 1 ด้านใดด้านหนึ่ง ผลลัพธ์จะเป็น 1 ไม่เช่นนั้นจะเป็น 0 ง่ายมาก! มาดูการดำเนินการ OR กับบิตเพิ่มเติม:
0110 OR
0111
-----
0111
และในทับทิม:
25.to_s(2) # 11001
30.to_s(2) # 11110
(25 | 30).to_s(2) # 11111
ตัวอย่างที่ใช้งานได้จริง 1:การอนุญาต
ณ จุดนี้ คุณอาจสงสัยว่าเหตุใดการดำเนินการเหล่านี้จึงมีประโยชน์ ท้ายที่สุดพวกเขาดูค่อนข้างต่ำ คุณอาจไม่มีแผนที่จะทำงานโดยตรงกับโปรโตคอลเครือข่าย กราฟิกหรือการเข้ารหัส
แต่คุณอาจเคยทำงานด้วยการอนุญาตมาก่อน สิทธิ์เป็นพื้นที่หนึ่งที่การดำเนินการระดับบิตมีประโยชน์จริงๆ ลองนึกภาพคุณมีระบบการอนุญาตที่ผู้ใช้สามารถดำเนินการต่างๆ ในเอกสารได้:
- ดู
- แก้ไข
- ลบ
- เชิญผู้ใช้รายอื่น
และตอนนี้เราต้องการจำลองการกระทำเหล่านี้ในบทบาทต่างๆ:
- ผู้ช่วย: สามารถดูและแก้ไขเอกสารได้
- ผู้สังเกตการณ์: สามารถดูเอกสารได้เท่านั้น
- ผู้เขียน: สามารถดำเนินการได้ทั้งหมด
เราจะสร้างแบบจำลองนี้ได้อย่างไร เราจะรู้ได้อย่างไรว่าผู้ใช้มีบทบาทเหล่านี้อย่างใดอย่างหนึ่งเมื่อใดก็ตาม คำตอบหนึ่งคือการใช้การดำเนินการระดับบิต
เราจะเก็บบิตเพียงชุดเดียวสำหรับผู้ใช้แต่ละรายที่แสดงถึงการอนุญาตที่พวกเขามี:
(Starting from the right side)
1st bit: View
2nd bit: Edit
3rd bit: Delete
4th bit: Invite other users
ตัวอย่างเช่น:
0001 = Can only view the document.
0011 = Can view and edit the document.
1001 = Can view, can't edit, can't delete and can invite others.
เมื่อเราตั้งค่าเหล่านี้สำหรับผู้ใช้บางคนแล้ว เราสามารถเปรียบเทียบอย่างรวดเร็วกับการอนุญาตที่เราต้องการได้ ลองนึกภาพว่าเรากำลังตรวจสอบว่าผู้ใช้สามารถแก้ไขเอกสารได้หรือไม่ เราสามารถใช้คำสั่ง AND ระดับบิตได้:
# This is called a bit mask. It contains only the value we want to check, in this case the second bit for editing.
EDIT_PERMISSION_MASK = 0b0010
# We can define a quick method to check this:
def can_edit_document?(user_permisions)
(EDIT_PERMISSION_MASK & user_permisions) != 0
end
ซึ่งหมายความว่าหากการดำเนินการระดับบิต AND ให้สิ่งที่แตกต่างจาก 0 แก่เรา เราจะมีการตั้งค่าบิตนั้น:
0010 AND
1101
----
0000 == 0 so we don't have the permission
0010 AND
1110
----
0010 != 0 so we have the permission
เราสามารถใช้ตรรกะเดียวกันกับการอนุญาตอื่น ๆ โดยแก้ไขตำแหน่งของบิตที่เราต้องการตรวจสอบ ดังนั้นเราจะลงเอยด้วยค่าคงที่เหล่านี้และวิธีการที่เกี่ยวข้อง:
VIEW_PERMISSION_MASK = 0b0001
EDIT_PERMISSION_MASK = 0b0010
DELETE_PERMISSION_MASK = 0b0100
INVITE_PERMISSION_MASK = 0b1000
นอกจากนี้ เราสามารถกำหนดสิทธิ์แบบไดนามิกและจัดเก็บการอนุญาตใหม่ในอนาคตด้วยการตรวจสอบอย่างรวดเร็ว
ตัวอย่างเช่น เราได้กล่าวไว้ก่อนหน้านี้ว่าผู้ช่วยสามารถดูและแก้ไขเอกสารได้เท่านั้น ดังนั้นการอนุญาตสำหรับผู้ใช้นั้นจะเป็น:0011
. เราสามารถเก็บค่านั้นไว้ในฐานข้อมูลของเรา จากนั้นเราสามารถตรวจสอบได้อย่างง่ายดายว่าผู้ช่วยสามารถดำเนินการตามวิธีการที่กำหนดไว้ก่อนหน้านี้หรือไม่
ASSISTANT_MASK = VIEW_PERMISSION_MASK | EDIT_PERMISSION_MASK
# This will be: 0011
# Optionally, we could use this method to check if this user is an assistant. This method could be defined inside the User class.
def is_assistant?(user)
(user.permissions == ASSISTANT_MASK)
end
หากทั้งหมดนี้ฟังดูคุ้นๆ นั่นก็เพราะเป็นวิธีเดียวกับที่ใช้กันทั่วไปสำหรับการอนุญาตไฟล์ในระบบที่ใช้ Unix
ตัวอย่างเชิงปฏิบัติ 2:ตำแหน่งในทีม
มาใช้การดำเนินการระดับบิตกันมากขึ้นหน่อย 😉
เราสามารถนำไปใช้ในกรณีอื่นที่ค่อนข้างธรรมดา:ตำแหน่งต่างๆ ในทีมกีฬาหรือตำแหน่งงานในบริษัท ไปกับทีมบาสเก็ตบอลเพื่อลดความซับซ้อน
ในบาสเก็ตบอลมี 5 ตำแหน่งในการเล่นเกม:- ตัวป้องกันจุด- ตัวป้องกันการยิง- ไปข้างหน้าเล็ก- กำลังไปข้างหน้า- ศูนย์
และเราสามารถกำหนดชุดบิตให้กับแต่ละตำแหน่งได้:
00001 Point guard
00010 Shooting guard
00100 Small forward
01000 Power forward
10000 Center
เช่นเดียวกันใน Ruby จะเป็น:
POINT_GUARD_POSITION = 0b00001
SHOOTING_GUARD_POSITION = 0b00010
SMALL_FORWARD_POSITION = 0b00100
POWER_FORWARD_POSITION = 0b01000
CENTER_POSITION = 0b10000
POINT_GUARD_POSITION | SHOOTING_GUARD_POSITION | SMALL_FORWARD_POSITION | POWER_FORWARD_POSITION | CENTER_POSITION # = 31
ตอนนี้เราทำสิ่งที่น่าสนใจได้แล้ว เช่น ตรวจสอบว่าทั้งทีมอยู่ด้วยหรือไม่:
# p1...p5 are the positions of each player
is_full_team_present = (p1 | p2 | p3 | p4 | p5 == 31)
ทำไม ด้วยการดำเนินการระดับบิต OR หากเรามีตำแหน่งทั้งหมด เราจะลงเอยด้วย:11111
# OR bitwise operation
00001 |
00010 |
00100 |
01000 |
10000
-----
11111
และ 11111 คือ 31 เพราะ 2^0 + 2^1 + 2^2 + 2^3 + 2^4 =31
สิ่งนี้ไม่เกี่ยวข้องอย่างเคร่งครัดกับการดำเนินการระดับบิต แต่ด้วยการสร้างแบบจำลองข้อมูลนี้ เราสามารถตรวจสอบว่าสามารถแลกเปลี่ยนผู้เล่นสองคนได้หรือไม่ สิ่งนี้จะง่ายมาก:
def can_be_exchanged?(player1, player2)
player1.position == player2.position
end
XOR
การดำเนินการอื่นที่เราสามารถทำได้ด้วยบิตสองชุดคือ XOR ซึ่งมีสัญลักษณ์คือ ^
.
XOR หมายถึง OR พิเศษ และเป็นไปตามตรรกะนี้:
1 | 1 = 0
1 | 0 = 1
0 | 1 = 1
0 | 0 = 0
ดังนั้นผลลัพธ์จะเป็น 1 ต่อเมื่อหนึ่งในสองบิตเป็น 1; หากทั้งคู่เท่ากันจะเป็น 0
การดำเนินการนี้ใช้ในอัลกอริธึมบางตัวเพื่อเปรียบเทียบตัวเลขกับตัวมันเอง เนื่องจาก x ^ x =0
กะ
นี่คือกลุ่มการดำเนินการที่น่าสนใจที่เรา "ย้าย" บิตไปด้านใดด้านหนึ่งหรืออีกด้านหนึ่งภายในชุด ให้ฉันอธิบาย
ด้วยการเลื่อนบิต เราจะย้ายหรือ "เปลี่ยน" บิตในทิศทางเดียว ซ้ายหรือขวา:
00010111 left-shift
<-------
00101110
10010111 right-shift
------->
11001011
เราสามารถย้ายหรือเปลี่ยนบิตได้ n ครั้ง นี่คือการเลื่อนด้านซ้ายที่ใช้กับหมายเลข 5 สองครั้งใน Ruby:
5.to_s(2) # 101
(5 << 2).to_s(2) # 10100
อย่างที่คุณเห็น การเลื่อนด้านซ้ายจะแสดงด้วย <<กะขวาใช้>>:
5.to_s(2) # 101
(5 >> 2).to_s(2) # 1
ในกรณีนี้ เราได้แค่อันเดียว เพราะบิต "0" และ "1" จาก 1*01 * ถูกทิ้ง
หารด้วย 2 ด้วยการเลื่อนขวา
ข้อเท็จจริงที่น่าสนใจประการหนึ่งเกี่ยวกับการเลื่อนบิตคือคุณสามารถคำนวณการดำเนินการทางคณิตศาสตร์บางอย่างกับมันได้ เมื่อก่อนวิธีนี้เร็วกว่า แต่ปัจจุบันนี้ใช้โดยโปรแกรมเมอร์โดยเฉพาะซึ่งทำงานกับข้อจำกัดด้านทรัพยากรมากขึ้น เช่น ในการพัฒนาเกม
หากเราใช้กะทางขวากับตัวเลข เราจะได้ผลลัพธ์จากการหารด้วย 2:
10.to_s(2) # 1010
(10 >> 1).to_s(2) # 101
10 >> 1 # 5
คูณด้วย 2 ด้วยการเลื่อนซ้าย
ในทำนองเดียวกัน เราสามารถคูณด้วย 2 ด้วยการเลื่อนซ้าย:
10.to_s(2) # 1010
(10 << 1).to_s(2) # 10100
10 << 1 # 20
ตรวจสอบอย่างรวดเร็วว่าตัวเลขเป็นเลขคี่หรือคู่
มีตัวอย่างง่ายๆ ของการดำเนินการระดับบิตที่รวดเร็วและง่ายต่อการติดตาม
ลองนึกภาพเราดำเนินการ AND ด้วย 1 เพียง 1 นั่นก็คือจำนวน 0 เท่าใดก็ได้ ขึ้นอยู่กับคอมพิวเตอร์ที่เราอยู่ และ 1. ลองทำด้วย 2:
2 = 00000010 &
00000001
-------------
00000000
ตอนนี้มี 4:
4 = 00000100 &
00000001
-------------
00000000
แล้ว 5 ล่ะ?
5 = 00000101 &
00000001
-------------
00000001
ตอนนี้เราได้ 1 แล้วคุณเดาได้ไหมว่านี่หมายถึงอะไร
การทำเช่นนี้ AND กับ 1 หากตัวเลขเป็นเลขคู่ เราจะได้ 0 ถ้ามันแปลก เราจะได้ 1 เราสามารถใช้ข้อเท็จจริงนี้เพื่อสร้างวิธีการสองสามวิธีใน Ruby ได้อย่างง่ายดาย:
def is_odd?(number)
number & 1
end
def is_even?(number)
is_odd?(number) == 0
end
# Or:
def is_even?(number)
(number & 1) == 0
end
หากคุณต้องการไปต่อหรือสัมผัสช่วงเวลาที่น่าประทับใจด้วยบิต ให้ลองดูคอลเล็กชันการแฮ็กระดับบิตเพื่อดูกลเม็ดเพิ่มเติม
บทสรุป
การดำเนินงานระดับ Bitwise นั้นยากต่อการติดตามหากคุณไม่เคยพบเจอมาก่อน แต่เมื่อคุณคุ้นเคยกับมันแล้ว คุณจะสามารถเตรียมรับมือกับโครงการที่มีอยู่หรือในอนาคตได้ดียิ่งขึ้น นอกจากนี้ คุณจะมีเครื่องมือใหม่ในการวางแผนวิธีแก้ปัญหาในโค้ดของคุณ