تیتر مطلب بلندتر از تیترهای معمول شد اما همچنان آنقدر که باید گویا نیست، بنابراین پاراگراف اول را به این اختصاص میدهم که قرار است در مورد چه چیزی صحبت کنم. همه ما شنیدهایم که کامپیوتر با ۰ و ۱ کار میکند، یا به شکل پایهایتر، در دنیای دیجیتال فقط دو مقدار (مثلا ۰ ولت و ۵ ولت) تعریف شدهاست. کامپیوتر از روی این صفرها و یکها چطور برای ما برنامه های رنگی اجرا میکند؟ چطور میفهمد باید چه کار کند؟ چطور در شبکه دادهها را انتقال میدهد؟ چطور همه چیزهای زیبا فقط با این دو عدد ساخته میشوند؟
چیزی که اینجا مینویسم، درواقع ریشه در درسهای مدار منطقی، معماری کامپیوتر، ریزپردازنده و اسمبلی، مبانی کامپیوتر، شبکه و شاید کمی بیشتر است اما سعی میکنم تا حد امکان ساده توضیح دهم. اگر جایی بیدقتی علمی دارد یا میشود بهتر توضیح داد حتما بگویید.
دنیای دیجیتال بین همه مقادیر دنیای آنالوگ فقط تعدادی مقدار گسسته را برگزید، در واقع دو مقدار در دنیای کامپیوترهای امروزی که این دو مقدار گسسته ۰ و ۱ هستند، اینکه ۰ و ۱ هرکدام معادل چه ولتاژی در آنالوگ هستند چندان مهم نیست مثلا منفی ۵ و مثبت ۵ یا ۰ و ۵. در این مرحله ولتاژ abstract شده و فقط با ۰ یا ۱ بودنش کار داریم. چرا مقدارهای بیشتری مثلا ۳ و ۴ گرفته نشد؟ اتفاقا تلاشهایی شد: (درمورد ternary computer بخوانید) اما پیادهسازی قطعات آنها سختتر بود، کلیت ایده هم تفاوتی ندارد، مثلا همچنان میتوان پرسید با ۴ مقدار چطور میتوان همه چیز را پیاده سازی کرد.
اگر موضوعی که در این مطلب سعی دارم پوشش دهم را سرچ کنید، اکثر مطالب به شما باینری (مبنای ۲) یاد میدهند، یعنی میگویند که ۱۱۰۰۱ در واقع نمایش عدد ۲۵ در مبنای دو است، یا اینکه چطور با روش تقسیمهای متوالی یک عدد را به نمایش مبنای دو ببریم؟ این هم علم مفیدی است و برنامهنویسها گاهی مجبور میشوند خودشان یک عدد را به مبنای دو ببرند یا نمایش باینری یک عدد را بخوانند و متوجه شوند مربوط به چه عددی است.
اما این فقط یکی از مشکلات است، در واقع فقط یاد میگیریم که عدد مثبت چطوری به باینری تبدیل میشود اما این برای اینکه بفهمیم مثلا تصویر چطور ذخیره میشود کافی است؟ نه. برای اینکه بفهمیم ویدیو چطور پخش میشود کافی است؟ بازهم نه! حتی یک سوال مهم هم نمیتوان جواب داد، از کجا میفهمیم این عدد تمام شده؟ تا چند رقم باینری این عدد ادامه دارد؟
شاید بسیاری از آموزشهای کامپیوتر هم به شما بگویند که در کامپیوتر هر کدام از صفر و یکها یک «بیت» (bit) است، یعنی یک بیت میتواند «یک» باشد یا «صفر». حالا هر ۸بیت را در یک دسته میگذاریم و اسمش را «بایت» (Byte) میگذاریم. خب این چه کمکی میکند؟ متاسفانه باز هم هیچ. البته دانستن این موضوع هم یکی از اجزای مهم برای فهم ادامه مطلب است برای همین اینجا آوردم تا اگر به گوشتان نخورده آشنا شوید.
حالا که بحث بیت شد یک سوال حاشیهای هم جواب دهم: کامپیوتر من ۶۴ بیتی است یعنی کلا ۶۴ تا بیت دارد؟ یعنی فقط ۶۴ تا صفر و یک؟ جواب این است که نه، کامپیوتر شما بیشتر از این حرفها صفر و یک دارد، این یکی از شاخص های اندازهگیری است که خیلی هم مهم نیست.
فرض کنیم که کامپیوتری درکار نیست و فقط خودمانیم. حالا میخواهیم یک مساله ریاضی حل کنیم، این مساله ریاضی دبستان یکسری عدد به ما میدهد و یکسری دستور که روی این اعداد پیادهکنیم، به عددها بگویم داده (value) و به دستورها (مثلا ضرب کن یا تقسیم کن یا جمع کن) بگوییم دستور (instruction).
برای کامپیوتر هم که ماشین محاسبات است هردوی اینها معنیدار است، یکسری دستور دارد که باید انجام دهد و یکسری داده دارد که عملیات روی اینها انجام میشود، حتی اگر ماشین حساب را یک کامپیوتر در نظر بگیریم (که تقریب بدی هم نیست)، یکسری داده دارد (اعداد) و یکسری دستور (مثلا عملیاتهای ریاضی) که آنها را میفهمد و روی دادهها اعمال میکند.
به طور ساده اگر بخواهم یک کامپیوتر را شرح دهم، یک پردازنده دارد که کار پردازش را انجام میدهد یعنی دستورها و مقدارها را میگیرد و کارهایی که دستورها بیان میکنند را روی دادهها انجام میدهد و دادههای نتیجه را به دست میآورد.
دادهها و دستورها در کجا ذخیره شدهاند؟ روی حافظه یا مموری (RAM). در واقع حافظه یک لیست بسیار بزرگ از بیتهاست که روی آن دادهها و دستورها وجود دارند. (روی این بیشتر صحبت خواهیم کرد) اما این مموری یک محدودیت دارد و این است که اگر برقش قطع شود (مثلا کامپیوتر خاموش شود) اطلاعاتش از دست میرود، پس وقتی سیستم را روشن میکنیم اطلاعاتش را از کجا میخواند؟
حافظه جانبی (که می تواند بیرون کامپیوتر باشد مثل فلش مموری یا داخلش مثل hard disk) هم مثل مموری یک حافظه بزرگ است اما با این تفاوت که اطلاعات را میتواند بدون نیاز به برق برای همیشه نگهداری کند، بنابراین وقتی سیستم روشن میشود اطلاعات از این حافظه باید خوانده شوند و در طول اجرا نیز مموری و پردازنده باید اطلاعاتی که نیاز دارند دفعات بعدی هم داشته باشند در حافظه جانبی بنویسند.
بیایید کمی دانش ریاضیات گسسته را به مفاهیم بیتی و بایتی هم اضافه کنیم.
اگر هر بیت دو حالت داشته باشد، یعنی یک بیت میتواند ۲ حالت مختلف را نشان دهد.
حالا دو بیت کنار هم چند عدد را میتوانند نشان دهند؟ بیایید حالتها را بنویسیم: ۰۰و ۰۱و ۱۰ و ۱۱، یعنی ۴ حالت.
برای ۳ بیت چی؟ برای ۳ بیت نتیجه دوبرابر جواب ۲ بیت است یعنی ۸ حالت. (۲ به توان ۳)
یک بایت (یا همان ۸بیت) که یک اندازه محبوب برای برنامهنویسهاست میتواند دو به توان ۸ (۲۵۶) حالت مختلف را برای ما بسازد.
در ۴ بایت (یا ۳۲ بیت) چند حالت مختلف میتوانیم داشته باشیم؟ ۲ به توان ۳۲ حالت. (4294967296)
بالاخره مقدمه تمام شد! در این قسمت میخواهیم به ۰ و ۱ ها کمکم معنی بدهیم.
وقتی میگویم یک بایت میتواند ۲۵۶ حالت مختلف را نشان دهد تقریبا مشخص است، یعنی ۲۵۶ شکل مختلف دارد که هر کدام میتواند یک معنی مختلف داشته باشد، اما این معنیها چه چیزایی هستند؟ آیا کامپیوتر خودش این ۲۵۶ حالت مختلف را میفهمد؟
نه کامپیوتر خودش ۲۵۶ حالت را نمیفهمد، خود این حالتها هم به خودی خود هیچ معنایی ندارند، مثلا 00011001 یکی از آن ۲۵۶ حالت است اما به خودی خود هیچ معنایی ندارد، حتی یک برنامهنویس خبره هم از این به تنهایی هیچ برداشتی نمیکند. شاید بگویید که نه ما کمی مبنای دو یاد گرفتیم، این عدد همان ۲۵ است که بالا دیدیم، اما بگذارید مخالفت کنم.
اگر در دنیای ریاضی صحبت میکردیم، 00011001 در واقع ۱۱۰۰۱ بود، چون نگفتم در مبنای ۲ است، هر عددی که فقط از ۰ و ۱ تشکیل شده که لزوما در مبنای دو نیست! در ریاضی برای اینکه مشخص کنیم 11001 باید چطوری خوانده شود از یک نگارش خاص استفاده می کنیم، مثلا پایین سمت راست عدد مبنایش را مینویسیم.
چرا در ریاضی این کار را میکنیم؟ برای اینکه مشخص باشد این دنبالهای از اعداد که پشت سر هم نوشتهایم چطور تفسیر میشود، در کامپیوتر هم نیاز به چنین چیزی است اما فعلا آن را نداریم، بنابراین سرخود 00011001 را به ۲۵ تفسیر نمیکنیم بلکه باید ببینیم چطور تفسیر میشود؟ بدون روش تفسیر فقط یک دنباله از ۰ و ۱ ها داریم.
درست است که یکی از روشهای تفسیر مهم همین مبنای دو است و هر رشتهای از ۰ و ۱ ها را میتوانیم مبنای دو در نظر بگیریم اما آیا واقعا این کار صحیح است؟
مثلا دنباله زیر را در نظر بگیرید:
اگر بپرسم این رشته باینری که به بایتها هم شکسته شده چیست چطور پاسخ میدهید؟ میخواهیم هر بایتش را مبنای ۲ فرض کنید و به یک عدد تبدیل کنید؟ یا کلش را یک عدد میگیرید؟ این کارها هرچند قابل انجام است اما صحیح نیست، این رشته صفر و یک در واقع اصلا عدد نیست، بلکه چند بایت اولی است که تصویر پروفایل من را نشان میدهد. درمورد اینکه چطور یک تصویر را میشود با رشته باینری نشان داد جلوتر صحبت میکنیم اما در همین حد مهم است بدانید که هر رشتهای از صفر و یک قرار نیست یک عدد بدون علامت باشد.
عدد یک چیز انتزاعی است، مثلا عدد ۱ است، همه ما وقتی به یک فکر میکنیم یک چیز واحد در ذهنمان میآید، همچنین عدد دو یا عدد پی، حتی با اینکه عدد پی به شکل دقیق محاسبه نشده ولی به یک عدد واحد فکر میکنیم.
اما نمایش (representation)، شکلی است که عدد را روی کاغذ مینویسیم مثلا «۰» یا «۱» یا «۲۵» (برای آخری فرض کنیم مبنا ده است) نمایش هستند.
یک عدد میتواند چند نمایش داشته باشد مثلا ۲۵ را میتوانیم با باینری هم نمایش دهیم که میشود 11001. همچنین یک رشته از ارقام هم میتواند نشاندهندهی چند عدد مختلف باشد مثلا ۱۰ بسته به اینکه در چه مبنایی بخوانیمش عدد متفاوتی میشود!
پس برای اینکه یک عدد را نمایش دهیم باید یک نمایش را انتخاب کنیم، مثلا مبنای ۲ یا دهدهی نمایشهایی هستند که بلدیم، از طرف دیگر برای اینکه رشته از ارقام را تفسیر کنیم (عددش را بفهمیم) نیاز داریم که بدانیم از چه نمایشی استفاده میکند.
همهی نمایشهای ممکن، در ریاضی نیستند، ریاضیدانها بر حسب نیاز خود که روی کاغذ مینوشتند یک نمایش برای هر مبانی درست کردند، اما اینکه روی کاغذ مینوشتند آزادیای به آنها میداد که برنامهنویسها روی کامپیوتر نداریم، بیاید به اعداد غیر از طبیعی نگاه کنیم، مثلا عدد منفی ۲۵، ریاضیدانها در مبنای ۲ آن را به شکل 11001- نشان میدهند اما در کامپیوتر که فقط ۰ و ۱ داریم، منها رو باید چکار کنیم؟! یا مثلا برای اعداد اعشاری مثلا 12.5 را به شکل 1100.1 نشان میدهند اما بازهم در کامپیوتر فقط ۰ و ۱ داریم و نقطهی اعشاری هم نداریم.
برای حل این مشکل، یعنی نشان دادن اعداد اعشاری یا علامتدار در کامپیوتر نمایشهای متفاوتی بر پایهی همین ۰ و ۱ درست کردهاند، توضیح همه انها اینجا جا نمیشود اما مثلا میتوانید فرض کنید که جدا از خود عدد یک بیت داریم که نشان میدهد عدد مثبت است یا منفی. مثلا 011001 میشود عدد ۲۵+ و 111001 میشود ۲۵−. البته به این سادگی نیست مثلا این روش دو تا صفر دارد (مثبت صفر یا منفی صفر) به این روش اشکالدار «علامت و مقدار» (sign and magnitude) میگویند.
برای مطالعه بیشتر در این مورد میتوانید اسلایدهای number representation از این لینک را بخوانید.
فرض کنیم تنها چیزی که میخواهیم نشان بدهیم اعداد مثبت و منفی هستند و از همین نمایش اشکالدار «علامت و مقدار» که پاراگراف بالا معرفی شد استفاده کنیم، آیا با این قراردادی که ما کردیم کامپیوتر میفهمد که 011001 یک عدد مثبت است و به معنی مثبت ۲۵ است؟
نه، کامپیوتر متاسفانه این قرارداد ما را نمیفهمد، هرقرارداد دیگری هم کنیم باز هم نمیفهمد، کامپیوتر اصلا قرار نیست چیزی را بفهمد، کامپیوتر قرار است برای ما محاسبه انجام دهد! اگر قرار بود بفهمد که اسمش computer (ماشین محاسبهگر) نبود.
اما اگر قرار است کامپیوتر چیزی را نفهمد پس چطوری محاسبه انجام میدهد؟ چطور به او بگوییم یک عدد را با دیگری جمع کن؟ حالا این دو عدد با نمایش «علامت و مقدار» در حافظه وجود دارند یا هر نمایش دیگری.
بگذارید من یک سوال دیگر بپرسم، اصلا چطور به کامپیوتر بگوییم یک کار را بکن؟ حالا اصلا هرکاری!
پردازنده از ما یکسری دستور (instruction) قبول میکند، روی کاتالوگ پردازنده میتوانید این دستورات را مطالعه کنید، حالا پردازنده برنامهریزی شده که هرکدام از این دستورات را انجام دهد، مثلا یک دستور ساده ولی مهم این است که یک عدد بدون علامت را بگیر (که میشود همان مبنای دو خودمان) و یکی به آن اضافه کن.
بنابراین برای اینکه کامپیوتر بتواند یک عدد «علامت و مقدار» را بعلاوه یک کند، باید برای اینکار طراحی شده باشد و یک دستور برای اینکار داشته باشد، اما متاسفانه پردازندهها برای این نمایش طراحی نشدهاند و چنین دستوری ندارند به جایش دستوری دارند که عددی که در سیستم نمایش «مکمل ۲» نمایش داده شده را بلاوه یک کنند چون سیستم مکمل دو سیستم رایجی است که اعداد علامتدار را با آن در کامپیوتر نشان میدهند.
وقتی به کامپیوتر میگوییم عددم را با یک جمع کن (اسم دستور inc است) از کجا میداند این عدد تا کجا است؟ مثلا ۲۵ در باینری علامت و مقدار میشود ۶ رقم اما ۲۵۰۰ میشود ۱۵ رقم، کامپیوتر از کجا میداند که چند رقم باید ادامه دهد تا آخر عدد را پیدا کند؟
در نوجوانی فکر میکردم که بعد از عدد حتما یک الگوی خاصی قرار میدهند مثلا 11111111 که کامپیوتر از روی آن میفهمد که دیگر عدد تمام شده و نباید جلوتر را بخواند (هرچند این شیوه هم واقعا جاهایی استفاده میشود!)
اما برای اعداد روش خیلی سادهتر است، عدد در کامپیوتر طول مشخصی دارد، ۴ بایت یا همان ۳۲ بیت! اگر عددمان کوچکتر بود چیکار کنیم؟ پشتش صفر میگذاریم.
اگر عدد بدون علامت باشد با این روش نهایتا همان ۲ به توان ۳۲ منهای یک که در قسمت بازی با اعداد گفتیم را میتواند نگه دارد اما اگر عدد علامت دار باشد یک بیت برای علامت می رود پس بزرگترین عدد نصف این عدد است. (۲ به توان ۳۱)
اما اگر عدد بزرگتر بود؟ پردازنده به ما کمکی نمیکند، مثل ماشین حساب که فقط مقدار محدودی محاسبات دارد، اما خودمان میتوانیم به خودمان کمک کنیم، مثلا به این شکل که عددمان را به نحوی به ۲ قسمت بشکنیم و در چند مرحله عملیات مورد نظرمان را انجام دهیم.
(البته ۴ بایت برای پردازندههای arm آن هم ۳۲ بیت دقیق است، برای سری x86 اعداد مختلفی هست ولی معمولا از بین اعداد ۱ یا ۲ یا ۴ یا ۸ بایت خارج نیست)
خب تا اینجا دیدیم که برای اعداد علامتدار باید خود پردازنده یک دستور مخصوص این کار داشته باشد، اما آیا پردازنده برای همه چیز دستور دارد؟ مثلا برای عدد اعشاری یا تصویر هم یک instruction خاص داریم؟ نه.
البته برای عدد اعشاری اکثر پردازندهها استاندارد ممیز شناور IEEE 754 را پشتیبانی میکنند اما این فقط یک حالت است، هرجور دیگری که بخواهیم قرارداد کنید پردازنده کمکی به شما نمیکند، به جز عدد هم که پردازنده کلا کمکی نمیکند، اما برای نمایش تصویر چه کار باید بکنیم؟
برای کاربردهای دیگر باید برنامه خودمان را طوری بنویسیم (یعنی instructionها را طوری دنبال هم قرار دهیم) که کاری که میخواهیم بکنیم را با دستورات دیگر انجام دهد، در این باره در قسمتهای بعد بیشتر میخوانیم.
اَسکی یا ASCII یکی از سادهترین مشکلاتی است که حل میشود و مثال خوبی برای شروع است، فرض کنید به جز عدد میخواهیم یک چیز دیگر هم نمایش دهیم: کارکترهای انگلیسی.
26 تا حرف انگلیسی داریم که با کوچک و بزرگش میشود 52 تا، با کارکترهای خاص مثل علامت تعجب و درصد و ویرگول و .. نهایتا به ۱۰۰ تا نمیرسد، پس میتوانیم همه حروف انگلیسی را با یک بایت نمایش دهیم. یادمان هست که بایت ۸ بیت بود و میتوانست ۲۵۶ حالت مختلف داشته باشد.
حالا باید یک mapping یک به یک از هریک کارکترها به یک نمایش بیتی داشته باشیم، یعنی در واقع قرارداد میکنیم که هر کدام از کارکترها یک عدد بین ۰ تا ۲۵۵ باشند. این قرارداد را در جدول اسکی میتوانید ببینید:
به جدول نگاه کنیم، ستون DEC یک عدد دهدهی را نشان میدهد، ستون chr یکی از کارکترهایی که در قرارداد هستند آمده، البته ستون سمت راست شاید خیلی آشنا به نظر نرسد چون کارکترهای خاص برای نیاز داخلی کامپیوتر هستند اما ستون دوم و سوم و چهارم کارکترهای آشنای خودمان هستند. قسمت Hex هم معادل مبنای ۱۶ (Hexadecimal) از عدد دهدهی است و نکته خاصی ندارد!
(فرض کنیم) این استاندارد کاملا جدید است، کامپیوترها در زمان ساخت هیچ چیزی در مورد اسکی نمیدانند اما با کمک برنامههایی که مینویسیم میتوانیم با اینها کار کنیم، مثلا یک عدد ۶۵ را نگه میداریم و آن را یکی اضافه میکنیم، در واقع کارکترمان از A تبدیل به B شد! اینکه چطوری این عدد را روی مانیتور نمایش میدهیم بماند برای یک قسمت دیگر!
برای کارکترهای فارسی و ایموجیهای دوست داشتنی هم از یک قرارداد مفصل تر استفاده میشود به اسم Unicode.
خوبی قرارداد ASCII این بود که همیشه در یک بایت جا میشد، اما Unicode اینطوری نیست، در واقع Unicode یک جدول بسیار بزرگ از کارکترهای همه زبانها و همهی ایموجیها دارد (character set)
اما همچنین یکسری نمایش (representation) مختلف هم وجود دارد، مثلا UTF-8 یا UTF-16 یا UTF-32 که تلاش میکنند این کارکترها را به ترتیب در یک بایت، دو بایت و چهار بایت جا دهند. (character encoding)
اینکه UTF-8 تلاش میکند هزاران کارکتر را با یک بایت نشان دهد هم جالب است، دقت کنید که تلاش میکند و لزوما موفق نمیشود! همگی در حداکثر حالت به ۴ بایت میرسند اما در جاهایی که بشود (مثلا اوایل لیست Unicode) از تعداد بایتهای کمتری استفاده میکنند.
توجه: این قسمت یه مقدار نادقیقه، استانداردهای بسیار بیشتری هم داریم که دلایل تاریخی دارند اما ایده کلی همین است که خواندید.
در قسمتهای بعدی در مورد چیزهای زیر میخوانیم:
۲− خود پردازنده چطور با ۰ و ۱ محاسبات را انجام میدهد؟
۳- رنگ، تصویر و ویدیو و کارت گرافیک چطور پردازش میشوند و نمایش داده میشوند.
۴- اطلاعات چطور منتقل میشوند. (گذری بر peripheral و لایههای شبکه)
۵- برنامه نویسی چطوری انجام میشود: کامپایلر و مفسر