হার্ডওয়্যারের একেবারে নিচের স্তরে ট্রানজিস্টর কেবল চালু বা বন্ধ থাকতে পারে: এই দুটি অবস্থাকে আমরা চিরাচরিতভাবে 1 আর 0 দিয়ে প্রকাশ করি।
এগুলোই binary digits, সংক্ষেপে bits।
bits আর binary সম্পর্কে ধারণা থাকা বিশেষভাবে জরুরি নিম্নস্তরের ভাষায় কাজ করা সিস্টেম প্রোগ্রামারদের জন্য।
তবে কম্পিউটিংয়ের ইতিহাসের বড় একটা অংশ জুড়ে প্রোগ্রামিংয়ের অগ্রাধিকার ছিল এই বাইনারি বাস্তবতাকে ক্রমে আরও পরিশীলিত উপায়ে অ্যাবস্ট্রাক্ট করে ফেলা।
Python-এ (এবং আরও অনেক উচ্চস্তরের প্রোগ্রামিং ভাষায়) আমরা int, float, string আর সংজ্ঞায়িত অন্যান্য টাইপ নিয়ে কাজ করি, এমনকি অডিও ও ভিডিও ফরম্যাট পর্যন্ত।
সবকিছুকে (একসময়) বিটে রূপান্তরের দায়িত্ব আমরা Python-এর ভেতরের ব্যবস্থার হাতে ছেড়ে দিই।
তবুও, বিটওয়াইজ অপারেটর ও বিটওয়াইজ অপারেশন ব্যবহার করলে Python-এর মতো উচ্চস্তরের ভাষাতেও গতি ও মেমরি দক্ষতায় মাঝেমধ্যে উল্লেখযোগ্য সুবিধা মিলতে পারে।
স্বাভাবিকভাবেই Python ব্যবহারকারীর সাথে দশমিক সংখ্যা দিয়ে যোগাযোগ করে, তবে একজন প্রোগ্রামার চাইলে এই ডিফল্ট বদলে দিতে পারেন।
আসলে Python binary, hexadecimal বা octal ফরম্যাটে লেখা int সহজেই গ্রহণ করে, আর এদের মধ্যে অনায়াসে গাণিতিক কাজও চালায়।
আরও বিস্তারিত জানতে আপনি binary-octal-hexadecimal কনসেপ্টটি দেখে নিতে পারেন।
বাইনারি সংখ্যা লেখা হয় 0b প্রিফিক্স দিয়ে, যেমন হেক্সাডেসিমালের জন্য 0x ব্যবহার করা যায় (হেক্স সংখ্যা ৪টি বিটের একটি গ্রুপ প্রকাশের সংক্ষিপ্ত উপায়), আর অক্টাল সংখ্যার জন্য 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 Logicbools কনসেপ্টে আমরা বুলিয়ান (True ও False) মানের সাথে ব্যবহৃত লজিক্যাল অপারেটর and, or ও not নিয়ে আলোচনা করেছি।
বিট নিয়ে কাজ করার সময়ও একই লজিকের নিয়ম খাটে।
তবে লজিক্যাল অপারেটরগুলোর বিটওয়াইজ সমতুল্য & (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
বিটওয়াইজ | (or)-এর ক্ষেত্রে, ইনপুট দুটির যেকোনো একটিতে 1 থাকলেই আউটপুটে 1 হয়:
>>> x = 0b01100110
>>> y = 0b00101010
>>> format(x | y, '08b')
'01101110'
বিটওয়াইজ exclusive or (xor)-এর ^ অপারেটরে, কোনো 1 ইনপুট দুটির যেকোনো একটিতে থাকলে, কিন্তু দুটোতেই নয়, তখনই আউটপুটে 1 হয়।
এই প্রতীকটি sets কনসেপ্ট থেকে চেনা লাগতে পারে, যেখানে এটি set-এর সিমেট্রিক ডিফারেন্স-এর জন্য ব্যবহৃত হয়, যা সেটে xor প্রয়োগের সমান।
xor ^ যদি অদ্ভুত লাগে, তবে জেনে রাখুন এটি ক্রিপ্টোগ্রাফিতে এখন পর্যন্ত সবচেয়ে বেশি ব্যবহৃত অপারেশন।
>>> x = 0b01100110
>>> y = 0b00101010
>>> format(x ^ y, '08b')
'01001100'
সবশেষে আছে ~ অপারেটর (টিল্ড ক্যারেক্টার), যা একটি বিটওয়াইজ not। এটি একটি ইনপুট নেয় এবং সব বিট উল্টে দেয়, যা হয়তো আপনি যে ফলাফল আশা করেছিলেন তা নয়!
রূপায়ণের প্রতিটি 1 বদলে 0 হয়, আর উল্টোটাও তাই।
বিস্তারিত জানতে নিচের অংশটি দেখুন।
দশমিক রূপায়ণে আমরা ধনাত্মক ও ঋণাত্মক সংখ্যার মধ্যে পার্থক্য করি অঙ্কগুলোর বাম দিকে + বা - চিহ্ন বসিয়ে।
বাইনারি স্তরে এই চিহ্নগুলো ব্যবহার করা ডিজিটাল কম্পিউটিংয়ের জন্য অদক্ষ প্রমাণিত হয়, আর +0 আর -0 এক নয়, এই সমস্যাটাও তৈরি করে।
- আর + ব্যবহার না করে সব আধুনিক কম্পিউটার ঋণাত্মক সংখ্যার জন্য two's complement রূপায়ণ ব্যবহার করে, একেবারে সিলিকন চিপের স্তর পর্যন্ত।
এর মানে সব বিট উল্টে দেওয়া হয়, আর বাঁ দিকের সবচেয়ে প্রান্তের বিটটি (যাকে "most significant bit", সংক্ষেপে MSB-ও বলা হয়) 1 হলে সংখ্যাটিকে ঋণাত্মক হিসেবে ব্যাখ্যা করা হয়।
ধনাত্মক সংখ্যার MSB হয় 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'