پرش به مطلب اصلی

باینری

اعتبار

این از موضوع آموزشی در انجمن‌های SA-MP است. نویسنده Kyosaur است.

باینری چیست؟

باینری سیستم عددی است که از دو نماد منحصر به فرد برای نمایش اعداد استفاده می‌کند. در حالی که سیستم دهدهی معمول‌تر از ده رقم استفاده می‌کند (مبنای 10)، باینری فقط از 0 و 1 استفاده می‌کند. این ممکن است در زندگی روزمره بی‌فایده به نظر برسد، اما باینری در مورد کامپیوترها ضروری است. کامپیوترها در پایین‌ترین سطحشان تمام محاسبات خود را با دستکاری جریان الکتریسیته برای نشان دادن حالت‌های روشن و خاموش انجام می‌دهند. این دقیقاً همان باینری است، فقط تن‌ها کلیدی که روشن و خاموش می‌شوند. این مفهومی نوعاً بیگانه برای اکثر مردم است، پس بیایید نگاهی به سیستم دهدهی و باینری کنار هم بیندازیم.

دهدهی (مبنای 10)

0
1
2
3
4
5
6
7
8
9
10
11
12
13

باینری (مبنای 2)

0 //0
1 //1
10 //2
11 //3
100 //4
101 //5
110 //6
111 //7
1000 //8
1001 //9
1010 //10
1011 //11
1100 //12
1101 //13

با نگاه به هر دو سیستم کنار هم، متوجه خواهید شد که دقیقاً یکسان رفتار می‌کنند. وقتی به آخرین عدد موجود می‌رسید باید به مکان دیگری بروید. این مکان‌ها در باینری bit (binary digits) نامیده می‌شوند و فقط توان‌های دو هستند؛ همان طور که مکان‌ها در سیستم دهدهی توان‌های 10 هستند. برای اثبات این، بیایید نگاهی به عدد 13 در نماد استاندارد بیندازیم.

توجه: '^' توان در چند مثال بعدی است، نه bitwise exclusive (که بعداً پوشش خواهیم داد).

دهدهی (مبنای 10)

13

//که برابر است

1 * (10^1) + 3 * (10^0)

//که برابر است

10+3

//که برابر است

13

باینری (مبنای 2)

1101

//که برابر است

1 * (2^3) + 1 * (2^2) + 0 * (2^1) + 1 * (2^0)

//که برابر است

8+4+0+1

//که برابر است

13

از مثال قبلی می‌بینیم که اگر bit روی 0 تنظیم شده باشد، می‌توانیم آن را نادیده بگیریم و ادامه دهیم؛ بالاخره، هر چیز ضرب در 0 صفر خواهد شد. مثال قبلی کمی پیچیده بود و فقط من سعی می‌کردم کاملاً واضح باشم. وقتی از باینری تبدیل می‌کنید، تمام آنچه واقعاً نیاز دارید نگران آن باشید جمع کردن توان‌های تمام bitهایی که روشن هستند است.

در اینجا 12 توان 2 فقط از بالای سرم:

4096,2048,1024,512,256,128,64,32,16,8,4,2,1

اگر چیزی در مورد کار با توان‌ها نمی‌دانید، این احتمالاً اصلاً برایتان معنا ندارد. توان عددی است که x مرتبه در خودش ضرب شده. با این اطلاعات در ذهن، لیست قبلی توان‌ها احتمالاً معنای بیشتری دارد؛ خوب به استثنای 1. ممکن است کنجکاو باشید چرا 2 به توان 0 نتیجه 1 می‌دهد، تمام آنچه می‌توانم به این بگویم این است که همینطور است.

2^1 = 2, 2^3 = 4, 2^4 = 8

می‌بینیم که وقتی به راست حرکت می‌کنیم، مقدار قبلی‌مان در 2 ضرب می‌شود؛ پس ایمن است فرض کنیم که وقتی به چپ حرکت می‌کنیم مقدار جدیدمان فقط عدد قبلی تقسیم بر 2 است. با این در ذهن می‌توانید ببینید چطور می‌توانیم به 2 به توان صفر مساوی 1 برسیم. اگر این به اندازه کافی رضایت‌بخش نیست، مطمئنم می‌توانید اثبات بیشتری در ** پیدا کنید. همه این گفته‌ها، بیایید نگاهی به یک مثال نهایی بیندازیم، و بیایید آن را تا حدودی پیچیده کنیم!

