21
مارس

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

تعتبر المعاملات الثنائية “Bitwise Operators” من المواضيع المجهولة لدى الكثير من مبرمجي PHP حتى المحترفين منهم. والسبب ببساطة يعود إلى قلّة استخدام “المعاملات الثنائية” في تطبيقات الويب.

توفر “المعاملات الثنائية” العديد من المزايا منها استخدامها في “التصاريح” (Permissions) كما في “error_reporting” في PHP. أيضاً إذا كنت تريد التقدّم لاختبار Zend Certificate يجب عليك الإلمام الكامل بالمعاملات الثنائية وطريقة عملها.

في هذه المقالة سأتطرق إلى المواضيع التالية:

  • نظام العدّ العشري (Decimal System).
  • نظام العدّ الثنائي (Binary System).
  • المعاملات الثنائية (Bitwise Operators) في PHP.
  • أمثلة واقعية عن استخدام “المعاملات الثنائية” في PHP.

ملاحظة 1: كما يقال “لا حاجة لاختراع العجلة مرّة أخرى”. لقد قمت بالاستعانة بكتيّب (رياضيات الحاسب من الأكاديمية العربية البريطانية للتعليم العالي) “بتصرّف” لتوضيح النظام الثنائي. الكتاب مجاني ويمكن للجميع تحميله من موقع الأكاديمية.

ملاحظة 2: هذه المقالة تختلف عن غيرها من المقالات، فهي تتطلب البعض من وقت ومزيد من التركيز.

النظام العشري (Decimal System)

نستخدم في حياتنا اليومية “النظام العشري” (Decimal Numbers) والمكوّن من 10 أرقام هي: 0 1 2 3 4 5 6 7 8 9 فإذا أردنا تمثيل العدد 159 سنقوم بما يلي:

(1 * 10 ^ 2) + (5 * 10 ^ 0) + (9 * 1 ^ 0)

كل ما علينا هو ضرب الرقم بقيمة الخانة (آحاد، عشرات، مئات). بمعنى:

  • واحد مضروب في عشرة (أس 2).
  • خمسة مضروبة في عشرة.
  • تسعة مضروبة في واحد.

إذا أردنا مواصلة العد كل ما علينا هو إضافة 1 ثم الرجوع إلى الرقم 0 ليصبح الرقم 10 وهكذا بالنسبة للأرقام البقية: 11,12,13,14,15 و…

لكل رقم في النظام العشري “مرتبة” خاصة به، وهي:

  • الآحاد، مثال: 1.
  • العشرات، مثال: 75.
  • المئات، 320.

إذا أردنا تمثيل الرقم 789 مثلاً نقوم بوضع الرقم في الخانة المخصّصة له:

الآحاد العشرات المئات
8 9 7

إذاً كل ما علينا في النظام العشري هو وضع الرقم في الحقل المخصص له، ويمكن توسعة الحقول للـ (آلاف، ملايين، مليارات) ولكن الأرقام ستبقى من 0 إلى 9 وكل ما علينا هو وضع الرقم في الحقل المخصص له (أحاد، عشرات، مئات و…).

أعتقد أن الطريقة واضحة للغاية، وهذا ما تعلّمناه في الدراسة الابتئداية :-).

لننتقل الآن إلى النظام الثنائي (Binary).

نظام العد الثنائي (Binary System)

يتم تمثيل الأرقام في النظام العشري طبقاً للخانة المتواجد فيها العدد (آحاد، عشرات، مئات) ويبدأ العد من 0 حتى 9، في كل خانة نقوم بوضع قيمة ما نضربها في قيمة الخانة ونجمع الناتج لنحصل على الرقم النهائي، مثال لتمثيل الرقم 875:

800 70 1
8 7 5

الأمر لا يختلف كثيراً في نظام العد الثنائي، إلا أنك لا تستخدم إلا الرقمان 0 و 1 لتحديد قيمة كل خانة، وقيمة كل خانة تختلف في تسلسلها عن قيم الخانات في نظام العد العشري، فهي تكون عبارة عن 1 ثم 2 ثم 4 ثم 8 وهكذا في كل مرة تضرب، الرقم 2 في العدد الأخير لتحصل على العدد التالي.

