Why Nostr? What is Njump?
2024-08-05 05:58:11

ΔVaz🌱 on Nostr: แรก ๆ ก็งง หลัง ๆ ...

แรก ๆ ก็งง หลัง ๆ ก็ยังงเหมือนเดิม

#siamstr

Bitwise Operation คือ กระบวนการที่การดำเนินการใด ๆ โดยตรงกับบิตของตัวเลข หรือชุดข้อมูลของเราใน memory ซึ่งการทำงานโดยตรงกับบิตของตัวเลข ทำให้มีประสิทธิภาพสูงและใช้บ่อยในการเขียนโปรแกรมระดับ low-level โดยหลัก ๆ ที่ใช้กันจะมี 6 แบบ เป็นเครื่องมือที่ทรงพลังสำหรับนักพัฒนาที่ต้องการประสิทธิภาพสูงและการควบคุมระดับบิต โดยเฉพาะในการพัฒนาระบบระดับ low-level และการทำงานกับฮาร์ดแวร์โดยตรง

TLDR

ภาพรวม Bitwise Operations:

AND (&):

ใช้เปรียบเทียบบิต, ให้ 1 เมื่อทั้งสองบิตเป็น 1 ประโยชน์: ตรวจสอบบิต, สร้าง mask

OR (|):

ให้ 1 เมื่อมีบิตใดบิตหนึ่งเป็น 1 ประโยชน์: รวมแฟล็ก, ตั้งค่าบิต

XOR (^):

ให้ 1 เมื่อบิตต่างกัน ประโยชน์: สลับค่า, เข้ารหัสอย่างง่าย

NOT (~):

กลับค่าทุกบิต ประโยชน์: สร้าง mask สำหรับลบบิต

Left Shift (<<):

เลื่อนบิตไปซ้าย, เติม 0 ทางขวา ประโยชน์: คูณด้วยกำลังของ 2, สร้าง mask

Right Shift (>>):

เลื่อนบิตไปขวา ประโยชน์: หารด้วยกำลังของ 2, แยกส่วนข้อมูล

1.) BitAnd (&):

BitAnd มักใช้สัญลักษณ์ & เป็นการดำเนินการทางตรรกะที่เปรียบเทียบบิตในตำแหน่งเดียวกันของสองตัวเลข โดยมีกฎดังนี้:

  • 1 AND 1 = 1
  • 1 AND 0 = 0
  • 0 AND 1 = 0
  • 0 AND 0 = 0

หรือกล่าวอีกนัยหนึ่ง ผลลัพธ์จะเป็น 1 ก็ต่อเมื่อทั้งสองบิตเป็น 1 เท่านั้น

ตัวอย่าง: สมมติว่าเรามีเลขสองตัว A = 5 และ B = 3 ในระบบเลขฐานสอง: A = 5 = 0101 B = 3 = 0011

เมื่อทำ BitAnd:

  0101 (A = 5)
& 0011 (B = 3)
  ----
  0001 (Result = 1)

จะเห็นได้ว่า:

  • บิตที่ 1 (จากขวา): 1 & 1 = 1
  • บิตที่ 2: 0 & 1 = 0
  • บิตที่ 3: 1 & 0 = 0
  • บิตที่ 4: 0 & 0 = 0

ดังนั้น ผลลัพธ์คือ 0001 ในเลขฐานสอง ซึ่งเท่ากับ 1 ในเลขฐานสิบ

การใช้งาน BitAnd:

  1. การตรวจสอบเลขคู่/คี่: ถ้า (n & 1) == 0 แสดงว่า n เป็นเลขคู่ ถ้า (n & 1) == 1 แสดงว่า n เป็นเลขคี่

  2. การตรวจสอบสถานะของบิตเฉพาะ: ถ้าต้องการตรวจสอบบิตที่ 3 ของ x: if (x & (1 << 2)) != 0 // บิตที่ 3 เป็น 1

  3. การลบบิตสุดท้ายที่เป็น 1: n = n & (n - 1)

BitAnd มีประโยชน์มากในการจัดการกับข้อมูลระดับบิต การตั้งค่าแฟล็ก และการทำงานกับฮาร์ดแวร์โดยตรง

2.) BitOr (|):