111011001011111000 //242424

//به خاطر داشته باشید، bitهایی که روشن نیستند را نادیده بگیرید.

1 * (2^17) = 131072

1 * (2^16) = 65536

1 * (2^15) = 32768

1 * (2^13) = 8192

1 * (2^12) = 4096

1 * (2^9) = 512

1 * (2^7) = 128

1 * (2^6) = 64

1 * (2^5) = 32

1 * (2^4) = 16

1 * (2^3) = 8


131072+65536+32768+8192+4096+512+128+64+32+16+8
=
242424

هنگام تبدیل به خاطر داشته باشید: اولین توان 0 است پس اشتباه نکنید که مکان 18ام را 2^18 ببینید. واقعاً 18 توان وجود دارد، اما این شامل توان 0 است، پس 17 در واقع بالاترین توان ما است.

نگاه عمیق‌تر به bitها

اکثر زبان‌های برنامه‌نویسی انواع داده مختلفی را امکان‌پذیر می‌کنند که در مقدار bitهایی که می‌تواند برای ذخیره اطلاعات استفاده شود متفاوت هستند؛ اما pawn زبان بدون نوع 32 بیتی است. این یعنی pawn همیشه 32 bit برای ذخیره اطلاعات در دسترس خواهد داشت. پس چه اتفاقی می‌افتد وقتی اطلاعات زیادی دارید؟ پاسخ این سؤال در اعداد صحیح signed و unsigned نهفته است.

اعداد صحیح signed

تا به حال متوجه شده‌اید که وقتی integer در pawn خیلی بالا می‌شود منفی می‌شود؟ این "پیچیدگی" به دلیل رفتن بالاتر از حداکثر مقدار در pawn که:

2^31 - 1 //توان، نه bitwise exclusive. همچنین -1 به این دلیل است که ما 0 را می‌شماریم (2,147,483,648 مقدار وجود دارد، اما آن با 0 است، پس فنی 2,147,483,647 حداکثر است).

//که برابر است

2,147,483,647

//که در باینری

1111111111111111111111111111111 //31 bit- همه روشن

ممکن است کنجکاو باشید چرا این حداکثر مقدار است، و نه 2^32-1 (4,294,967,295). اینجا اعداد صحیح signed و unsigned وارد بازی می‌شوند. اعداد صحیح signed قابلیت ذخیره مقادیر منفی دارند، جایی که اعداد صحیح unsigned ندارند. این ممکن است به نظر برسد از سؤال منحرف شده‌ام، اما اطمینان می‌دهم که نه. دلیل اینکه حداکثر integer برابر 2^32-1 نیست این است که bit سی و دوم به عنوان نوعی toggle برای مقادیر منفی و مثبت استفاده می‌شود. این MSB (Most significant bit) نامیده می‌شود. اگر MSB روشن باشد، عدد منفی خواهد بود؛ اگر خاموش باشد، عدد مثبت است. بسیار ساده، درست؟

قبل از اینکه چند مقدار منفی نشان دهم، نیاز دارم توضیح دهم که مقادیر منفی در pawn چگونه نمایش داده می‌شوند. Pawn از سیستمی به نام 2's complement برای نمایش مقادیر منفی استفاده می‌کند، که اساساً یعنی هر bit واحد در عدد خود را flip می‌کنید و 1 به عدد جدید اضافه می‌کنید تا آن را منفی کنید.

بیایید نگاهی به چند مقدار منفی بیندازیم وقتی این ایده همچنان در سرتان است:

11111111111111111111111111111111 //همه 32 bit روشن

//برابر است

-1

//و

11111111111111111111111111111110

//برابر است

-2

//و در نهایت

10000000000000000000000000000000

//برابر است

-2147483648

ببینید، همه اعداد منفی فقط عدد مثبت اصلی با همه bitهایش flip شده و یکی اضافه شده هستند. این با مثال آخرمان فوق‌العاده واضح است، زیرا بالاترین integer مثبت 2147483647 است.