ويقوم الكمبيوتر بجميع عملياته باستخدام نظام العد الثنائي، لأنه يعطي كل خانة أحد قيمتين فقط إما 0 أو 1 وذلك عن طريق التمييز بين عمليتين فيزيائيتين تحدثان داخل الكمبيوتر هما توصيل التيار (1) وقطع التيار (0) وفي الأقراص الصلبة تخزن المعلومات في صورة (فيرومغناطيسية) وهي تميز أيضا بين حالتين فقط الأولى عندما يكون اتجاه قطب المغناطيس الصغير الموجب إلى الأعلى، والحالة الثانية هي الحالة المعاكسة، لهذا السبب فإن الكمبيوتر لا بد له من استخدام نظام العد الثنائي.

في الأعداد العشرية إذا قلنا أننا نستطيع كتابة 5 خانات فهذا يعني أننا نستطيع كتابة الأرقام من 0 إلى 99999 أي تفسير ذلك أننا نستطيع ترتيب الأرقام من 0 إلى 9 (عشرة أرقام) في خمس خانات فذلك يعني أننا نستطيع تغيير الأرقام وترتيبها للحصول على العديد من الاحتمالات، عدد هذا الاحتمالات هو: 10 لأن كل خانة تحتمل 10 احتمالات، وكل 10 × 10 × 10 × 10 × 10 احتمال منها يحتمل عشر احتمالات معه في الخانة المجاورة وهكذا حتى الخانة الأخيرة، وهذا يعني أننا نمتلك عدد من الاحتمالات يساوي 10 أس 5 أي عدد الأرقام في كل خانة أس عدد الخانات، ويكون الناتج هو 100000 احتمال كل منها يعبر عن رقم . وهذه الأرقام تبدأ من 0 إلى 99999 الأمر ينطبق هنا أيضاً على الأعداد الثنائية، فإذا قلنا أن عدد الخانات هو 5 فإن 32 = عدد الاحتمالات الكلية = عدد الاحتمالات في كل خانة أس عدد الخانات = 52 وهي 32 احتمال تعبر عن الأرقام من 0 إلى 31 ، ويسمى عدد الخانات بطول الرقم، فالمتغيرة أو العداد أو أي شيء طوله 5 يعني أنه يتكون من 5 خانات ثنائية.

تسمّى كل خانة من الخانات الثنائية بالـ (Bit) وكل 8 بت يساوي (1 Byte).

وكما قلنا إن النظام العشري يعتمد على أساس عشرة أرقام، أما الرقم الثنائي فيعتمد على رقمين فقط و هما صفر وواحد وبنفس الطريقة عند الانتهاء من الأرقام نضيف الرقم صفر ونزيد واحد:

111 110 101 100 11 10 1 0

نلاحظ أن النظام يتكون من رقمين فقط، صفر و واحد نبدأ بالصفر ثم واحد ثم نضيف واحد مكان الصفر ونضيف واحد بجوار الرقم عند انتهاء الأرقام (في حالتنا انتهاء الأرقام هما صفر وواحد) الرقم التالي 101100 في النظام الثنائي لا يلفظ بمائة وعشرة آلاف ومائة! بل يلفظ كالتالي: واحد صفر واحد واحد صفر صفر.

والقاعدة هي: عندما نصل إلى رقم صاحب الترتيب الذي يساوي أساس نظام العد وفي حالتنا هنا النظام الثنائي مثلاً نقوم بوضع الرقم صفر في الخانة الحالية ونضيف الرقم واحد في الجهة التالية له.

يطلق على النظام الثنائي اسم نظام الأساس أثنين (2) ويشار إليه بالأساس (2) لأنه يعتمد على رمزين اثنين فقط هما (0,1) ومراتب الخانات في النظام الثنائي من اليمين إلى اليسار تمثل قوى العدد (2).

التحويل من النظام الثنائي إلى العشري

في هذا القسم سأشرح أسهل طريقة للتحويل من النظام الثنائي (Binary System) إلى النظام العشري (Decimal System).

كلّ موقع ثنائي، يبدأ من اليمين ويعمل إلى اليسار، ونأخذ مصفوفة الأضعاف التالية:

128    64    32    16   8    4    2    1

بعد أن عرفنا مصفوفة الأضعاف، نأتي الآن إلى تحويل الرقم الثنائي 10011101 إلى رقم عشري.

  • نقوم بإدراج الرقم الثنائي ليقابل مصفوفة الأرقام الثنائية كما يلي:
128 64 32 16 8 4 2 1
1 0 0 1 1 1 0 1
  • نهمل الأرقام التي باللون الأحمر (الأصفار) ثم نجمع الأرقام المتبقية:

128 + 16 + 8 + 4 + 1 = 157