BitOr มักใช้สัญลักษณ์ | เป็นการดำเนินการทางตรรกะที่เปรียบเทียบบิตในตำแหน่งเดียวกันของสองตัวเลข โดยมีกฎดังนี้:

  • 1 OR 1 = 1
  • 1 OR 0 = 1
  • 0 OR 1 = 1
  • 0 OR 0 = 0

กล่าวคือ ผลลัพธ์จะเป็น 1 เมื่อมีบิตใดบิตหนึ่งเป็น 1 หรือทั้งสองบิตเป็น 1

ตัวอย่าง: สมมติว่าเรามีเลขสองตัว A = 5 และ B = 3 ในระบบเลขฐานสอง: A = 5 = 0101 B = 3 = 0011

เมื่อทำ BitOr:

  0101 (A = 5)
| 0011 (B = 3)
  ----
  0111 (Result = 7)

จะเห็นได้ว่า:

  • บิตที่ 1 (จากขวา): 1 | 1 = 1
  • บิตที่ 2: 0 | 1 = 1
  • บิตที่ 3: 1 | 0 = 1
  • บิตที่ 4: 0 | 0 = 0

ดังนั้น ผลลัพธ์คือ 0111 ในเลขฐานสอง ซึ่งเท่ากับ 7 ในเลขฐานสิบ

การใช้งาน BitOr:

  1. การรวมแฟล็ก: เช่น ในการตั้งค่าสิทธิ์ไฟล์ในระบบ Unix permissions = READ | WRITE | EXECUTE

  2. การตั้งค่าบิตเฉพาะ: ถ้าต้องการตั้งค่าบิตที่ 3 ของ x เป็น 1: x = x | (1 << 2)

  3. การรวมสี RGB: ในระบบสี RGB ที่ใช้ 8 บิตต่อสี color = (red << 16) | (green << 8) | blue

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

BitXor (^):

BitXor หรือ Exclusive OR (XOR) มักใช้สัญลักษณ์ ^ เป็นการดำเนินการทางตรรกะที่เปรียบเทียบบิตในตำแหน่งเดียวกันของสองตัวเลข โดยมีกฎดังนี้:

  • 1 XOR 1 = 0
  • 1 XOR 0 = 1
  • 0 XOR 1 = 1
  • 0 XOR 0 = 0

กล่าวคือ ผลลัพธ์จะเป็น 1 เมื่อบิตทั้งสองมีค่าต่างกัน

ตัวอย่าง: สมมติว่าเรามีเลขสองตัว A = 5 และ B = 3 ในระบบเลขฐานสอง: A = 5 = 0101 B = 3 = 0011

เมื่อทำ BitXor:

  0101 (A = 5)
^ 0011 (B = 3)
  ----
  0110 (Result = 6)

จะเห็นได้ว่า:

  • บิตที่ 1 (จากขวา): 1 ^ 1 = 0
  • บิตที่ 2: 0 ^ 1 = 1
  • บิตที่ 3: 1 ^ 0 = 1
  • บิตที่ 4: 0 ^ 0 = 0

ดังนั้น ผลลัพธ์คือ 0110 ในเลขฐานสอง ซึ่งเท่ากับ 6 ในเลขฐานสิบ

การใช้งาน BitXor:

  1. การสลับค่าโดยไม่ใช้ตัวแปรเพิ่ม: a ^= b; b ^= a; a ^= b; // ตอนนี้ a และ b ได้สลับค่ากันแล้ว

  2. การเข้ารหัสอย่างง่าย: encrypted = data ^ key decrypted = encrypted ^ key // จะได้ data กลับคืนมา

  3. การตรวจสอบความเท่ากันของตัวเลข: if ((a ^ b) == 0) // a และ b เท่ากัน

  4. การหาตัวเลขที่ไม่มีคู่ในชุดข้อมูล: result = num1 ^ num2 ^ num3 ^ … // ผลลัพธ์จะเป็นตัวเลขที่ไม่มีคู่

BitXor มีคุณสมบัติพิเศษคือ การ XOR กับตัวเองจะได้ 0 และการ XOR กับ 0 จะได้ตัวเองกลับคืนมา ทำให้มันมีประโยชน์มากในการเข้ารหัสและการตรวจสอบข้อมูล

