انتقل إلى المحتوى
Topos Uranos

ملخص

العدد الصحيح، في نظر المعالج، صفّ من البتات، وتقدّم C++ عوامل تعمل على هذا الصفّ عمودًا عمودًا: & و| و^ و~، والإزاحتين << و>>. يعلّم هذا الدرس حسابها باليد، واستعمالها لحفظ عدة أعلام في عدد صحيح واحد بتقنية الأقنعة، وتمييز الإزاحات التي لا يعرّفها المعيار. ثم يشرح لماذا تساوي 2 ^ 3 القيمة 1 لا 8، ولماذا تتطلّب العوامل البتّية أقواسًا. ويعرض النصف الثاني المكتبة الرياضية: دوال <cmath>، وثوابت <numbers>، والقاسم المشترك الأكبر والمضاعف المشترك الأصغر من <numeric>، والدوال البتّية في <bit>، مع مبدأ يصحّ في أي لغة: ما تقدّمه المكتبة صحيحًا ومختبَرًا لا تُعاد كتابته.

أهداف التعلم

  1. حساب نتيجة & و| و^ و~ والإزاحات باليد على أعداد صحيحة صغيرة.
  2. التمييز بين الإزاحات المعرَّفة (ومنها إزاحات الأعداد السالبة، منذ C++20) والإزاحات التي هي سلوك غير معرَّف، والتنبؤ بنتيجة الأولى.
  3. استعمال الأقنعة للاستعلام عن بتّ وتشغيله وإطفائه وقلبه.
  4. شرح لماذا تساوي 2 ^ 3 القيمة 1 لا 8، والاختيار بين std::pow والإزاحة بحسب الحالة.
  5. استعمال دوال <cmath> (std::sqrt وstd::pow وstd::abs وstd::hypot والدوال المثلثية) وثوابت <numbers>، مثل std::numbers::pi، في الصيغ.

العوامل البتّية

بيّن الدرس الأعداد الصحيحة وتمثيلها أن int صفّ من البتات، 32 بتًا في أنظمة الدورة، وأن قيمته مجموع أوزان المواضع التي فيها 1. غير أن البرنامج لم يعامل العدد الصحيح حتى الآن إلا بوصفه عددًا: جمعه وضربه وقسمه. لكنّ الصفّ نفسه يقبل قراءة أخرى، بوصفه مجموعة من 32 جوابًا بنعم أو لا، ولهذه القراءة تملك C++ أربعة عوامل تعمل على البتات واحدًا واحدًا، دون حَمْل بين المواضع.

تُرقَّم البتات من اليمين ابتداءً من 0: البتّ kk هو الذي وزنه 2k2^k. فمثلًا في 1212، الذي يُكتب بالثنائي 1100، البتّان 2 و3 مُشغَّلان، والبتّان 0 و1 مُطفآن.

العوامل هي هذه. «و» البتّية، a & b، فيها 1 في كل موضع يكون فيه لكلٍّ من a وb الرقم 1. و«أو» البتّية، a | b، فيها 1 حيث يكون 1 لواحد منهما على الأقل. و«أو» الحصرية، a ^ b، فيها 1 حيث يكون 1 لواحد منهما بالضبط، أي حيث يختلف البتّان. والنفي البتّي، ~a، له معامَل واحد ويقلب كل بتّ. وجداول الثلاثة الأولى، لزوج من البتات pp وqq، هي جداول العطف والفصل والفصل الحصري:

pqp&qp∣qp^q11110100110101100000\begin{array}{cc|ccc} p & q & p \mathbin{\&} q & p \mid q & p \mathbin{\hat{}} q \\ \hline 1 & 1 & 1 & 1 & 0 \\ 1 & 0 & 0 & 1 & 1 \\ 0 & 1 & 0 & 1 & 1 \\ 0 & 0 & 0 & 0 & 0 \end{array}

من تعلّم في دورة «المنطق الرياضي 1» سيتعرّف هنا الروابط القضوية، مع 1 للصدق و0 للكذب: فالعامل البتّي ليس إلا رابطًا يُطبَّق على جميع الأعمدة في آن واحد. ومن ذلك تلزم طريقة الحساب باليد: يُكتب العددان بالثنائي، أحدهما فوق الآخر، محاذَيين من اليمين، ويُطبَّق الجدول على كل عمود دون الالتفات إلى جيرانه.

