درک و فهم کامل از نحوه برخورد کامپیوتر ها با اعداد بحث بسیار مهمی هستش و میشه گفت شاید یکی از ستون های اصلی علوم کامپیوتر و نرم افزار باشه این بحث، البته این نکته رو همینجا توی شروع کار بگیم که منظور ما از اعداد در واقع اعداد صحیح هستش توی لغت ریاضیش، یا integral data type توی لغت کامپیوتریش. مبنای کارکرد اکثر کامپیوتر ها معمولا به شکل دودویی یا باینری هستش یعنی کامپیوتر ها فقط اعداد ۰ و ۱ رو میفهمن که توی این پست ما میخوایم بریم ببینیم که اعداد صحیح چطوری توی یه کامپیوتر دودویی نمایش داده میشن، چطور دخیره میشن و چطوری عمل های محاسباتی مثل ضرب و تقسیم روشون انجام میشه.


دیتا تایپ Integer

به نوع دیتای اعداد صحیح توی کامپیوتر و نرم افزار عموما integer (اینتیجر) میگن، یه integer میتونه یا صفر باشه یا یه عدد مثبت باشه یا یه عدد منفی و هیچ وقت اعشاری نمیشه، اغلب توی زبان های برنامه نویسی static-type اینتیجر ها به دو دسته تقسیم میشن:

۱.اعداد Unsigned: فقط شامل اعداد مثبت (صفر و بالاتر) میشه.

۲.اعداد Signed: شامل اعداد مثبت به علاوه اعداد منفی (منفی یک و پایین تر) میشه.

که این integer ها به شکل یک رشته ای از اعداد دودویی یعنی صفر و یک نمایش داده میشن، و هر چقدر طول این رشته بزرگ تر باشه یعنی صفر و یک های بیشتری پشت سر هم ردیف بشن ما میتونیم اعداد بزرگ تری رو باهاش نمایش بدیم.


شکل باینری Integer ها

همونطور که بالاتر هم اشاره کردیم کامپیوتر ها از سیستم عددی دودویی استفاده میکنن یعنی فقط اعداد صفر و یک که بر مبنای دو هستند رو میفهمن، به هر کدوم از این اعداد صفر و یک، یک bit (بیت) میگن که این bit دوباره خودش مخفف binary digit یعنی اعداد دودویی هستش، این اعداد با کنار هم قرار گرفتن و تشکیل یک رشته میتونن اعداد صحیح بزرگ تر از یک رو نشون بدن و موقعیتشون توی این رشته مقدارشون رو مشخص میکنه مثلا عدد چهارم از راست توی این رشته با به توان رسوندن ۲ به ۴ منفی ۱ که سه میشه، بدست میاریم، که مقدارش میشه ۸: 1000 = 2^3 = 8

شکل باینری اعداد شکل باینری اعداد

اعداد Unsigned

یک عدد Unsigned میتونه از تمام بیت های یک رشته استفاده کنه تا یک عدد مثبت رو توی خودش نگه داره، برای مثال با یک رشته ۴ بیتی ما میتونیم این رنج از اعداد رو نمایش بدیم:

کمترین - باینری: 0000 = عدد صحیح: 0

بیشترین - باینری: 1111 = عدد صحیح: 15


اعداد Signed

اعداد Signed باید بتونن هم مقادیر مثبت رو نشون بدن و هم مقادیر منفی رو، برای این مورد یکی از مرسوم ترین راه هایی که وجود داره اینه که یک بیت رو (معمولا پر ارزشترین بیت که میشه اولین بیت از سمت چپ یا به اصطلاح MSB, Most Significant Bit) به عنوان بیت sign یا علامت رزرو کنیم و از اون استفاده کنیم برای مشخص کردن منفی یا مثبت بودن اعداد، با توجه به مقدارش:

  • 0 مشخص میکنه که این یه عدد مثبته
  • 1 مشخص میکنه که این یه عدد منفیه

متود های انکود اعداد Signed

به دلیل اینکه اعداد Signed نیاز دارن که بتونن به طور همزمان هم اعداد منفی رو نشون بدن و هم اعداد مثبت رو، چالش مختلفی رو این موضوع وجود داشت و در طی این زمان متود ها و تکنیک های مختلفی ابداع شد که چند تا از مهم ترین هاشون رو الان با هم برسی میکنیم:

