المعاملات الثنائية في JavaScript

من موسوعة حسوب
مراجعة 08:41، 16 يناير 2018 بواسطة عبد اللطيف ايمش (نقاش | مساهمات)
(فرق) → مراجعة أقدم | المراجعة الحالية (فرق) | مراجعة أحدث ← (فرق)

المعاملات الثنائية (bitwise operators) تُعامِل القيم كأنها سلسلة تتألف من 32 بت (أصفار وواحدات) بدلًا من الأرقام Number في النظام العشري (decimal) أو الست عشري (hexadecimal) أو الثماني (octal)؛ فمثلًا يُمثَّل العدد 9 في نظام العد الثنائي بالمحارف 1001؛ وتجري معاملات البتات في JavaScript العمليات على التمثيل الثنائي للأعداد، لكنها تعيد قيمًا عدديةً عاديةً قياسيةً في JavaScript.

الجدول الآتي يُلخِّص معاملات البتات في JavaScript:

المعامل الاستخدام الوصف
AND a & b يُعيد 1 مكان كل بت له القيمة 1 في كلا القيمتين.
OR a | b يُعيد 1 مكان كل بت له القيمة 1 في إحدى القيمتين أو كلتيهما.
XOR a ^ b يُعيد 1 مكان كل بت له القيمة 1 في إحدى القيمتين فقط دونًا عن الأخرى.
NOT ‎~ a يعكس قيمة البتات.
الإزاحة نحو اليسار a << b يزيح بتات القيمة a بمقدار b إلى اليسار، وسنحصل على أصفار في اليمين.
معامل الإزاحة إلى اليمين مع الحفاظ على الإشارة a >> b يزيح بتات القيمة a بمقدار b إلى اليمين.
معامل الإزاحة إلى اليمين دون الحفاظ على الإشارة a >>> b يزيح بتات القيمة a بمقدار b إلى اليمين، وسيؤدي إلى إضافة أصفار على الطرف الأيسر.

الأعداد الصحيحة بطول 32 بت مع إشارة