از این می‌بینیم که محدوده عدد در pawn در واقع:

2^31 to +2^311

اعداد صحیح unsigned

چیزی به نام اعداد صحیح unsigned در pawn وجود ندارد، اما این را فقط برای تعادل اضافه می‌کنم. تنها تفاوت بین integer signed و unsigned این است که اعداد صحیح unsigned نمی‌توانند مقادیر منفی ذخیره کنند؛ Integerها همچنان wrap می‌شوند، اما به جای مقدار منفی به 0 برمی‌گردند.

عملگرهای باینری

عملگرهای باینری به شما امکان دستکاری bitهای مجزای pattern bit را می‌دهند. بیایید نگاهی به لیستی از عملگرهای bitwise موجود بیندازیم.

  • Bitwise arithmetic shift: >>, و <<
  • Bitwise logical shift: >>>
  • Bitwise NOT (aka complement): ~
  • Bitwise AND: &
  • Bitwise OR: |
  • Bitwise XOR (aka exclusive-or): ^

Bitwise AND

توجه: با عملگر logical AND '&&' اشتباه نگیرید

AND باینری فقط logical AND bitها در هر موقعیت عدد در فرم باینری را می‌گیرد. این کمی گیج‌کننده به نظر می‌رسد، پس بیایید نگاهی به آن در عمل بیندازیم!

1100 //12
&
0100 //4
=
0100 //4 زیرا هر دو "100" در آنها دارند (که 4 است)

آن کمی آسان بود، بیایید نگاهی به یکی سخت‌تر بیندازیم:

10111000 //184
&
01001000 //72
=
00001000 //8

نگاه به مثال‌ها باید ایده خوبی از آنچه این عملگر انجام می‌دهد به شما بدهد. دو مجموعه bit را با هم مقایسه می‌کند، اگر هر دو آنها bit 1 مشترک داشته باشند، نتیجه همان bit روشن خواهد داشت. اگر هیچ bit مشترکی نداشته باشند، آنگاه نتیجه 0 است.

Bitwise OR

توجه: با عملگر logical OR '||' اشتباه نگیرید

Bitwise OR تقریباً دقیقاً مثل bitwise AND کار می‌کند. تنها تفاوت بین آن دو این است که bitwise OR فقط نیاز دارد یکی از دو bit pattern برای داشتن bit روشن تا نتیجه همان bit روشن داشته باشد. بیایید نگاهی به چند مثال بیندازیم!

1100 //12
|
0100 //4
=
1100 //12.

بیایید نگاهی به یک مثال دیگر بیندازیم.

10111000 //184
|
01001000 //72
=
11111000 //248

فکر می‌کنم این بسیار خودتوضیح است، اگر یکی از اعداد bit روشن داشته باشد عدد نتیجه هم همان bit روشن خواهد داشت.

Bitwise XOR

این عملگر کمی شبیه عملگر bitwise OR است، اما کمی تفاوت دارد. بیایید همان مثال استفاده شده در بخش bitwise OR را ببینیم، و ببینیم آیا می‌توانید تفاوت را تشخیص دهید.

1100 //12
^
0100 //4
=
1000 //8.

و در نهایت:

10111000 //184
^
01001000 //72
=
11110000 //240

Bitwise NOT

این عملگر هر bit در pattern bit را flip می‌کند، همه 1ها را به 0 و بالعکس تبدیل می‌کند.

~0
=
11111111111111111111111111111111 //-1

//و

~100 //4
=
11111111111111111111111111111011 //-5

//و

~1111111111111111111111111111111 //2147483647 (با -1 که 32 bit دارد، نه 31 اشتباه نگیرید)
=
10000000000000000000000000000000 //-2147483648 (32nd bit روشن)

اگر نمی‌فهمید چرا مقادیر منفی تا حدودی "معکوس" هستند لطفاً بخش در مورد اعداد صحیح signed را بخوانید.

Bit Shifting