BitNot (~):

BitNot หรือ NOT มักใช้สัญลักษณ์ ~ เป็นการดำเนินการทางตรรกะที่กลับบิตของตัวเลข โดยมีกฎง่ายๆ คือ:

  • NOT 1 = 0
  • NOT 0 = 1

กล่าวคือ BitNot จะเปลี่ยนทุกบิต 0 เป็น 1 และทุกบิต 1 เป็น 0

ตัวอย่าง: สมมติว่าเรามีเลข A = 5 ในระบบเลขฐานสอง (สมมติว่าเป็นเลข 8 บิต): A = 5 = 00000101

เมื่อทำ BitNot:

  00000101 (A = 5)
  --------
~ 11111010 (Result = 250 ใน unsigned int, หรือ -6 ใน signed int)

จะเห็นได้ว่า:

  • ทุกบิต 0 ถูกเปลี่ยนเป็น 1
  • ทุกบิต 1 ถูกเปลี่ยนเป็น 0

ผลลัพธ์จะขึ้นอยู่กับว่าเราใช้ระบบเลขแบบไหน (signed หรือ unsigned):

  • ในระบบ unsigned 8 บิต: 11111010 = 250
  • ในระบบ signed 8 บิต: 11111010 = -6

การใช้งาน BitNot:

  1. การสร้าง mask สำหรับการลบบิต: clear_mask = ~(1 << n) // สร้าง mask ที่มีบิตที่ n เป็น 0 และที่เหลือเป็น 1 x &= clear_mask // ลบบิตที่ n ของ x

  2. การหาค่าสัมบูรณ์ของเลขติดลบในระบบ two’s complement: abs_value = (x ^ (x >> 31)) - (x >> 31) // x >> 31 จะได้ 0 สำหรับเลขบวก และ -1 สำหรับเลขลบ

  3. การสลับสถานะของทุกบิต: inverted = ~original

  4. การหาค่าสูงสุดของตัวแปรที่มีขนาด n บิต: max_value = ~0 >> (32 - n) // สำหรับตัวแปร 32 บิต

BitNot มักใช้ร่วมกับ BitAnd เพื่อลบบิตที่ต้องการ หรือใช้ในการสร้าง mask สำหรับการจัดการบิต

Left Shift (<<):

Left Shift มักใช้สัญลักษณ์ << เป็นการดำเนินการที่เลื่อนบิตทั้งหมดของตัวเลขไปทางซ้ายตามจำนวนที่กำหนด โดยเติม 0 ทางด้านขวา Left Shift โดยทั่วไปแล้วไม่แตกต่างกันระหว่างเลข unsigned int และ signed int แต่มีบางประเด็นที่ควรระวัง:

  1. ผลลัพธ์:

    • ในกรณีของเลขไม่มีเครื่องหมาย ผลลัพธ์จะเป็นบวกเสมอ
    • สำหรับเลขมีเครื่องหมาย ผลลัพธ์อาจเปลี่ยนเครื่องหมายได้หากบิตเครื่องหมาย (sign bit) ถูกเปลี่ยน
  2. Overflow:

    • ทั้งสองกรณีอาจเกิด overflow ได้ถ้าเลื่อนมากเกินไป
    • สำหรับเลข signed int overflow อาจทำให้เกิดการเปลี่ยนเครื่องหมายโดยไม่คาดคิด
  3. Undefined Behavior:

    • ในบางภาษา เช่น C/C++, การเลื่อนด้วยค่าที่มากกว่าหรือเท่ากับจำนวนบิตของตัวแปรอาจทำให้เกิด Undefined Behavior ได้

ตัวอย่าง: สมมติว่าเรามีเลข A = 5 และต้องการเลื่อนซ้าย 2 ตำแหน่ง ในระบบเลขฐานสอง (สมมติว่าเป็นเลข 8 บิต): A = 5 = 00000101

เมื่อทำ Left Shift 2 ตำแหน่ง (A << 2):

  00000101 (A = 5)
  --------
  00010100 (Result = 20)

จะเห็นได้ว่า::

  • บิตทั้งหมดถูกเลื่อนไปทางซ้าย 2 ตำแหน่ง
  • เติม 0 สองตัวทางด้านขวา

