على مستوى العتاد، لا يمكن للترانزستورات إلا أن تكون في حالة تشغيل أو إيقاف: وهما حالتان جرت العادة على تمثيلهما بـ 1 و0.
وهذه هي binary digits، وتُختصر بـ bits.
وتكتسب معرفة bits وbinary أهمية خاصة لدى مبرمجي الأنظمة الذين يعملون بلغات منخفضة المستوى.
غير أن الأولوية البرمجية طوال معظم تاريخ الحوسبة كانت إيجاد طرق متزايدة التطور للتجريد من هذه الحقيقة الثنائية.
في Python (وكثير من لغات البرمجة عالية المستوى الأخرى)، نتعامل مع int وfloat وstring وغيرها من الأنواع المعرَّفة، وصولًا إلى صيغ الصوت والفيديو.
وندع التفاصيل الداخلية لـ Python تتولى (في النهاية) ترجمة كل شيء إلى بتات.
ومع ذلك، قد يكون لاستخدام العوامل على مستوى البت والعمليات على مستوى البت في بعض الأحيان مزايا كبيرة في السرعة وكفاءة الذاكرة، حتى في لغة عالية المستوى مثل Python.
كما هو متوقع، تتعامل Python مع المستخدم باستخدام الأعداد العشرية، لكن يمكن للمبرمج تجاوز هذا الافتراض.
بل إن Python تقبل بسهولة عددًا من نوع int بصيغة binary أو hexadecimal أو octal، وتُجري بينها عمليات حسابية بكل سرور.
لمزيد من التفاصيل، يمكنك مراجعة مفهوم binary-octal-hexadecimal.
تُدخَل الأعداد الثنائية بسابقة 0b، تمامًا كما يمكن استخدام 0x للنظام الست عشري (فالأعداد الست عشرية طريقة موجزة لتمثيل مجموعات من 4 بتات)، ويمكن استخدام oct للأعداد الثمانية.
هناك طرق متعددة لتحويل الأعداد الصحيحة إلى سلاسل نصية ثنائية، تختلف في ما إذا كانت تتضمن السابقة 0b وفي ما إذا كانت تدعم الحشو من اليسار بالأصفار:
# Binary entry.
>>> 0b10111
23
# Converting an int display to binary string, with prefix.
>>> bin(23)
'0b10111'
>>> number = 23
# Binary without prefix, padded to 8 digits.
>>> format(number, '08b')
'00010111'
# Same format, but using an f-string.
>>> f"{number} in decimal is {number:08b} in binary and {number:x} in hex"
'23 in decimal is 00010111 in binary and 17 in hex'
Bitwise Logicفي مفهوم bools، تحدثنا عن العوامل المنطقية and وor وnot المستخدمة مع القيم المنطقية (True وFalse).
وتنطبق القواعد المنطقية نفسها عند التعامل مع البتات.
غير أن النظائر على مستوى البت للعوامل المنطقية & (and) و| (or) و~ (not) و^ (XOR)، تُطبَّق على كل بت في التمثيل الثنائي، حيث يُعامَل 1 كما لو كان True («تشغيل») و0 كما لو كان False («إيقاف»).
وقد يوضّح مثال على العامل & على مستوى البت هذه الفكرة أكثر:
>>> x = 0b01100110
>>> y = 0b00101010
>>> format(x & y, '08b')
'00100010'
لا تُضبط على 1 في الناتج إلا المواضع التي تحتوي على 1 في كلا العددين المُدخَلين.
كثيرًا ما يُستخدم العامل & على مستوى البت وسيلةً لعزل بتات مفردة ضمن مجموعة مضغوطة من القيم True/False، مثل الإعدادات التي يمكن للمستخدم ضبطها في تطبيق ما.
وهذا يُمكّن قيمة كل بت على حدة من التحكم في منطق البرنامج:
>>> number = 0b0110
>>> number & 0b0001 > 0
False
>>> number & 0b0010 > 0
True
أما في حالة العامل | (أو) على مستوى البت، فيُضبط 1 في الناتج إذا وُجد 1 في أيٍّ من المُدخَلين:
>>> x = 0b01100110
>>> y = 0b00101010
>>> format(x | y, '08b')
'01101110'
أما العامل ^ الذي يمثّل أو الحصرية (xor)، فيُضبط 1 إذا ظهر في أيٍّ من المُدخَلين وليس في كليهما معًا.
وقد يبدو هذا الرمز مألوفًا من مفهوم sets، حيث يُستخدم للـ_فرق المتماثل_ بين المجموعات set، وهو نفسه تطبيق xor على المجموعات.
وإذا بدا لك xor ^ غريبًا، فاعلم أنه العملية الأكثر شيوعًا في التشفير بفارق كبير.
>>> x = 0b01100110
>>> y = 0b00101010
>>> format(x ^ y, '08b')
'01001100'
وأخيرًا، هناك العامل ~ (رمز tilde)، وهو not على مستوى البت يأخذ مُدخَلًا واحدًا و_يقلب كل البتات_، وقد لا يكون هذا هو الناتج الذي كنت تتوقعه!
فتتحول كل 1 في التمثيل إلى 0، والعكس صحيح.
انظر القسم أدناه لمعرفة التفاصيل.
في التمثيل العشري، نميّز بين الأعداد الموجبة والسالبة بوضع علامة + أو - على يسار الأرقام.
وقد ثبت أن استخدام هذين الرمزين على المستوى الثنائي غير فعّال في الحوسبة الرقمية، وأثار مشكلة أن +0 ليس مثل -0.
وبدلًا من استخدام - و+، تستخدم جميع الحواسيب الحديثة تمثيل two's complement للأعداد السالبة، وصولًا إلى مستوى شريحة السيليكون.
وهذا يعني أن كل البتات تُقلب، ويُ_فسَّر العدد على أنه سالب_ إذا كانت البتة الواقعة في أقصى اليسار (وتُسمى أيضًا «البتة الأكثر أهمية»، أو MSB) تساوي 1.
أما الأعداد الموجبة فبتتها الأكثر أهمية تساوي 0.
ولهذا التمثيل ميزة أنه يحتوي على نسخة واحدة فقط من الصفر، فلا يضطر المبرمج إلى التعامل مع -0 و+0.
وتضيف هذه الطريقة في تمثيل الأعداد السالبة والموجبة تعقيدًا في Python: فلا توجد في اللغة الأساسية مفاهيم للأعداد الصحيحة محدودة الحجم مثل int32 أو int64.
وفي Python «الحديثة»، تكون قيم int بحجم غير محدود (لا تحدّه إلا سعة العتاد)، ويكون للعدد السالب أو المقلوب بتات عدد لا نهائي (نظريًا) من قيم 1 على اليسار، تمامًا كما يكون للعدد الموجب عدد غير محدود من قيم 0.
وهذا يجعل من الصعب تقديم مثال مفيد على bitwise not:
>>> x = 0b01100110
>>> format(x, '08b')
'01100110'
# This is a negative binary (not twos-complement display).
>>> format(~x, '08b')
'-1100111'
# Decimal representation.
>>> x
102
# Using the Bitwise not, with an unintuitive result.
>>> ~x
-103
وهذا ليس هو 0b10011001 الذي قد نراه في لغات ذات أعداد صحيحة ثابتة الحجم.
لا يعمل العامل ~ كما هو متوقع إلا مع أنواع البايت أو الأعداد الصحيحة غير المُوقَّعة، أو مع أنواع الأعداد الصحيحة ثابتة الحجم.
وهذه الأنواع العددية مدعومة في حزم خارجية مثل NumPy وpandas وsympy، لكن ليس في نواة Python.
عمليًا، كثيرًا ما يستخدم مبرمجو Python العوامل & و| و^ وعوامل الإزاحة الموصوفة أدناه مع الأعداد الموجبة فقط.
والعمليات على مستوى البت مع الأعداد السالبة أقل شيوعًا بكثير.
من الأساليب المتبعة إضافة 2**32 (or 1 << 32) إلى قيمة سالبة لجعل int غير مُوقَّع، لكن إدارته تصبح صعبة.
وثمة استراتيجية أخرى هي العمل مع وحدة ctypes واستخدام أنواع أعداد صحيحة على نمط لغة C، لكن هذا غير عملي بالقدر نفسه.
Shift operatorsيحرّك عامل الإزاحة إلى اليسار x << y كل البتات في x بمقدار y موضعًا نحو اليسار، ويملأ الفجوات الجديدة بالأصفار.
لاحظ أن هذا مطابق حسابيًا لضرب عدد في (2**y).
أما عامل الإزاحة إلى اليمين x >> y فيفعل العكس.
وهذا مطابق حسابيًا للقسمة الصحيحة x // (2**y).
تذكّر القسم السابق عن الأعداد السالبة والأخطاء الشائعة عند إزاحتها في Python.
>>> x = 8
>>> format(x, '08b')
'00001000'
# A left bit shift.
>>> x << 2
32
>>> format(x << 2, '08b')
'00100000'
# A right bit shift.
>>> format(x >> 2, '08b')
'00000010'