ترتبط أرقام النظام الثنائي في النهاية ارتباطًا مباشرًا بالترانزستورات في المعالج أو الذاكرة، وبما إذا كان كل ترانزستور «مُشغَّلًا» أم «مُطفأً».
التلاعب منخفض المستوى، المعروف بشكل غير رسمي باسم «bit-twiddling»، مهم بشكل خاص في لغات النظم.
اللغات عالية المستوى مثل Julia عادةً ما تخفي عنك معظم هذه التفاصيل. ومع ذلك، تتوفر مجموعة كاملة من عمليات مستوى البت في اللغة الأساسية.
ملاحظة: لرؤية مخرجات ثنائية مقروءة للبشر في REPL، تحتاج معظم الأمثلة أدناه إلى تغليفها داخل دالة bitstring().
هذا يشتّت الانتباه بصريًا، لذا أُزيلت معظم المواضع التي تظهر فيها هذه الدالة.
يمكن تمثيل أنواع الأعداد الصحيحة، بإشارة أو بدونها، على هيئة سلسلة نصية من 1 و0.
julia> bitstring(UInt8(5))
"00000101"
إزاحة البتات تنقل كل شيء إلى اليسار أو اليمين بعدد محدد من المواضع.
مع أنواع UInt، تسقط بعض البتات من أحد الطرفين، ويُحشى الطرف الآخر بالأصفار:
julia> ux::UInt8 = 5
5
julia> bitstring(ux)
"00000101"
julia> ux << 2 # left by 2
"00010100"
julia> ux >> 1 # right by 1
"00000010"
كل إزاحة إلى اليسار تُضاعف القيمة، وكل إزاحة إلى اليمين تنصّفها (مع مراعاة الاقتطاع). يظهر ذلك بوضوح أكبر في التمثيل العشري:
julia> 3 << 2
12
julia> 24 >> 3
3
هذا النوع من إزاحة البتات أسرع بكثير من الحساب «التقليدي»، ما يجعل هذه التقنية شائعة جدًا في البرمجة منخفضة المستوى.
مع الأعداد الصحيحة ذات الإشارة، علينا أن نكون أكثر حذرًا.
الإزاحات إلى اليسار بسيطة نسبيًا:
julia> sx = Int8(5)
5
julia> sx # positive integer
"00000101"
julia> sx << 2
"00010100"
julia> -sx # negative integer
"11111011"
julia> -sx << 2
"11101100"
إذن، إزاحة الأعداد الصحيحة الموجبة ذات الإشارة إلى اليسار هي نفسها كما مع الأعداد الصحيحة بدون إشارة.
تُخزَّن القيم السالبة بصيغة المتمّم الثنائي، ما يعني أن البت الأقصى يسارًا يساوي 1. لا مشكلة في الإزاحة إلى اليسار، لكن عند الإزاحة إلى اليمين، كيف نحشي البتات اليسرى؟
julia> sx >> 2 # simple for positive values!
"00000001"
julia> -sx # negative integer
"11111011"
julia> -sx >> 2 # pad with repeated sign bit
"11111110"
julia> -sx >>> 2 # pad with 0
"00111110"
يُجري العامل >> إزاحة حسابية، محافظًا على بت الإشارة.
يُجري العامل >>> إزاحة منطقية، فيحشي بالأصفار كما لو كان العدد بدون إشارة.
رأينا في مفهوم سابق أن العوامل && (و)، و|| (أو)، و! (ليس) تُستخدم مع القيم المنطقية.
توجد عوامل مكافئة هي & (و البتية)، و| (أو البتية)، و~ (وهي تِلدَة، النفي البتي) لمقارنة البتات في عددين صحيحين.
julia> 0b1011 & 0b0010 # bit is 1 in both numbers
"00000010"
julia> 0b1011 | 0b0010 # bit is 1 in at least one number
"00001011"
julia> ~0b1011 # flip all bits
"11110100"
julia> xor(0b1011, 0b0010) # bit is 1 in exactly one number, not both
"00001001"
هنا xor() هي «أو الحصرية»، وتُستخدم كدالة.
أرسل إليك صديقك للتو رسالة تحتوي على سرّ مهم. ولأنه لم يشأ أن يسهّل على الآخرين قراءتها، جرى تشفير الرسالة عبر عدة عمليات للتلاعب بالبتات. ستحتاج إلى كتابة الدوال للمساعدة في فك تشفير الرسالة.
الخطوة الأولى في فك تشفير الرسالة هي إلغاء الإزاحة التي حدثت أثناء عملية التشفير بإعادة إزاحة البتات إلى اليمين. ستأتي خطوات لاحقة في عملية فك التشفير تفترض إدخال أصفار من الجهة اليسرى.
نفّذ الدالة shift_back() التي تأخذ قيمة وعدد المواضع المراد الإزاحة فيها وتقوم بالإزاحة.
shift_back(0b1001, 2) # => 0b0010
بعد ذلك، هناك بعض البتات التي يجب ضبطها على 1.
نفّذ الدالة set_bits() التي تأخذ قيمة وقناعًا وتُرجع نتيجة ضبط البتات في القيمة على 1.
يجب ضبط البِت من value على 1 حيث تكون البِتة في mask أيضًا 1.
أما بقية البتات فتبقى دون تغيير.
set_bits(0b0110, 0b0101) # => 0b0111
تُقلب بعض البتات أثناء التشفير. وسيلزم قلبها مرة أخرى لفك تشفير الرسالة.
نفّذ الدالة flip_bits() التي تأخذ قيمة وقناعًا.
يحدّد القناع البتات التي يجب قلبها في القيمة.
إذا كانت البِتة تساوي 1 في mask، تُقلب البِتة في value.
أما بقية البتات فتبقى دون تغيير.
flip_bits(0b1100, 0b0101) # => 0b1001
وأخيرًا، هناك أيضًا بتات معيّنة يفك تشفيرها دائمًا إلى 0.
نفّذ الدالة clear_bits() التي تأخذ قيمة وقناعًا.
يجب ضبط البتات في value على 0 حيث تكون البِتة في mask تساوي 1.
أما بقية البتات فتبقى دون تغيير.
clear_bits(0b0110, 0b0101) # => 0b0010
سجّل في Exercism لتتعلّم وتتقن Julia عبر 35 مفهومًا128 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.