ผลลัพธ์: 00010100 ในเลขฐานสอง ซึ่งเท่ากับ 20 ในเลขฐานสิบ

ตัวอย่าง 2:

unsigned int a = 1;  // 00000001 in binary
int b = 1;           // 00000001 in binary

// Left shift by 3
unsigned int a_result = a << 3;  // 00001000 (8 in decimal)
int b_result = b << 3;           // 00001000 (8 in decimal)

// สำหรับตัวเลขเล็ก ๆ ผลอาจจะไม่ต่างกัน
// แต่เมื่อมีตัวเลขที่ใหญ่ขึ้น อาจจะให้ผลตรงกันข้ามแทน:

unsigned int large_a = 1 << 31;  // 10000000 00000000 00000000 00000000 (2147483648 in decimal)
int large_b = 1 << 31;           // 10000000 00000000 00000000 00000000 (-2147483648 in decimal, due to two's complement)

ในตัวอย่างสุดท้าย เราเห็นความแตกต่าง:

  • สำหรับ unsigned int, ผลลัพธ์คือเลขบวกที่ใหญ่ที่สุดที่เป็นไปได้
  • สำหรับ signed int, ผลลัพธ์คือเลขลบที่น้อยที่สุดที่เป็นไปได้ เนื่องจากบิตซ้ายสุดถูกตีความเป็นเครื่องหมายลบในระบบ two’s complement

กฎการทำงาน:

  • เลื่อนบิตทั้งหมดไปทางซ้าย n ตำแหน่ง
  • เติม 0 ทางด้านขวา n ตัว
  • บิตที่เลื่อนออกไปทางซ้ายจะหายไป

การใช้งาน Left Shift:

  1. การคูณด้วยกำลังของ 2: x << n เท่ากับ x * (2^n) เช่น 5 << 2 = 5 * (2^2) = 5 * 4 = 20

  2. การสร้างหน้ากาก (mask) สำหรับการตั้งค่าบิต: mask = 1 << n // สร้างหน้ากากที่มีบิตที่ n เป็น 1 และที่เหลือเป็น 0 x |= mask // ตั้งค่าบิตที่ n ของ x เป็น 1

  3. การเข้ารหัสข้อมูลหลายบิตในตัวแปรเดียว: encoded = (r << 16) | (g << 8) | b // เก็บค่า RGB ในตัวแปร 32 บิต

  4. การทำงานกับบิตแฟล็ก: FLAG_A = 1 << 0 FLAG_B = 1 << 1 FLAG_C = 1 << 2 // ใช้งาน: flags = FLAG_A | FLAG_B

Left Shift มีประสิทธิภาพสูงในการคูณด้วยกำลังของ 2 และมักใช้ในการจัดการบิตแฟล็กและการสร้างหน้ากากสำหรับการดำเนินการระดับบิต

Right Shift (>>):

Right Shift มักใช้สัญลักษณ์ >> เป็นการดำเนินการที่เลื่อนบิตทั้งหมดของตัวเลขไปทางขวาตามจำนวนที่กำหนด การทำงานจะแตกต่างกันเล็กน้อยระหว่างเลขที่มีเครื่องหมาย (signed) และไม่มีเครื่องหมาย (unsigned)

สำหรับเลข unsigned int (Logical Right Shift):

  • เลื่อนบิตทั้งหมดไปทางขวา n ตำแหน่ง
  • เติม 0 ทางด้านซ้าย n ตัว
  • บิตที่เลื่อนออกไปทางขวาจะหายไป

สำหรับเลข signed int (Arithmetic Right Shift):

  • เลื่อนบิตทั้งหมดไปทางขวา n ตำแหน่ง
  • เติมด้วยบิตเครื่องหมาย (sign bit) ทางด้านซ้าย n ตัว
  • บิตที่เลื่อนออกไปทางขวาจะหายไป

ตัวอย่าง: สมมติว่าเรามีเลข A = 20 และต้องการเลื่อนขวา 2 ตำแหน่ง ในระบบเลขฐานสอง (สมมติว่าเป็นเลข 8 บิต): A = 20 = 00010100

