فرض کنید پنج گاو داریم. در زبان فارسی، میتوانیم این عدد را بهصورت «پنج» یا «۵» نمایش دهیم. در زبان برنامهنویسی C، میتوانید از چهار نمایش برای اعداد استفاده کنید: دهدهی (مبنای ده یا دسیمال)، دودویی (مبنای دو یا باینری)، هشتهشتی (مبنای هشت یا اکتال) و شانزدهشانزدهی (مبنای شانزده یا هگزادسیمال).
به طور معمول، مردم از سیستم دهدهی یا مبنای ۱۰ استفاده میکنند، اما کامپیوترها اعداد را در سیستم بهصورت باینری یا مبنای ۲ ذخیره میکنند زیرا ساخت مدارهای باینری ارزان و راحت است. بهعنوانمثال، ممکن است دستور انتساب زیر را با استفاده از سیستم دهدهی بنویسیم:
1 | aNumber = 5; |
همین دستور را میتوان بهصورت باینری مانند زیر نوشت:
1 | aNumber = 0b101; // عدد ۵ در باینری |
پیشوند 0b نشان میدهد که یک عدد باینری بعد از آن میآید. (ما میتوانیم از 0B نیز استفاده کنیم، اما خواندن آن دشوارتر است.)
یا میتوانیم از سیستم اکتال یا مبنای ۸ هم استفاده کنیم:
1 | aNumber = 05; // عدد ۵ در هشتدهی |
در نهایت، استفاده از سیستم هگزادسیمال یا مبنای ۱۶:
1 | aNumber = 0x5; // عدد ۵ در شانزدهدهی |
اعداد باینری یا دودویی به دلیل طولانیبودن، فضای زیادی را اشغال میکنند. به همین دلیل، برای نمایش فشرده و دقیق مقادیر باینری در برنامهنویسی، از نمادگذاری هگزادسیمال استفاده میشود. هر رقم هگزادسیمال نمایندهی چهار بیت باینری میباشد، همانطور که در جدول 4-2 نشاندادهشده است.
جدول ۴-۲: تبدیل بین باینری و شانزدهدهی
باینری | شانزدهدهی | باینری | شانزدهدهی |
0000 | 0 | 1000 | 8 |
0001 | 1 | 1001 | 9 |
0010 | 2 | 1010 | A |
0011 | 3 | 1011 | B |
0100 | 4 | 1100 | C |
0101 | 5 | 1101 | D |
0110 | 6 | 1110 | E |
0111 | 7 | 1111 | F |
باتوجهبه جدول بالا، معادل مقدار هگزادسیمالی xFC0 عدد باینری b 11111100 است. همچنین، xA50 برابر با b 10100101 است. با استفاده از این جدول، میتوانید خیلی راحت باینری و شانزدهدهی را به هم ترجمه کنید.
کامپیوترها اعداد را بهصورت مجموعهای از بیتها ذخیره میکنند. هر بیت میتواند 0 یا 1 باشد.
همانطور که ما از حروف و اعداد برای نوشتن کلمات و جملات استفاده میکنیم، کامپیوتر از بیتها برای ذخیره اطلاعات استفاده میکنند.
معنای هر بیت به شیوه تفسیر ما بستگی دارد. بهعنوانمثال، الگوی بیت 0000 0101 میتواند به معنی 5 باشد، اگر آن را به عنوان یک عدد باینری تفسیر کنیم. اما 0000 0101 میتواند معانی دیگری هم داشته باشد. ما میتوانیم هر معنای دلخواه را به آن اختصاص دهیم. برای مثال، میتوانیم بگوییم 0000 0101 به معنی “حرف E” یا “LED0+LED2” و یا حتی “اردیبهشت” است.
جدول زیر یک روش نگاشت الگوی بیت را نشان میدهد که اگرچه کمتر رایج است اما همچنان کاربردی است.
الگوی بیت | معنی |
000 | 0 |
001 | 1 |
011 | 2 |
010 | 3 |
110 | 4 |
111 | 5 |
101 | 6 |
100 | 7 |
الگوی بیت به عدد
در نگاه اول، به نظر میرسد که الگو تصادفی است. اما اگر با دقت بیشتری نگاه کنید، متوجه خواهید شد که تنها یک بیت بین هر عدد تغییر میکند. این ویژگی آن را برای استفاده در انکدرها (Encoder) ایدهآل میسازد (به شکل 4-2 مراجعه کنید). در واقع، این یک الگوی استاندارد کدگذاری بیت است که به نام کد گری (gray) شناخته میشود.
زبان برنامهنویسی C بهتنهایی قادر به درک معنای یک رشته از بیتها نیست. برنامهنویس است که با تعیین نوع داده، به کامپیوتر میگوید چگونه این رشته را تفسیر کند.
اعداد صحیح استاندارد
هیچ استانداردی برای اندازه اعداد صحیح وجود ندارد. بهعبارتدیگر، در زبانهای برنامهنویسی مختلف، اعداد صحیح میتوانند اندازههای متفاوتی داشته باشند.
این موضوع میتواند مشکلاتی را ایجاد کند. برای مثال، فرض کنید شما برنامهای مینویسید که باید روی یک دستگاه ۳۲ بیتی اجرا شود. در این برنامه، شما باید از نوع عدد صحیحی استفاده کنید که 32 بیت حافظه را اشغال کند.
اما مشکل اینجاست که نمیدانید در زبان برنامهنویسی مورداستفاده شما، کدام نوع عدد صحیح 32 بیت حافظه را اشغال میکند. مجبور هستید حدس بزنید یا به مستندات زبان برنامهنویسی مراجعه کنید.
برای حل این مشکل، برنامهنویسان از تکنیکهای مختلفی استفاده میکنند. یکی از این تکنیکها، استفاده از انواع عدد صحیح استاندارد است.
انواع عدد صحیح استاندارد، اندازههای مشخصی دارند. برای مثال،int8_t یک عدد صحیح 8 بیتی ،int16_t یک عددصحیح 16 بیتی وint32_t یک عددصحیح 32 بیتی است.
با استفاده از انواع عدد صحیح استاندارد، شما میتوانید مطمئن باشید که اعداد صحیح در برنامه شما، اندازههای مشخصی دارند و بهدرستی روی دستگاههای مختلف اجرا میشوند.
مانند اکثر ایدههای خوب، این انواع جدید به طور گسترده مورداستفاده قرار گرفتند – آنقدر گسترده که کمیته استانداردهای C تصمیم گرفت آنها را با کتابخانه stdint به زبان اضافه کند؛ بنابراین باید آنها را با استفاده از دستور زیر وارد کنید:
1 | #include <stdint.h> |
لیست 4-1 انواع عدد صحیح جدید را در حال کار نشان میدهد.
در این برنامه، ما از کاراکتر قالببندی %x برای چاپ اعداد در مبنای شانزده استفاده میکنیم. به طور خاص، کاراکتر قالب %x یک int را در مبنای شانزده چاپ میکند، اما به دلیل برخی موارد پشتصحنه به نام ارتقای آرگومان، میتوانیم از آن برای int8_t، int16_t و int32_t نیز استفاده کنیم.
C زبان نسبتاً سهلانگاری است. زیرا انتقال یک عدد صحیح ۱۶ بیتی به printf زمانی که پردازنده شما رجیسترهای ۳۲ بیتی دارد، کار سختی است،C مقدار int16_t را برای همین یک عمل به int32_t تبدیل یا ارتقا میدهد که این به ما اجازه میدهد تا از %x برای یک int16_t استفاده کنیم. به طور مشابه، میتوانیم از %x برای یک int8_t استفاده کنیم؛ زیرا آن نیز به int32_t ارتقا داده میشود. (به طور دقیق، استاندارد C بیان میکند که ارتقا ممکن است رخ دهد. این کد روی ماشین x86_64 ما با این کامپایلر کار میکند، اما قابلحمل به سایر سیستمها نیست.)
حالا به سراغ int64_t میرویم. اگر C بخواهد این را به یک int (int32_t) تبدیل کند، نیمی از عدد را از دست میدهیم. C نمیتواند کاری با آن انجام دهد، بنابراین یک آرگومان int64_t را بهعنوان یک آرگومان int64_t پاس میدهد. فرمت برای چاپ مقدار طولانیتر باید از %x (int) به %lx (int64_t) تغییر کند.
بررسی برای خواننده: سعی کنید %lx را در مثال به %x تغییر دهید و ببینید چه چیزی به دست میآورید.
انواع عدد صحیح بدون علامت
در بخش قبلی، از انواع عدد صحیح با علامت استفاده کردیم که میتوانند مثبت یا منفی باشند. انواع عدد صحیح بدون علامت، فقط مقادیر مثبت را نگه میدارند و از همتایان با علامت خود سادهتر هستند. انواع بدون علامت استاندارد، عبارتاند از: uint8_t، uint16_t،uint32_t و uint64_t.
نوع uint8_t یک عدد صحیح بدون علامت ۸ بیتی است که میتواند اعداد را از ۰ (باینری: 00000000) تا ۲۵۵ (باینری: 11111111) نگه دارد. محدودههای انواع عددصحیح بدون علامت در جدول ۴-۴ نشان داده شده است.
محدودههای انواع عدد صحیح بدون علامت
نوع | مقدار کم | مقدار زیاد |
uint8_t | 0000 0000 (0) | 1111 1111 (255) |
uint16_t | 0000 0000 0000 0000 (0) | 1111 1111 1111 1111 (65,535) |
uint32_t | 0000 0000 0000 0000 0000 0000 0000 0000 (0) | 1111 1111 1111 1111 1111 1111 1111 1111 (4,294,967,295) |
uint64_t | 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 (0) | 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 (18,446,744,073,709,551,615) |
در اینجا یک مثال ساده استفاده از یک متغیر uint8_t آورده شده است. این برنامه سه نمایش مختلف را برای smallNumber چاپ میکند بدون اینکه مقدار آن را تغییر دهد:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | /* * استفاده ساده از uint8_t */ #include <stdio.h> #include <stdint.h> int main() { uint8_t smallNumber = 0x12; // یک عدد کوچک printf("0x12 is %u دسیمال\n", smallNumber); printf("0x12 is %o اکتال\n", smallNumber); printf("0x12 is %x هکس\n", smallNumber); return (0); } |
دستور خروجی printf یعنی %u به C میگوید که میخواهیم یک عدد صحیح بدون علامت (نوع پیشفرض عدد صحیح بدون علامت) چاپ کنیم. ما از دستور خروجی %o برای چاپ در مبنای هشت و %x برای چاپ در مبنای شانزده استفاده میکنیم.
خروجی این برنامه بهصورت زیر است:
0x12 is 18 دسیمال
0x12 is 22 اکتال
0x12 is 12 هکس
سرریز (Overflow)
حالا به محدودیتهای ماشین خود میپردازیم. در واقع، ما از آنها فراتر خواهیم رفت. بزرگترین عدد uint8_t برابر با ۲۵۵ است (باینری: 0b1111 1111). چه اتفاقی میافتد که فراتر از آن برویم و سعی کنیم ۲۵۵ + ۱ را چاپ کنیم؟ بیایید امتحان کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | /* * ببینید چه اتفاقی میافتد وقتی از حداکثر تعداد عبور میکنیم. * (حاوی یک اشتباه) */ #include <stdio.h> #include <stdint.h> int main() { uint8_t smallNumber = 255; // عدد صحیح بسیار کوچک، روی حداکثر تنظیم شده است printf("255+1 is %u\n", smallNumber + 1); return (0); } |
باتوجهبه این برنامه، نتیجهی اضافهکردن ۱ به عدد صحیح بدون علامت ۸ بیتی ۲۵۵، برابر با ۲۵۶ است، اما عدد ۲۵۶ در باینری برابر با b100000000 یا x1000 است که نمیتواند در ۸ بیت جای گیرد. یا ما قوانین جهان هستی را تحریف کردهایم یا مشکلی در برنامه ما وجود دارد.
برای درک اینکه چه اتفاقی در حال رخدادن است، بیایید نگاهی به دستور خروجی چاپ (خط ۱) بیندازیم. نوع smallNumber برابر با uint8_t است؛ بااینحال، در اکثر کامپیوترهای ۳۲ بیتی، جمعکردن دو عدد صحیح ۸ بیتی دشوار است، اگر غیرممکن نباشد. به دلیل نحوه ساخت کامپیوتر، شما مجبور هستید دو عدد ۳۲ بیتی را جمع کنید؛ بنابراین، برای محاسبه یک عبارت، کامپایلر C کارهای زیر را انجام میدهد:
- smallNumber را به یک مقدار uint32_t تبدیل میکند.
- ۱ را به نتیجه (از نوع uint32_t) اضافه میکند.
- نتیجه (۲۵۶) را بهعنوان یک uint32_t چاپ میکند.
نتیجه یک uint32_t است که میتواند مقدار ۲۵۶ را نگه دارد و این همان چیزی است که چاپ میشود؛ بنابراین، ما باعث سرریز ۸ بیتی (که میخواستیم نشان دهیم) نشدیم. در عوض، ارتقای خودکار را که قبلاً در این فصل بحث کردیم، نشان دادیم.
برای اینکه نتیجه موردنظر را به دست آوریم، باید یک تغییر کوچک در برنامه ایجاد کنیم تا نتیجه را در یک مقدار uint8_t ذخیره کنیم (تغییرات در برنامه را با حروف درشت برجسته کردهام):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | /* * ببینید چه اتفاقی میافتد وقتی از حداکثر تعداد عبور میکنیم. */ #include <stdio.h> #include <stdint.h> ... int main() { uint8_t smallNumber; uint8_t result; smallNumber = 255; result = smallNumber + 1; printf("255+1 is %d\n", result); return (0); { |
حالا نتیجه ۰ است. چرا؟ چون محاسبه ما 0b1111 1111 + 0b000 0001 = 0b1 0000 0000 را برگرداند. فقط قسمت برجسته به دلیل فضای محدود برای ذخیره متغیر، ذخیره شد.
سرریز (Overflow) زمانی اتفاق میافتد که نتیجه برای پردازش توسط ماشین خیلی بزرگ باشد. در این مورد، نتیجه ۹ بیتی در یک مقدار ۸ بیتی جای نمیگیرد. به کیلومترشمار یک ماشین فکر کنید. این میتواند مسافت را تا ۹۹۹,۹۹۹ نمایش دهد. اگر کسی یک میلیون مایل رانندگی کند چه اتفاقی میافتد؟
درک اینکه چگونه کامپایلر اعداد را دستکاری میکند، کلید ساخت برنامههای تعبیهشده (Embedded) است. برای مثال، من یکبار یک GPS داشتم که ارتفاع را بهصورت یک عدد بدون علامت نگه میداشت. (برای کار در زیر سطح دریا طراحی نشده بود.) آن را به سفرم در درهای بردم و از کار افتاد. دلیل این است که وقتی به اعماق دره، ارتفاع ۲۸۲- فوت رسیدم نمیتوانست ارتفاع منفی را تحمل کند. طراحان GPS فرض کرده بودند همه ارتفاعات بیشتر از صفر خواهند بود. ازاینگذشته، GPS برای کار در زیر سطح دریا طراحی نشده بود؛ بنابراین، استفاده از یک عدد صحیح بدون علامت برای ارتفاع یک تصمیم غیرمنطقی نبود – به جز برای کاربرانی که در مکانهایی مانند دره قرار دارند، جایی که ارتفاع منفی است و باعث ازکارافتادن GPS میشود. این اشتباه نشان میدهد که چرا دانستن محدودیتهای سیستم شمارهگذاری شما مهم است.