حيل التعامل مع البِتّات (Bit Manipulation Tricks)
سنفترض وجود المتغيرين التاليين:
int x; // some non-negative integer
int k; // 0-based bit index (0 = least significant bit)وسنستخدم أقنعة (masks) مثل 1 << k، والتي يكون فيها البِت رقم k فقط مساويًا لـ1.
فحص ما إذا كان البِت k يساوي 1
bool isSet = (x >> k) & 1;x >> k→ إزاحة لليمين بحيث ينتقل البِت رقمkإلى أقصى اليمين.(x >> k) & 1→ تُبقي البِت الأيمن فقط وتصفّر باقي البِتّات.- الناتج يكون
0أو1، وهو يعادلfalseأوtrueمنطقيًا.
أمثلة
افحص ما إذا كان البِت رقم 1 في
xمُعينًا (يساوي 1).
int x = 10; // 10 = 1010 (in binary)
int k = 1;
bool isSet = (x >> 1) & 1; // 1010 >> 1 = 101; 101 & 1 = 1 (true)معطى عددان
nوk، اكتب دالة تحدد ما إذا كان البِت رقمkفيnمساويًا لـ1.
bool kthBitIsSet(int n, int k) {
return (n >> k) && 1;
}ضبط البِت رقم k إلى 1
x |= (1 << k);- العامل
|يعيّن البِت إلى 1 إذا كان على الأقل أحد الطرفين يحتوي 1. - بهذا نجبر البِت رقم
kأن يصبح 1، بينما تبقى باقي البِتّات كما هي.
مثال
int x = 4; // 0100
x |= (1 << 0); // 0100 | 0001 = 0101 (5)تصفير البِت رقم k (جعله 0)
x &= ~(1 << k);1 << k→ قناع فيه البِت رقمkيساوي 1.~(1 << k)→ قناع فيه البِت رقمkيساوي 0، وجميع البِتّات الأخرى تساوي 1.- إجراء
&مع هذا القناع يقوم بتصفير البِت رقمkفقط.
مثال
int x = 13; // 1101
x &= ~(1 << 2); // ~(0100) = ...1011
// 1101 & 1011 = 1001 (9)الآن البِت رقم 2 أصبح 0.
قلب (تبديل) البِت رقم k
x ^= (1 << k);العامل
^(XOR) يقلب البِت عندما يكون القناع فيه 1:0 ^ 1 = 11 ^ 1 = 0
مثال
int x = 5; // 0101
x ^= (1 << 2); // 0101 ^ 0100 = 0001 (1)البِت رقم 2 كان 1 → أصبح الآن 0.
إزالة أقل بِت معيّن (أقل 1 من اليمين)
هذه حيلة مشهورة جدًّا:
x = x & (x - 1);هذا السطر يزيل (يُصفّر) أقل بِت معيّن (أول 1 من جهة اليمين) في x.
لماذا تعمل هذه الحيلة؟
فكّر ماذا يحدث للبِتّات عندما نطرح 1:
- عند تنفيذ
x - 1، نمشي من اليمين حتى نجد أول بِت قيمته 1. - هذا البِت الأقل (أقرب 1 لليمين) يتحوّل إلى
0. - كل البِتّات التي على يمينه (والتي كانت 0) تتحوّل إلى
1(بسبب الاقتراض في الطرح).
إذًا حول أقل بِت معيّن، النمط يكون بهذا الشكل:
x: .... 1 0 0 0 0
x - 1: .... 0 1 1 1 1
عند إجراء AND:
x = .... 1 0 0 0 0
x - 1 = .... 0 1 1 1 1
-------------------------
x&(x-1) = .... 0 0 0 0 0
- أقل بِت معيّن يصبح 0 لأنه
1 & 0. - كل البِتّات التي على يمينه كانت 0 في
x، ومع أي قيمة فيx - 1تعطي0 & something = 0. - كل البِتّات التي على يساره لا تتأثر، لأن الطرح لم يغيّرها؛ أي أن قيمتها في
xوx - 1متطابقة، وبالتالي تبقى كما هي بعد AND.
لذلك x & (x - 1) تساوي قيمة x نفسها لكن مع أقل بِت معيّن مصفّرًا.
مثال
x = 12 = 1100
x - 1 = 11 = 1011
x & (x - 1) = 8 = 1000
إذًا قمنا بإزالة أقل بِت معيّن (البِت رقم 2).
فحص ما إذا كان x قوة للعدد 2
سنفترض أن x عدد موجب، ونستخدم الحيلة السابقة:
bool isPowerOfTwo = (x & (x - 1)) == 0;لماذا تعمل هذه الحيلة؟
- العدد الذي هو قوة للعدد 2 في الثنائي (
1,10,100,1000, …) يحتوي بِتًا واحدًا فقط معيّنًا. - عند تنفيذ
x & (x - 1)نقوم بإزالة أقل بِت معيّن. - إذا كان هناك بِت واحد فقط معيّن، فبعد إزالة هذا البِت تصبح القيمة 0.
إذًا:
x & (x - 1) == 0⇔xلديه بِت واحد معيّن فقط (أي قوة للعدد 2).
أمثلة
x = 8 (1000)→x & (x-1) = 1000 & 0111 = 0000→ قوة للعدد 2.x = 12 (1100)→x & (x-1) = 1100 & 1011 = 1000→ ليست 0 → ليس قوة للعدد 2.
الحصول على أقل بِت معيّن فقط
int lowest = x & -x;هنا -x هو المتمّم الثنائي (two’s complement) للعدد x.
الفكرة:
- التعبير
x & -xيعزل أقل بِت معيّن (أول 1 من جهة اليمين) ويصفّر جميع البِتّات الأخرى.
مثال
x = 12 = ...00001100
-x (two's complement) = ...11110100
x & -x = ...00000100 = 4
أقل بِت معيّن في x يمثّل القيمة 4 (أي البِت رقم 2).
ملخص سريع
- فحص البِت رقم
k:isSet = (x >> k) & 1; - ضبط البِت رقم
kإلى 1:x |= (1 << k); - تصفير البِت رقم
k:x &= ~(1 << k); - قلب (تبديل) البِت رقم
k:x ^= (1 << k); - إزالة أقل بِت معيّن:
x &= (x - 1); - فحص إذا كان
xقوة للعدد 2:x > 0 && (x & (x - 1)) == 0 - الحصول على أقل بِت معيّن فقط:
lowest = x & -x;
هذه هي أهم وأشهر حيل التعامل مع البِتّات (bit manipulation) في البرمجة.