Bit shifting دقیقاً کاری که تصور می‌کنید انجام می‌دهد؛ bitها را در عدد به سمت جهت خاصی shift می‌کند. اگر قبل‌تر در مقاله به خاطر داشته باشید گفتم PAWN محدوده حافظه خاصی دارد (32 bit که می‌تواند برای ذخیره‌سازی استفاده شود). چه اتفاقی می‌افتد وقتی عددی را فراتر از آن محدوده shift کنید؟ پاسخ این سؤال در اینکه کدام عملگر shifting استفاده می‌کنید، و به کدام جهت shift می‌کنید نهفته است.

توجه: در مثال‌های زیر، همه اعداد باینری به طور کامل نوشته خواهند شد (همه 32 bit) تا از هرگونه ابهام جلوگیری شود.

Arithmetic shifts

Right shift

همه bitها در عدد x بار به راست shift می‌شوند وقتی از این عملگر استفاده می‌کنید. بیایید نگاه سریعی به مثال ساده بیندازیم.

00000000000000000000000000001000  //8
>>
2

=

00000000000000000000000000000010 //2

از مثال قبلی می‌توانید ببینید که هر bit دو مکان به راست حرکت کرده، و دو صفر در سمت چپ به عنوان padding اضافه شده‌اند. این دو صفر در واقع مقدار MSB (Most significant bit) هستند و هنگام shifting اعداد صحیح signed بسیار مهم هستند. دلیل استفاده از MSB به عنوان padding این است که علامت عددی که shift می‌شود را حفظ کنیم. بیایید نگاهی به همان مثال بیندازیم، مگر اینکه آن را منفی کنیم.

11111111111111111111111111111000 //-8
>>
2

=

11111111111111111111111111111110 //-2

واضح است این دقیقاً مثل مثال قبلی رفتار می‌کند، مگر اینکه bitهای چپ استفاده شده برای padding یک هستند؛ که ثابت می‌کند padding راست arithmetic shift مقدار MSB است.

Left shift

این دقیقاً مخالف عملگر right arithmetic shifting است. همه bitها در عدد را x بار به چپ shift می‌کند. بیایید نگاهی به مثال بیندازیم.

00000000000000000000000000001000  //8
<<
2

=

00000000000000000000000000100000 //32

تنها تفاوت بین left و right arithmetic shift (غیر از جهت shift) روش مدیریت padding خواهد بود. با right arithmetic shift، padding مقدار MSB (Most significant bit) است، اما با left arithmetic shift مقدار فقط 0 است. این به این دلیل است که اطلاعات مرتبطی مثل علامت عدد برای ردیابی وجود ندارد.

11111111111111111111111111111000 //-8
<<
2

=

11111111111111111111111111100000 //-32

ببینید؟ حتی اگر padding همیشه 0 باشد، علامت عدد همچنان حفظ می‌شود. تنها چیزی که واقعاً نگران آن باید باشید shift کردن زیادی است. اگر عدد مثبتی را فراتر از بالاترین عدد ممکن shift کنید، منفی خواهد شد و بالعکس با مقادیر منفی (در نهایت به 0 خواهید رسید).

Logical Shifts

Right Shift

این مخالف arithmetic left shift است. بهترین راه توصیف آن ترکیب بین دو arithmetic shift خواهد بود. بیایید نگاهی به آن در عمل بیندازیم!

00000000000000000000000000001000  //8
>>>
2

=

00000000000000000000000000000010 //2

bitها در عدد 8 دو بار به راست shift شدند. پس چطور این با arithmetic right shift متفاوت است؟ پاسخ padding است. با arithmetic right shift، padding مقدار MSB است، اما با logical right shift padding فقط 0 است (همان طور که با arithmetic left shift است). این یعنی علامت عدد را حفظ نخواهد کرد، و نتیجه ما همیشه مثبت خواهد بود. برای اثبات این، بیایید عدد منفی shift کنیم!

11111111111111111111111111111000 //-8
>>>
2

=

00111111111111111111111111111110 //1073741822

این ثابت می‌کند که وقتی از logical right shift استفاده می‌کنیم مقادیر منفی نخواهیم گرفت!

Left shift

logical left shift وجود ندارد، زیرا دقیقاً همانند arithmetic left shift عمل می‌کرد. این را فقط برای جلوگیری از هرگونه ابهام اضافه کردم، و همچنین برای متعادل نگه داشتن بخش.