لاحظ أن العملية بسيطة جداً!

قم بتجربة الطريقة إلى أن تصبح ملماً بها.

مثال آخر:

64 32 16 8 4 2 1
1 0 0 1 1 1 1

نقوم بحذف الأرقام باللون الأحمر (الأصفار) لنظهر المجموع كما يلي:

64 + 8 + 4 + 2 +1 = 79

أليست العملية بسيطة؟ :-)

التحويل من العشري إلى الثنائي

سأستعرض أسهل طريقة للتحويل من العشري إلى الثنائي.

لنشرح طريقة تحويل الرقم 176 إلى الرقم الثنائي:

  • قم بإنشاء جدول من مضاعفات العدد 2 كما يلي:
128 64 32 16 8 4 2 1
  • إذا كان الرقم 176 أكبر من أو يساوي الرقم 128. نقوم بطرح 128 من 176 ونخزن الباقي لدينا. ونضع رقم 1 أسفل 128 (في الجدول) كما يلي:
128 64 32 16 8 4 2 1
1
  • الآن لدينا الرقم 48 (ناتج الطرح). هل 48 أكبر من أو تساوي 64؟ كلا… إذاً نضع 0 أسفل 64 في الجدول. ونحتفظ بالرقم 48.
  • هل 48 أكبر من أو يساوي 32؟ نعم.. إذاً، نطرح 32 من 48. نحتفظ بالباقي (16). ونضع 1 أسفل الرقم 32.
  • هل 16 أكبر من أو يساوي 16؟ نعم.. إذاً، نطرح 16 من 16. نحتفظ بالباقي (0). ونضع 1 أسفل الرقم 16.
  • نضع 0 أسفل كل من 8 و 4 و 2 و 1 لأن (0) لا يساوي وليس أكبر من هذه الأرقام.

الناتج:

128 64 32 16 8 4 2 1
1 0 1 1 0 0 0 0

الآن كل ما عليك هو تجربة الطريقة لتصبح ملمّاً بها.

بعد أن فهمنا ماذا تعني الأرقام الثنائية نأتي الآن إلى لغة PHP لنعرف طريقة التعامل مع الأرقام الثنائية في PHP.

المعاملات الثنائية (Binary Operators) في لغة PHP

المعامل & (AND)

يقوم المعامل & (And) بمقارنة قيمتين ثنائيتين ثم يعيد القيمة التي تتشارك فيها القيمتين كرقم عشري.

قم بتجربة الشيفرة التالية:

1
2
3
4
$a    =    9;
$b    =    10;
 
echo $a & $b; //Output: 8

كما رأيت، ستحصل على الرقم 8 عند تنفيذ هذه الشيفرة، لكن لماذا 8؟ لأن 8 هو البت (Bit) الوحيد المشترك بين القيمتين a و b. ولكي تفهم ماذا يعني البت المشترك أنظر إلى الجدول التالي:

1 Byte (8 bits )
Place Value
128
64
32
16
8
4
2
1
$a
0
0
0
0
1
0
0
1
=
9
$b
0
0
0
0
1
0
1
0
=
10
  • يقوم PHP بتحويل كل من الرقم 9 و 10 إلى النظام الثنائي.
  • بعد التحويل يقوم بمقارنة البتات (Bits) المشتركة ويعيد النتيجة.
  • كما ترى في الجدول أعلاه، البت 8 (Bit 8) هو الوحيد المشترك بين القيمتين a و b.

ملاحظة: يتم إعادة القيم المشتركة مجموعة إذا كانت موجبة أما القيم السالبة فلن يتم إعادتها.

مثال آخر:

1
2
3
4
$a    =    232;
$b    =    155;
 
echo $a & $b; //Output: 136

حسناً.. لنرى لماذا أعيدت القيمة 136؟

1 Byte (8 bits )
Place Value
128
64
32
16
8
4
2
1
$a
1
1 1
0
1
0
0
0
=
232
$b 1
0
0
1
1
0
1
1
=
10

كما هو موضّح، يشترك a و b مع 8 و 128 إذاً المجموع: 136.

ملاحظة: تتساءل الآن ما الفائدة من هذه المقارنات؟ أو ما الفائدة من استخدام المعاملات الثنائية أصلاً؟ في نهاية هذه المقالة سأذكر لك مثالاً واقعياً حول استخدام المعاملات الثنائية، كل ما عليك الآن هو فهم المعاملات فقط.

المعامل | (OR)