เมื่อทำ Right Shift 2 ตำแหน่ง (A >> 2):

  00010100 (A = 20)
  --------
  00000101 (Result = 5)

จะเห็นได้ว่า::

  • บิตทั้งหมดถูกเลื่อนไปทางขวา 2 ตำแหน่ง
  • เติม 0 สองตัวทางด้านซ้าย (สำหรับเลขไม่มีเครื่องหมาย)

ผลลัพธ์: 00000101 ในเลขฐานสอง ซึ่งเท่ากับ 5 ในเลขฐานสิบ

การใช้งาน Right Shift:

  1. การหารด้วยกำลังของ 2: x >> n เท่ากับ x / (2^n) (ปัดเศษลง) เช่น 20 >> 2 = 20 / (2^2) = 20 / 4 = 5

  2. การแยกส่วนของข้อมูลที่ถูกเก็บรวมกัน: r = (rgb >> 16) & 0xFF // แยกค่าสีแดงจาก RGB 24 บิต g = (rgb >> 8) & 0xFF // แยกค่าสีเขียว b = rgb & 0xFF // แยกค่าสีน้ำเงิน

  3. การตรวจสอบเครื่องหมายของตัวเลข: sign = x >> 31 // สำหรับตัวแปร 32 บิต, จะได้ 0 สำหรับบวก และ -1 สำหรับลบ

  4. การทำ rounded division: (x + (1 << (n-1))) >> n // หารด้วย 2^n และปัดเศษ

ข้อควรระวัง:

  • ใน Java, >> เป็น arithmetic shift สำหรับ int และ long, ส่วน >>> เป็น logical shift
  • ในภาษาอื่นๆ เช่น C/C++, การทำงานของ >> กับเลขติดลบอาจแตกต่างกันไปตาม Compiler

Right Shift มีประสิทธิภาพสูงในการหารด้วยกำลังของ 2 และมักใช้ในการจัดการข้อมูลที่ถูกเก็บรวมกันในรูปแบบบิต

หากเปรียบกับ Right Shift แม้ว่าการทำงานพื้นฐานของ Left Shift จะเหมือนกันสำหรับทั้งเลข signed int และ unsigned int แต่การตีความผลลัพธ์และผลกระทบต่อเครื่องหมาย(sign bit)อาจแตกต่างกัน ดังนั้นจึงควรระมัดระวังเมื่อใช้ Left Shift กับเลขที่มี signed int โดยเฉพาะเมื่อเลื่อนด้วยค่าที่สูงมาก ๆ

Conclusion

ลักษณะเด่นของ Bitwise Operations:

  • ประสิทธิภาพสูง: ทำงานเร็วกว่าการคำนวณปกติ
  • ประหยัดหน่วยความจำ: จัดเก็บข้อมูลหลายอย่างในตัวแปรเดียว
  • ใช้ในงานระดับต่ำ: การทำงานกับฮาร์ดแวร์, ระบบปฏิบัติการ
  • การเข้ารหัส: ใช้ในอัลกอริทึมการเข้ารหัสและบีบอัดข้อมูล
  • การจัดการแฟล็ก: ใช้ในการตั้งค่าและตรวจสอบสถานะต่างๆ

การใช้งานทั่วไป:

  • จัดการสิทธิ์และการอนุญาตในระบบไฟล์
  • การเข้ารหัสและถอดรหัสข้อมูล
  • การบีบอัดข้อมูล
  • การทำงานกับพิกเซลในการประมวลผลภาพ
  • การจัดการโปรโตคอลเครือข่าย
  • การทำงานกับ register ใน Embedded system

ข้อควรระวัง:

  • ต้องระมัดระวังเรื่องขนาดของข้อมูล (8, 16, 32, 64 บิต)
  • ความแตกต่างระหว่างเลขมี signed int และ unsigned int
  • ความแตกต่างของการทำงานในแต่ละภาษาโปรแกรม

**unsigned int คือตัวเลขเป็นบวกเสมอ signed int สามารถมีค่าเป็นลบได้

#siamstr #Blog

Author Public Key
npub1vaz88a5zhsqsrj220vh5vdnpjsu53msm34hzvcrh27x5d7zeav7qm45t60