۱. متود Sign-Magnitude Representation یا Sign-Bit Representation: ساده ترین و یکی از اولین متود ها برای مشخص کردن اعداد منفی روش Sign-Magnitude بود که کامپیوتر های نسل اول مثل IBM ۷۰۹۰ از این روش استفاده میکردن، روش نمایشش خیلی ساده است و مثل روش نمایش ریاضیش که یه sign مثل ۱- یا ۱+ که پشت عدد میزاریم اینجا هم پر اهمیت ترین بیت (MSB) یعنی اولین بیت از سمت چپ رو به عنوان بیت sign در نظر میگیریم، اگر این بیت مقدارش ۱ باشه عدد ما منفیه و اگر ۰ باشه عدد ما مثبته، مثلا:

باینریعدد صحیح
00000+
00011+
00102+
00113+
01004+
01015+
01106+
01117+
10000-
10011-
10102-
10113-
11004-
11015-
11106-
11117-

اما خب این روش مشکلات و محدودیت های زیادی داشت مثلا:

  • توی این روش ما دو تا صفر داشتیم یکی صفر مثبت (0000) و یکی صفر منفی (1000)
  • توی عملیات های حسابی مثلا جمع یا تفریق ما بر اساس sign-bit رفتار و لاجیک متفاوتی نیاز داشتیم، مثلا توی اعداد منفی برای اضافه کردنی عددی به یک بیت نیاز داشتیم که مقدار بیت اون عدد رو کم کنیم ازش و توی اعداد مثبت این بر عکس میشد و باید اضافه میکردیم مقدار بیتش رو که این باعث ایجاد پیچیدگی توی پیاده سازی سخت افزاری و نرم افزاری میشد:
باینریعدد صحیحباینریعدد صحیحباینریعدد صحیح
عدد اول00102+01117+11015-
عدد دوم10102-10102-00113+
حمع باینری11004-00011+00000+
جمع صحیح0+5+2-
  • بحث signed-extension هم دقیقا مثل بحث عملیات های حسابی نیاز به لاجیک پیچیده ای داشت:
عدد صحیح۴-بیت۵-بیت۶-بیت
2+001000010000010
7+011100111000111
2-101010010 (! = 11010)100010 (! = 111010)
7-111110111 (! = 11111)100111 (! = 111111)

۲. متود One’s Complement: دقیقا مثل روش قبلی اینجا هم از پر اهمیت ترین بیت (MSB) استفاده میکنیم برای نمایش اعداد منفی با این تفاوت که مقدار صحیح اعداد میشه مکمل (NOT) عددی که توی رشته بیت نمایش داده میشه یعنی مثلا اگر رشته بیت ما داشت این مقدار رو نشون میداد: 1010 ما برای اینکه عدد صحیحش رو به دست بیاریم باید هر کدوم از بیت هارو بر عکس کنیم یعنی اگر ۰ بود بکنیم ۱ و اگر ۱ بود بکنیم ۰ که برای موردی که مثال زدیم میشه: 0101 = 5 یعنی رشته بیت 1010 داره عدد ۵- رو نشون میده.

شکل باینریمقدار صحیح
متود One’s complementمتود Signed bit
00000+0+
00011+1+
00102+2+
00113+3+
01004+4+
01015+5+
01106+6+
01117+7+
10007-0-
10016-1-
10105-2-
10114-3-
11003-4-
11012-5-
11101-6-
11110-7-

با این روش عملیات های حسابی رو ساده تر میشن و بحث signed-extension به شکل ساده ای امکان پذیر میشه، اما همچنان اینجا هم ما دوتا صفر داریم و این مشکل همچنان پا برجاست.


۳. متود Two’s Complement: این روش در حال حاضر استاندارد اصلی و متداول ترین روش نمایش اعداد توی کامپیوتر هستش، نحوه نمایشش دقیقا شبیه به One’s Complement هستش با این تفاوت که بعد از محاسبه مکمل نتیجه رو به علاوه یک میکنیم:

شکل باینریمقدار صحیح
متود Two’s complementمتود One’s complementمتود Signed bit
00000+0+0+
00011+1+1+
00102+2+2+
00113+3+3+
01004+4+4+
01015+5+5+
01106+6+6+
01117+7+7+
10008-7-0-
10017-= inverse of 7 +1-bit6-1-
10106-= inverse of 6 +1-bit5-2-
10115-= inverse of 5 +1-bit4-3-
11004-= inverse of 4 +1-bit3-4-
11013-= inverse of 3 +1-bit2-5-
11102-= inverse of 2 +1-bit1-6-
11111-= inverse of 1 +1-bit0-7-

بحث Overflow