المعامل | يعمل على مقارنة البتات (Bits) بطريقة (أو) وليس (و) كما في &. بمعنى آخر: يقوم بإعادة القيم الموجبة إذا توفرت في a (أو) b. لنأخذ المثال التالي:

1
2
3
$a =     9;
$b =     10;
echo $a | $b;
1 Byte (8 bits )
Place Value 128 64 32 16 8 4 2 1
$a 0 0 0 0 1 0 0 1 = 9
$b 0 0 0 0 1 0 1 0 = 10

كما هو موضّح باللون الأحمر، سيتم إظهار القيمة 11. لأن القيمة الموجبة (1) موجودة في الحقل: 8 و 2 و 1 إذاً المجموع = 11.

المعامل ^ (XOR)

المعامل ^ لا يختلف عن المعامل | كثيراً، فكلاهما يجريان نفس العملية (مقارنة القيم المشتركة بصيغة “أو” OR) إلا أن ^ يعيد القيم المشتركة من جانب واحد فقط ولن يعيد القيم المشتركة من الجانبين. أي أنه كالمعامل المنطقي XOR تماماً ولكن للعمليات الثنائية فقط، مثال:

1
2
3
$a =     9;
$b =     10;
echo $a ^ $b;
1 Byte (8 bits )
Place Value
128
64
32
16
8
4
2
1
$a
0
0
0
0
1
0
0
1
=
9
$b
0
0
0
0
1
0
1
0
=
10

سيعيد القيمة 3. أي (1 + 2). ما يقوم به ^ هو التحقق من اشتراك إحدى القيم فقط، لاحظ معي (إحدى القيم) بمعنى لو اشتركت قيمتين (كما في 8) فلن يتم إعادتها.

المعامل ~ (NOT)

يقوم المعامل ~ بالتحقق أيٌّ من القيمتين غير مشتركة. بمعنى آخر: أي من قيم a غير موجودة في b. وبمعنى بسيط آخر: أي من القيم (الموجبة) في a غير موجودة في b.

لنأخذ المثال التالي ونرى ماذا سيعيد:

1
2
3
4
$a =     9;
$b =     10;
 
echo $a & ~$b;

سيتم إعادة القيمة 1. لكن لماذا 1؟ دعنا نرى في الجدول التالي (القيم الموجبة الموجودة في a وغير موجودة في b).

1 Byte (8 bits )
Place Value
128
64
32
16
8
4
2
1
$a
0
0
0
0
1
0
0
1
=
9
$b
0
0
0
0
1
0
1
0
=
10

كما نرى القيمة (1) هي القيمة الوحيدة (الموجبة) الموجودة في a ولكن غير موجودة في b.

ملاحظة 1: في الخانة 2 نرى أن القيمة الموجبة موجودة في b لكن غير موجودة في a. وهنا لا يعيد المعامل شيئاً لأننا نريد القيم الموجودة في a وغير موجودة في b وليس العكس.

ملاحظة 2: لاحظ معي أننا استخدمنا المعامل & يليه المعامل ~ وهذا يعني أن المعامل ~ لا يمكن استخدامه وحده! لأنه ببساطة لا يأتي بنتيجة.

المعامل >> (Shift)

يقوم هذا العامل بمضاعفة القيمة المسندة إليه.

مثال:

1
2
3
$a = 8;
 
echo $a << 2;

المثال أعلاه سيعيد الرقم 32 بمعنى (8 * 2^2).

مثال آخر:

1
2
3
$a =     16;
 
echo $a << 2; //Output: 64

المعامل << (Shift Right)

يقوم هذا المعامل بقسمة القيمة المسندة إليه لمرتين.

مثال:

1
2
3
$a =     16;
 
echo $a >> 2;

المثال أعلاه يعيد الرقم 4 لأنه تم قسمة الرقم 16 إلى 2 (لمرتين).

الطريقة واضحة جداً، كل ما سيقوم به هو قسمة a إلى الرقم المسند إليه (في حالتنا 2).

مثال واقعي: نظام تصاريح باستخدام المعاملات الثنائية

يعمل نظام التصاريح (Permissions System) على إعطاء صلاحيات (Privileges) معيّنة لشخص ما.

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

دعنا نتصوّر أن لديك 4 صلاحيات للأعضاء في موقعك هي:

  • إرسال مقالة.
  • تعديل مقالة.
  • حذف مقالة.
  • إرسال تعليق.