جميع القيم التي تُجرى عليها عمليات عبر معاملات البتات ستحوّل إلى أعداد صحيحة بطول 32 بت لها إشارة (بصيغة المتمم الثنائي two's complement format). صيغة المتمم الثنائي تعني أنَّ العدد السالب المقابل للعدد الحالي (أي 5 و ‎-5) هو ناتج قلب جميع البتات في العدد + 1، فمثلًا هذا هو تمثيل العدد 314 في النظام الثنائي:

00000000000000000000000100111010

وما يلي هو ناتج ‎~314 أي المتمم الأحادي (ones' complement) للعدد ‎-314:

11111111111111111111111011000101

وفي النهاية هذا هو العدد ‎-314، أي المتمم الثنائي للعدد ‎314:

11111111111111111111111011000110

المتمم الثنائي يضمن أنَّ البت الموجود على اليسار هو 0 إذا كان العدد موجبًا و 1 إذا كان العدد سالبًا، ولهذا السبب يسمى هذا البت بالمصطلح «بت الإشارة» (sign bit). العدد 0 في النظام العشري هو سلسلة من الأصفار في النظام الثنائي:

0 (base 10) = 00000000000000000000000000000000 (base 2)

أما العدد ‎-1 في النظام العشري هو سلسلة من الواحدات في النظام الثنائي:

-1 (base 10) = 11111111111111111111111111111111 (base 2)

والعدد ‎-2147483648 في النظام العشري (تمثيله في نظام العد الست عشري هو ‎-0x80000000) هو سلسلة من الأصفار باستثناء أوّل بت (على اليسار) فهو 1:

-2147483648 (base 10) = 10000000000000000000000000000000 (base 2)

أما العدد 2147483647 في النظام العشري (تمثيله في نظام العد الست عشري هو 0x7fffffff) هو سلسلة من الواحدات باستثناء أوّل بت (على اليسار):

2147483647 (base 10) = 01111111111111111111111111111111 (base 2)

لاحظ أنَّ العددين ‎-2147483648 و 2147483647 هما أدنى وأقصى عدد صحيح يمكن تمثيله مع إشارة في 32 بت.

المعاملات الثنائية

تعمل معاملات البتات كما يلي:

  • ستحوّل القيم إلى أعداد صحيحة بطول 32 بت تتألف من الأصفار والواحدات، والأعداد الأطول من 32 بت سيُقتَطَع جزءٌ منها، فالعدد الآتي أطول من 32 بت، لاحظ كيف سيحوّل إلى 32 بت:
Before: 11100110111110100000000000000110000000000001
After:              10100000000000000110000000000001
  • كل بت في القيمة الأولى سيرتبط بمقابله في القيمة الثانية، أي أنَّ أوّل بت في القيمة الأولى سيرتبط بأوّل بت في القيمة الثانية، وهلمّ جرًا للبقية.
  • بعد تطبيق المعامل على كل زوج من البتات، فستعاد القيمة النهائية.

المعامل AND (&)

إجراء عملية AND على كل زوج من البتات، فالتعبير a AND b يعطي 1 إذا كان زوج البتات المعني في a و b هو 1. هذا جدول يوضِّح ناتج عملية AND على مختلف القيم:

a b a AND b
0 0 0
0 1 0
1 0 0
1 1 1

مثال عن استخدام المعامل AND على العددين 9 و 14، وسيكون الناتج هو 8:

.    9 (base 10) = 00000000000000000000000000001001 (base 2)
    14 (base 10) = 00000000000000000000000000001110 (base 2)
                   --------------------------------
14 & 9 (base 10) = 00000000000000000000000000001000 (base 2) = 8 (base 10)

لاحظ أنَّ إجراء العملية AND على العدد x مع العدد 0 سينتج 0، وإجراء العملية AND على العدد x مع العدد -1 سينتج x.

المعامل OR (|)

إجراء عملية OR على كل زوج من البتات، فالتعبير a OR b يعطي 1 إذا كان زوج البتات المعني في a أو b هو 1. هذا جدول يوضِّح ناتج عملية OR على مختلف القيم:

a b a OR b
0 0 0
0 1 1
1 0 1
1 1 1

مثال عن استخدام المعامل OR على العددين 9 و 14، وسيكون الناتج هو 15:

.    9 (base 10) = 00000000000000000000000000001001 (base 2)
    14 (base 10) = 00000000000000000000000000001110 (base 2)
                   --------------------------------
14 | 9 (base 10) = 00000000000000000000000000001111 (base 2) = 15 (base 10)

لاحظ أنَّ إجراء العملية OR على العدد x مع العدد 0 سينتج x، وإجراء العملية OR على العدد x مع العدد -1 سينتج -1.

المعامل XOR (^)

إجراء عملية XOR على كل زوج من البتات، فالتعبير a XOR b يعطي 1 إذا كان زوج البتات المعني في a أو b مختلفًا عن بعضه. هذا جدول يوضِّح ناتج عملية XOR على مختلف القيم:

a b a XOR b
0 0 0
0 1 1
1 0 1
1 1 0

مثال عن استخدام المعامل XOR على العددين 9 و 14، وسيكون الناتج هو 7:

.    9 (base 10) = 00000000000000000000000000001001 (base 2)
    14 (base 10) = 00000000000000000000000000001110 (base 2)
                   --------------------------------
14 ^ 9 (base 10) = 00000000000000000000000000000111 (base 2) = 7 (base 10)

لاحظ أنَّ إجراء العملية XOR على العدد x مع العدد 0 سينتج x، وإجراء العملية XOR على العدد x مع العدد -1 سينتج ‎~x.

المعامل NOT (~)

يجري المعامل NOT عمليته على كل بت، فالتعبير NOT a سيؤدي إلى قلب القيمة a (أي إعادة المتمم الأحادي). هذا جدول يوضِّح ناتج عملية NOT:

a NOT a
0 1
1 0

مثال عن استخدام المعامل NOT على العدد 9، وسيكون الناتج هو -10:

 9 (base 10) = 00000000000000000000000000001001 (base 2)
               --------------------------------
~9 (base 10) = 11111111111111111111111111110110 (base 2) = -10 (base 10)

لاحظ أنَّ إجراء العملية NOT على العدد x سينتج ‎‎‎-‎(x+1)‎. فمثلًا التعبير ‎~-5 سينتج 4. مثال عن استخدام المعامل NOT مع الدالة indexOf()‎:

var str = 'rawr';
var searchFor = 'a';

// هذه طريقة بديلة لكتابة العبارة الشرطية
// if (-1*str.indexOf('a') <= 0)
if (~str.indexOf(searchFor)) {
  // ما نبحث عنه موجودٌ في السلسلة النصية
} else {
  // ما نبحث عنه ليس موجودًا في السلسلة النصية
}

// هذه هي القيمة المُعادة من التعبير
// (~str.indexOf(searchFor))

// r == -1
// a == -2
// w == -3

معاملات الإزاحة الثنائية

تأخذ معاملات الإزاحة الثنائية (bitwise shift operators) قيمتين: القيمة الأولى هي القيمة التي ستُزاح البتات فيها، والقيمة الثانية تُمثِّل مقدار إزاحة بتات القيمة الأولى؛ اتجاه عملية الإزاحة يختلف حسب المعامل المُستخدَم.

معامل الإزاحة إلى اليسار (>>)

هذا المعامل يؤدي إلى إزاحة بتات القيمة الأولى وفق العدد المحدد باتجاه اليسار، والبتات الزائدة التي تُزاح إلى اليسار ستُهمَل، وستُضاف أصفار من الجهة اليمنى.

المثال الآتي يُطبِّق معامل الإزاحة إلى اليسار على العدد 9، إذ سنزيح بتاته بمقدار 2، وستنتج عندنا القيمة 36:

.    9 (base 10): 00000000000000000000000000001001 (base 2)
                  --------------------------------
9 << 2 (base 10): 00000000000000000000000000100100 (base 2) = 36 (base 10)

لاحظ أنَّ إزاحة إي قيمة (x) إلى اليسار بمقدار y سينتج x * 2^y.

معامل الإزاحة إلى اليمين مع الحفاظ على الإشارة (<<)

معامل الإزاحة إلى اليمين مع الحفاظ على الإشارة (sign-propagating right shift) يؤدي إلى إزاحة بتات القيمة الأولى وفق العدد المحدد باتجاه اليمين، والبتات الزائدة التي تُزاح إلى اليمين ستُهمَل، وستُنسَخ قيمة البت الموجود على أقصى اليسار، وبالتالي لن تتغير إشارة العدد (بت الإشارة).

المثال الآتي يُطبِّق معامل الإزاحة إلى اليمين مع الحفاظ على الإشارة على العدد 9، إذ سنزيح بتاته بمقدار 2، وستنتج عندنا القيمة 2:

.    9 (base 10): 00000000000000000000000000001001 (base 2)
                  --------------------------------
9 >> 2 (base 10): 00000000000000000000000000000010 (base 2) = 2 (base 10)

وبشكلٍ مشابه، التعبير ‎-9 >> 2 سينتج ‎-3، وذلك لأنَّ هذا المعامل يحافظ على الإشارة:

.    -9 (base 10): 11111111111111111111111111110111 (base 2)
                   --------------------------------
-9 >> 2 (base 10): 11111111111111111111111111111101 (base 2) = -3 (base 10)

معامل الإزاحة إلى اليمين دون الحفاظ على الإشارة (<<<)

معامل الإزاحة إلى اليمين دون الحفاظ على الإشارة (zero-fill right shift) يؤدي إلى إزاحة بتات القيمة الأولى وفق العدد المحدد باتجاه اليمين، والبتات الزائدة التي تُزاح إلى اليمين ستُهمَل، وستصبح قيمة بت الإشارة 0، ولهذا ستكون نتيجة هذا العملية عددًا موجبًا دومًا.

لاحظ أنَّ تطبيق المعامل <<< على الأعداد الموجبة ينُتِج نفس نتيجة تطبيق المعامل <<، فمثلًا ناتج العملية ‎9 >>> 2 هو 2، وسنحصل على النتيجة نفسها إن طبقنا العملية ‎9 >> 2:

.     9 (base 10): 00000000000000000000000000001001 (base 2)
                   --------------------------------
9 >>> 2 (base 10): 00000000000000000000000000000010 (base 2) = 2 (base 10)

لكن الوضع مختلف بالنسبة إلى الأرقام السالبة، فمثلًا العملية ‎-9 >>> 2 ستُنتِج 1073741821، وهذا يختلف كثيرًا عن العملية ‎-9 >> 2 (التي تُنتِج ‎-3):

أمثلة

الرايات وأقنعة البتات

تُستخدَم المعاملات الثنائية عادةً لإنشاء أو تعديل أو قراءة سلسلة من الرايات (flags)، والتي تُشبه المتغيرات الثنائية (binary variables)، ويمكن أن تُستخدَم المتغيرات العادية بدل استخدام سلسلة من الرايات، لكن الرايات الثنائية تستهلك ذاكرةً أقل بكثير (أقل بمقدار 32 ضعف).

لنفترض أنَّ لدينا أربع رايات:

  • الراية A: لدينا مشكلة مع النمل (ant)!
  • الراية B: نملك خفاشًا (bat)!
  • الراية C: لدينا قطة (cat)!
  • الراية D: لدينا بطة (duck)!

تُمثَّل هذه الرايات بسلسلة من البتات DCBA، وعندما «تُضبَط» الراية فستملك القيمة 1، وعندما «تُحذَف» الراية فستملك القيمة 0؛ ولنفرض مثلًا أنَّ المتغير flags له القيمة الثنائية 0101:

var flags = 5;   // binary 0101

هذه القيمة تُشير إلى أنَّ:

  • الراية A مضبوطة (لدينا مشكلة من النمل).
  • الراية B غير مضبوطة (لا نملك خفاشًا).
  • الراية C مضبوطة (لدينا قطة).
  • الراية D غير مضبوطة (لا نملك بطةً).

ولمّا كانت المعاملات الثنائية تُجري عملياتها على أعداد بطول 32 بت، فإنَّ القيمة 0101 تُخزَّن فعليًا بالشكل 00000000000000000000000000000101، لكن الأصفار الموجودة على اليسار غير مهمة إذ لا تحتوي على معلومات لها معنى.

قناع البتات (bitmask) هو سلسلة من البتات التي يمكنها تعديل أو قراءة الرايات، ويُعرَّف عادةً قناعٌ «أوليٌّ» (primitive) لكل راية:

var FLAG_A = 1; // 0001
var FLAG_B = 2; // 0010
var FLAG_C = 4; // 0100
var FLAG_D = 8; // 1000

يمكن إنشاء أقنعة جديدة باستخدام المعاملات الثنائية على الأقنعة الأوليّة، فمثلًا القناع 1001 يمكن إنشاؤه باستخدام العملية OR على القيم FLAG_A و FLAG_B و FLAG_D:

var mask = FLAG_A | FLAG_B | FLAG_D; // 0001 | 0010 | 1000 => 1011

يمكن استخراج قيمة الرايات باستخدام العملية AND عليها مع قيمة القناع، لاحظ أنَّ أقنعة البتات ستَحجب (mask) الرايات غير المهمة، فلو كانت قيمة القناع 0100 فيمكننا أن نعرف إذا كانت الراية C مضبوطة عبر:

// إذا كنّا نملك قطةً
if (flags & FLAG_C) { // 0101 & 0100 => 0100 => true
   // ...
}

القناع الثنائي الذي ضُبِطَت فيه أكثر من راية سيعمل عمل «إما/أو»، فالمثالان الآتيان متكافئان:

// إذا كنا نملك خفاشًا أو كنا نملك قطةً
// (0101 & 0010) || (0101 & 0100) => 0000 || 0100 => true
if ((flags & FLAG_B) || (flags & FLAG_C)) {
   // ...
}

والمثال:

// إذا كنا نملك خفاشًا أو قطةً
var mask = FLAG_B | FLAG_C; // 0010 | 0100 => 0110
if (flags & mask) { // 0101 & 0110 => 0100 => true
   // ...
}

يمكن ضبط الرايات عبر إجراء عملية OR عليها مع القناع الثنائي، فإن لم يكن البت الموافق للراية مضبوطًا فسيُضبَط عبر العملية OR. فمثلًا القناع 1100 يمكن أن يُستخدَم لضبط الرايتين C و D:

// نملك قطةً وبطةً
var mask = FLAG_C | FLAG_D; // 0100 | 1000 => 1100
flags |= mask;   // 0101 | 1100 => 1101

يمكن إلغاء ضبط الرايات بتنفيذ العملية AND عليها مع القناع الثنائي، إذ سيؤدي كل بت له القيمة 0 إلى إلغاء ضبط الراية المرتبطة به. يمكن إنشاء القناع عبر تنفيذ العملية NOT على أقنعة الرايات الأوليّة، فمثلًا يمكن استخدام القناع 1010 لإلغاء ضبط الرايتين A و C:

// ليس لدينا مشكلة مع النمل ولا نملك قطةً
var mask = ~(FLAG_A | FLAG_C); // ~0101 => 1010
flags &= mask;   // 1101 & 1010 => 1000

يمكن إنشاء القناع باستخدام التعبير ‎~FLAG_A & ~FLAG_C (ويسمى ذلك بقانون دي مورغان [[[wikipedia:De_Morgan's_laws|De Morgan's law]]]):

// ليس لدينا مشكلة مع النمل ولا نملك قطةً
var mask = ~FLAG_A & ~FLAG_C;
flags &= mask;   // 1101 & 1010 => 1000

يمكن تفعيل/تعطيل الرايات بتنفيذ العملية XOR عليها مع القناع الثنائي، إذ سيؤدي وجود البت في القيمة إلى تفعيل أو تعطيل الراية المرتبطة به، فمثلًا القناع 0110 يمكن أن يستخدم لتفعيل/تعطيل الرايتين B و C:

// إذا لم يكن لدينا خفاش فأصبح لدينا واحدٌ الآن 
// وإذا كان لدينا خفاش فلم يعد موجودًا الآن
// وينطبق المثل على القطط
var mask = FLAG_B | FLAG_C;
flags = flags ^ mask;   // 1100 ^ 0110 => 1010

لاحظ أنَّ بإمكاننا قلب الرايات باستخدام معامل NOT:

flags = ~flags;    // ~1010 => 0101

تحويل بين السلاسل النصية والأرقام الثنائية

لتحويل سلسلة نصية String تُمثِّل عددًا ثنائيًا إلى عددٍ عشري Number:

var sBinString = '1011';
var nMyNumber = parseInt(sBinString, 2);
alert(nMyNumber); // 11, i.e. 1011

أما لتحويل عدد عشري Number إلى سلسلة نصية String ثنائية:

var nMyNumber = 11;
var sBinString = nMyNumber.toString(2);
alert(sBinString); // 1011, i.e. 11

أتمتة إنشاء الأقنعة

يمكننا إنشاء عدّة أقنعة (masks) من مجموعة قيمة منطقية Boolean كما في الدالة الآتية:

function createMask() {
  var nMask = 0, nFlag = 0, nLen = arguments.length > 32 ? 32 : arguments.length;
  for (nFlag; nFlag < nLen; nMask |= arguments[nFlag] << nFlag++);
  return nMask;
}
var mask1 = createMask(true, true, false, true); // 11, i.e.: 1011
var mask2 = createMask(false, false, true); // 4, i.e.: 0100
var mask3 = createMask(true); // 1, i.e.: 0001
// etc.

alert(mask1); // 11, i.e.: 1011

دعم المتصفحات

الميزة Chrome Firefox Internet Explorer Opera Safari
الدعم الأساسي نعم نعم نعم نعم نعم

مصادر ومواصفات