مثالاثنا عشر وعشرة، عمودًا عمودًا

للعددين 12=12 = 1100 و10=10 = 1010 خاصية أن أعمدتهما الأربعة تمرّ على التركيبات الأربع لبتّين؛ ولذلك يصلحان لرؤية الجداول الثلاثة معًا.

البرهان

  1. المعطيات: 1100 هو 12 و1010 هو 10. والأعمدة، من اليسار إلى اليمين، فيها الأزواج (1,1)(1, 1) و(1,0)(1, 0) و(0,1)(0, 1) و(0,0)(0, 0).

  2. العمود الأول من 12 & 10: كلا البتّين 1، فتعطي «و» القيمة 1.

  3. العمود الثاني: 1 و0. يكفي 0 واحد لتعطي «و» القيمة 0.

  4. العمود الثالث: 0 و1؛ فتعطي «و» القيمة 0 مرة أخرى.

  5. العمود الرابع: 0 و0 يعطيان 0. وصفّ النتيجة، 1000، هو 8: 12 & 10 تساوي 8.

  6. تشغّل «أو» كل عمود فيه 1 واحد على الأقل: 1110، أي أن 12 | 10 تساوي 14.

  7. وتشغّل «أو» الحصرية الأعمدة التي يختلف بتّاها فقط: 0110، و12 ^ 10 تساوي 6.

البرنامج الآتي يتحقق من الشكل. وحقل التنسيق {:04b}، الذي قُدِّم في الدرس عن الأعداد الصحيحة، يكتب القيمة بالثنائي بأربعة أرقام، مكمَّلة بالأصفار.

برنامج بلغة C++
#include <print>​int main(){    const int a{0b1100};    const int b{0b1010};    std::println("a     = {:04b} = {}", a, a);    std::println("b     = {:04b} = {}", b, b);    std::println("a & b = {:04b} = {}", a & b, a & b);    std::println("a | b = {:04b} = {}", a | b, a | b);    std::println("a ^ b = {:04b} = {}", a ^ b, a ^ b);    std::println("~a    = {}", ~a);}
المخرجات
a     = 1100 = 12b     = 1010 = 10a & b = 1000 = 8a | b = 1110 = 14a ^ b = 0110 = 6~a    = -13

السطر الأخير يستحق شرحًا. النفي يقلب البتات الاثنين والثلاثين في a، لا الأربعة المكتوبة فحسب: فالأصفار الثمانية والعشرون التي على اليسار تصير آحادًا، وint الذي يبدأ بـ 1 سالب في المتمِّم الثنائي. لكنّ نظير العدد في المتمِّم الثنائي يُحصَل عليه بقلب بتاته وإضافة 1، أي −x=∼x+1-x = {\sim}x + 1؛ إذن ∼x=−x−1{\sim}x = -x - 1، و~12 تساوي −13-13. فالنفي البتّي على عدد صحيح ذي إشارة يعطي عددًا قلّما يهمّ بوصفه عددًا: إنما يهمّ بوصفه نمطًا للبتات، ولذلك تُعالَج البتات بالأفضلية في أنواع بلا إشارة، كما سيتبيّن في القسم الخاص بالأقنعة.

تنبيه بشأن الأنواع الصغيرة. قبل إجراء العملية تحوّل C++ إلى int كل معامَل من نوع صحيح أضيق، مثل std::uint8_t؛ وهذه هي الترقية التي وصفها الدرس العمليات الحسابية للعمليات الحسابية، وهي تسري هنا أيضًا. ومن ذلك يلزم أن ~flags، حين يكون flags من النوع std::uint8_t وقيمته 5، ليس البايت 11111010، بل int قيمته −6-6، تتضمّن بتاته الاثنان والثلاثون 24 آحادًا مضافة على اليسار؛ ولا يُقتطع إلى ثمانية بتات ويُستعاد البايت المنتظَر، 250، إلا عند حفظه من جديد في std::uint8_t.

ينبغي ألّا تُخلط العوامل البتّية بالعوامل المنطقية في الدرس العلاقات والمنطق. 6 && 3 هي true: كلا المعامَلين مختلف عن الصفر، والنتيجة من النوع bool. أما 6 & 3 فعدد صحيح، هو الذي تكوّنه الأعمدة التي فيها 1 لكليهما. وفوق ذلك، تُحسب && حسابًا مختصرًا، بينما تحسب & المعامَلين دائمًا.