في الشيفرة التقليدية ستقوم بتجهيز 4 حقول (Fields) في قاعدة البيانات لتستوعب الصلاحيات الأربعة أعلاه. وهذا شيء يمكن السيطرة عليه. لكن ماذا لو كان لديك أكثر من 4 حقول للصلاحيات؟ كأن تكون لديك 10 أو حتى 20 صلاحية أو أكثر؟ فهل ستقوم بإضافة 10 أو 20 حقل في جدول قاعدة البيانات؟ إنها عملية صعبة ومرهقة.

بدلاً من هذه العملية المرهقة، يمكننا تحديد حقل واحد فقط للصلاحيات! نعم حقل واحد فقط لا غير. هذا الحقل سيكون من النوع الثنائي (Binary). لنرى الآن كيف يمكننا فعل ذلك:

لنبدأ بالخطوات كما يلي:

  • الخطوة الأولى: تحديد الصلاحيات، في هذه الخطوة نقوم بتخزين الصلاحيات المستخدمة في الموقع في مصفوفة (Array)، مثال:
1
2
3
4
5
6
$perms = array(
'can_post'             => 1,
'can_comment'         => 2,
'can_edit'             => 4,
'can_delete'         => 8
);

ملاحظة: استخدم العد الثنائي (أساس 2) لإضافة أي صلاحية أخرى، كـ 16,32,64 و…

  • الخطوة الثانية: تحديد الصلاحيات لعضو معين. يمكننا تحديد الصلاحيات لعضو ما عن طريق الشيفرة التالية:
$user_perms  = $perms['can_post'] | $perms['can_comment'] | $perms['can_edit'];

تقوم أساس معاملاتنا الثنائية بناء على الجدول التالي (جدول التصاريح):



128

64

32

16

8

4

2

1
can_post 0 0 0 0 0 0 0 1
can_comment 0 0 0 0 0 0 1 0
can_delete 0 0 0 0 1 0 0 0
can_edit 0 0 0 0 0 1 0 0

بعد إجراء الشيفرة أعلاه، سيحمل المتغير user_perms القيمة 7. لكن لماذا 7؟ الجواب بسيط وهو: مجموع الأرقام في (can_post, can_edit, can_comment) هو 7 (عشري). ثنائياً يكون كما يلي:

user_perms Binary Table

128

64

32

16

8

4

2

1
user_perms 0 0 0 0 0 1 1 1

كما نرى في الجدول أعلاه، تم تحديد 3 صلاحيات من أصل 4. يمكننا التحقق من صلاحيات عضو معين باستخدام الشيفرة التالية:

1
2
3
4
5
6
7
8
if ( $user_perms & $perms['can_delete'] )
{
echo 'You can post an article!';
}
else
{
echo 'You can\'t post!';
}

استخدم الشيفرة التالية لإضافة صلاحيات:

$user_perms |= $perms['can_delete'];

ولإلغاء صلاحية استخدم:

$user_perms ^= $perms['can_delete'];

الآن ربما تتساءل كيف أمكننا استخدام المعامل ^ لحذف التصريح can_delete، وللإجابة على هذا السؤال أنظر إلى الشيفرة أدناه:

1
2
3
4
$a    =    7;
$b    =    2;
 
echo $a ^ $b;

ستقوم هذه الشيفرة بإرجاع الرقم 5. لكن لماذا 5؟ لأنه مجموع الرقم الذي لا تشترك فيه البتات (Bits) من جانبين. إذاً من المنطقي أن لا يُذكر الرقم 4 كون البتات (Bits) متساوية، ولمزيد من التوضيح أنظر إلى الجدول التالي:


Place Value
128
64
32
16
8
4
2
1
$a
0
0
0
0
0
1
1
1
=
9
$b
0
0
0
0
0
0
1
0
=
10

كما هو موضّح، يشترك البت رقم 2 في a مع b لذا سيتم تجاهله.

الخلاصة

تطرّقت في هذه المقالة إلى أحد أهم مواضيع البرمجة، أتمنى أن تكون مقالة مفيدة للجميع.

إذا كانت لديكم أي أسئلة أو استفسارات فأرجو ذكرها في التعليقات.

علماً أن هذه المقالة ستأخذ منكم المزيد من الوقت والجهد لفهمها بالكامل.

user_perms Binary Table

128
1

عدد التعليقات 4

تعليقك على الموضوع

خلاصات التعليقات RSS   التعقيبات

جميع الحقوق محفوظة لـ مدونة أحمد المياحي © 2017