وقتی که نتیجه یه عمل حسابی (مثلا جمع) از گنجایش نمایش یه رشته بیت بیشتر باشه رشته بیت ما سر ریز یا به اصطلاح Overflow میکنه، مثلا توی یه رشته بیت 8 تایی اگر به بزرگ ترین عدد قابل نمایش یعنی 255 یک بیت اضافه کنیم رشته بیت ما Overflow میکنه و نتیجه میشه صفر، یه مثال هم بخوایم بزنیم توی دنیای واقعی اگه تا حالا به این شمارنده های مکانیکال مثل این صلوات شمار ها یا کیلومتر شما دقت کرده باشید وقتی عدد به ۹۹۹۹۹ میرسه اگه یکی دیگه بهش اضافه بشه عدد میشه صفر و درواقع Overflow میکنه.

کیلومتر شمار کیلومتر شمار

البته Overflow فقط توی جمع اتفاق نمیفته، توی منها هم ممکنه اتفاق بیفته، البته رفتار با توجه به متود مورد استفاده برای نمایش اعداد Signed ممکنه متفاوت باشه، مثلا توی یه رشته بیت 8 تایی طبق متود Two’s Complement اگر بخوایم کوچک ترین عدد ممکن یعنی ۱۲۸- رو منهای یک بکنیم Overflow اتفاق میفته و نتیجه میشه 127+ . تشخیص این موضوع در سطح سخت افزار معمولا هندل میشه و توی اکثر موارد سخت افزار Overflow رو تشخیص میده و با Condition Code Register ها به نرم افزار اطلاع داده میشه، توی متود Two’s Complement یه فرمول خیلی ساده برای تشخیص این موضوع وجود داره که میگه اگر توی یک عمل محاسباتی مثل جمع بیت انتقالی ورودی به sign bit برابر نباشه با بیت انتقالی خروجی Overflow اتفاق افتاده، یعنی اگر یک جمعی باعث شد که یه بیت به sign bit انتقال پیدا کنه و مقدار sign bit هم صفر باشه، Overflow اتفاق میفته.

باینریعدد صحیحباینریعدد صحیحباینریعدد صحیحباینریعدد صحیح
عدد اول10115-0010201117+10115-
عدد دوم11004-0110611102-00113
جمع(1) 0111(0)1000(1)0101(0)1110
بیت ورودی به sign-bit0overflow میکنه1overflow میکنه1overflow نمیکنه0overflow نمیکنه
بیت خروجی از sign-bit1010

نمایش Integer ها در زبان های برنامه نویسی

زبان +C/C

ما توی C تایپ های مختلفی برای Integer ها داریم که معمولا سایزشون رو مشخص میکنه مثلا: int, short long و long long، البته سایز نهایی وابسته است به معماری و word size سیستمی که داره بیلد میگیره برنامه رو، بحث Signed بودن یا Unsigned بودن هم به طور واضح با تایپ های signed یا unsigned مشخص میشه.

زبان Go

تو گولنگ دقیقا مثل C ما برای هر سایزی از Integer تایپ های مختلفی داریم با این تفاوت که سایز هم به طور مشخص توی اسم تایپ وجود داره مثلا int8, int16, int32, int64 برای اعداد Signed و برای اعداد Unsigned هم به شکل uint8, uint16, uint32, uint64 هستش.

زبان Java

توی Java برعکس زبان های بالا ما تنوع زیادی توی تایپ های Integer نداریم و دستمون بسته است، کلا دوتا تایپ داریم به اسم int و long که به ترتیب 32 و 64 بیتی هستن و هردو با دیفالت signed هستن و هیچ شکلی از اعداد Unsigned توی جاوا وجود نداره.

مؤخره

برای مباحث درک عمیق کارکرد اپلیکیشن ها یا بهبود پرفورمنس اپلیکیشن ها یا همچنین دیزاین و توسعه یه نرم افزار با کیفیت، یادگیری فهم نحوه کارکرد اعداد توی کامپیوتر یکی از نیاز های مهمی هستش که باید بهش مسلط باشیم، اینکه بفهیم توی سطوح پایین اعداد چطوری نمایش داده میشن یا عمل های محاسباتی چطور روشون اجرا میشه کمک میکنه که برنامه ها یا الگوریتم هایی بنویسیم که خیلی کارآمد تر باشه همچنین با درک این موضوع میتونیم از مشکلات رایجی مثل Overflow جلوگیری کنیم، البته این نکته رو هم بگم درک صرفا این موضوع کافی نیست برای مواردی که گفتیم، اگر علاقه دارید به این موضوعات پیشنهاد میکنم کتاب Computer Systems: A Programmers Perspective رو تهیه کنید و بخونید، مطالب جالب و جامعی توش پیدا خواهید کرد.