در سطح سختافزار، ترانزیستورها فقط میتوانند روشن یا خاموش باشند: دو حالتی که بهطور سنتی آنها را با 1 و 0 نمایش میدهیم.
اینها همان binary digits هستند که به اختصار bits نامیده میشوند.
آگاهی از bits و binary برای برنامهنویسان سیستمی که با زبانهای سطح پایین کار میکنند، اهمیت ویژهای دارد.
با این حال، در بیشتر تاریخ رایانش، اولویت برنامهنویسی این بوده است که راههای روزافزون پیچیدهتری برای انتزاع کردن این واقعیت دودویی پیدا شود.
در پایتون (و بسیاری از زبانهای برنامهنویسی سطح بالا دیگر)، ما با int، float، string و دیگر انواع تعریفشده کار میکنیم، تا جایی که به قالبهای صوتی و تصویری هم میرسد.
میگذاریم درون پایتون (در نهایت) همهچیز را به بیتها ترجمه کند.
با این وجود، استفاده از عملگرهای بیتی و عملیات بیتی گاهی میتواند مزیتهای چشمگیری در سرعت و بهرهوری حافظه داشته باشد، حتی در زبانی سطح بالا مثل پایتون.
جای تعجب نیست که پایتون با اعداد دهدهی با کاربر تعامل میکند، اما یک برنامهنویس میتواند این پیشفرض را نادیده بگیرد.
در واقع، پایتون بهراحتی یک int را در قالب binary، hexadecimal یا octal میپذیرد و با کمال میل عملیات ریاضی را بین آنها انجام میدهد.
برای جزئیات بیشتر، میتوانید مفهوم 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 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
برای | بیتی (or)، اگر در هر یک از ورودیها 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'
در نهایت، عملگر ~ (نویسهی تیلدا) وجود دارد که یک not بیتی است، یک ورودی میگیرد و همهی بیتها را معکوس میکند، که ممکن است همان نتیجهای نباشد که انتظار داشتید!
هر 1 در نمایش به 0 تبدیل میشود و برعکس.
برای جزئیات به بخش زیر مراجعه کنید.
در نمایش دهدهی، اعداد مثبت و منفی را با استفاده از علامت + یا - در سمت چپ ارقام از هم تشخیص میدهیم.
استفاده از این نمادها در سطح دودویی برای رایانش دیجیتال ناکارآمد بود و این مشکل را پیش آورد که +0 با -0 یکسان نیست.
رایانههای مدرن بهجای استفاده از - و +، برای اعداد منفی از نمایش two's complement استفاده میکنند، تا سطح تراشهی سیلیکونی.
این یعنی همهی بیتها معکوس میشوند و اگر چپترین بیت (که «پرارزشترین بیت» یا MSB هم نامیده میشود) 1 باشد، عدد منفی تفسیر میشود.
اعداد مثبت MSB برابر 0 دارند.
این نمایش این مزیت را دارد که فقط یک نسخه از صفر دارد، بنابراین برنامهنویس مجبور نیست -0 و +0 را مدیریت کند.
این روش نمایش اعداد منفی و مثبت، برای پایتون پیچیدگیای ایجاد میکند: در هستهی زبان، هیچ مفهوم عدد صحیح با اندازهی محدود مانند int32 یا int64 وجود ندارد.
در پایتون «مدرن»، 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 پشتیبانی میشوند، اما در هستهی پایتون نه.
در عمل، برنامهنویسان پایتون اغلب از &، |، ^ و عملگرهای شیفت که در ادامه توضیح داده میشوند، فقط با اعداد مثبت استفاده میکنند.
عملیات بیتی با اعداد منفی بسیار کمرایجتر است.
یک تکنیک این است که 2**32 (or 1 << 32) را به یک مقدار منفی اضافه کنیم تا int را بدون علامت کنیم، اما مدیریت آن دشوار میشود.
راهبرد دیگر کار با ماژول ctypes و استفاده از انواع صحیح به سبک زبان C است، اما این هم به همان اندازه دستوپاگیر است.
Shift operatorsعملگر شیفت چپ x << y همهی بیتهای x را به اندازهی y مکان به سمت چپ میبرد و شکافهای جدید را با صفر پر میکند.
توجه داشته باشید که این از نظر ریاضی معادل ضرب یک عدد در (2**y) است.
عملگر شیفت راست x >> y عکس این را انجام میدهد.
این از نظر ریاضی معادل تقسیم صحیح x // (2**y) است.
بخش قبلی دربارهی اعداد منفی و دامهایشان هنگام شیفت دادن آنها در پایتون را در نظر داشته باشید.
>>> 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'