کدنویسی ماشین حساب سیستم عددی در پایتون – از صفر تا صد


در این مقاله یک ماشین حساب سیستم عددی در پایتون میسازیم که با استفاده از آن میتوانیم هر عددی را از هر مبنایی به مبنای دیگر تبدیل کنیم. «مبنا» (Base) در سیستمهای عددی به ما میگوید که چه تعداد رقم در اختیار داریم. سیستم «دودویی» (Binary) از دو رقم تشکیل یافته است. سیستم «هشتهشتی» (Octal) از هشت رقم تشکیل یافته و سیستم «دهدهی» یا «اعشاری» (Decimal) نیز دارای 10 رقم است.
برنامهای که میخواهیم بنویسیم میتواند هر عدد را در هر مبنایی که به آن میدهیم مدیریت کند. تنظیم چنین سیستمی با درک ریاضیات پسزمینه آن کار سادهای است. چیزی که میخواهیم طراحی کنیم. در نهایت مانند تصویر زیر خواهد بود:
آشنایی با ریاضیات سیستمهای عددی
ماشین حسابی که در این مقاله میخواهیم طراحی کنیم، همانند هر ماشین حساب دیگری بر مبنای ریاضیات کار میکند. بنابراین باید دانش نظری خودمان در مورد تبدیل مبناهای عددی را یادآوری کنیم.
اگر در مدرسه با ریاضیات سرو کار داشته یا هر نوع تحصیلات آکادمیک در رشته کامپیوتر داشتهاید، حتماً تاکنون با عملیات تبدیل یک عدد از یک مبنا به مبنای دیگر آشنا شدهاید و احتمالاً جدولهای دودویی یا صحبت از سیستم «دهدهی»، «هشتهشتی» و «شانزدهشانزدهی» را به خاطر میآورید.
سیستم اعشاری (مبنای 10) سیستمی است که به طور روزمره برای شمارش همه چیز از آن استفاده میکنیم. ارقام این سیستم شامل 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 هستند. زمانی که این ارقام پایان یابند، موقعیت دیگری در سمت چپ عدد اضافه میکنیم و اعداد را به صورت 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 و غیره ادامه میدهیم. زمانی که دوباره این اعداد پایان یابند، یک موقعیت جدید دیگر اضافه میکنیم و اعداد را به صورت 100, 101, 102…118, 119, 120 اضافه میکنیم.
در مورد فرایند ساخت این اعداد تأمل زیادی نمیکنیم، چون از بدو کودکی با آنها آشنا شدهایم، اما اگر از یک کودک بخواهید اعداد را در سیستم «هشتهشتی» بشمارد، بعید است موفقیت چندانی داشته باشد. اما تئوری همه سیستمهای عددی آسان بوده و دقیقاً مشابه سیستم دهدهی است که به استفاده از آن چنین خو کردهایم.
شمارش در سیستم هشتهشتی به صورت زیر است:
0,1,2,3,4,5,6,7 → 10,11,12,13,14,15,16,17 → 20,21…
تبدیل از یک مبنا به مبنایی دیگر
در ابتدا تبدیل اعداد از مبناهای دیگر به مبنای 10 را بررسی میکنیم. تبدیل از سیستم دهدهی به سیستمهای دیگر کار بسیار آسانی است. از این رو بهتر است به بررسی تبدیل اعداد به سیستم دهدهی بپردازیم.
اگر دو سیستم دهدهی و هشتهشتی را در نظر بگیریم، برای آشنایی با روش تبدیل یک عدد از سیستم هشتهشتی به سیستم دهدهی یک مثال را به صورت زیر بررسی میکنیم:
256(Base-8) →???(Base-10)
ما در عدد هشتهشتی خود سه موقعیت داریم که رقم 2 در موقعیت صدگان، 5 در موقعیت دهگان و 6 در موقعیت یکان است. این موقعیتها میتوانند ارقامی بین 0 تا 7 را داشته باشند. ما باید هر یک از این ارقام را به در همه موقعیتها به سیستم دهدهی تبدیل کنیم. سیستم دهدهی دارای ده رقم از 0 تا 9 است.
روش انجام این تبدیل آن است که هر یک از اعداد را برداشته و آن را در مبنا به توان اندیس موقعیت ضرب میکنیم. اگر از کمترین رقم سمت راست آغاز کنیم، رقم 6 را در ، رقم 5 را در و رقم 2 را در ضرب میکنیم. برای درک بهتر موضوع مراحل کار در تصویر زیر به نمایش درآمده است:
زمانی که عددی را در سیستم دهدهی داریم، تبدیل آن به مبناهای دیگر کار بسیار آسانی محسوب میشود. برای مثال فرض کنید میخواهیم عدد 174 را از مبنای 10 به مبنای 8 تبدیل کنیم که نتیجه 256 را ایجاد میکند.
برای یافتن اعداد در مبناهای دیگر، عدد را به طور مکرر بر آن مبنا تقسیم میکنیم و در هر مرحله باقیماندهها را نگه میداریم. این باقیماندهها عدد را در مبنای دیگر برای ما تشکیل میدهند. زمانی که باقیماندهها به دست آمدند، عدد را از کمارزشترین رقم به پرارزشترین رقم یعنی از انتها به بالا میخوانیم. بدین ترتب عدد جدید در مبنای جدید به دست میآید.
174/2 نتیجه 21.75 را به دست میدهد. چنان که میبینید 21 به رنگ قرمز و 0.75 به رنگ سبز است. ما 21 را به خط بعدی میفرستیم و با 0.75 کار میکنیم. اگر 0.75 را در مبنا ضرب کنیم باقیمانده به دست میآید. این عدد به سمت راست ارسال میشود و رنگ آن آبی است. زمانی که به 0 برسیم رسماً کار انجام یافته است و عدد را در مبنای جدید به دست آوردهایم.
به این ترتیب نه تنها میتوانیم از مبنای 10 به مبنای 8 برویم، بلکه به طور معکوس نیز میتوانیم عمل کنیم. ما میتوانیم از مبنای X به مبنای Y برویم.
کدنویسی
اینک نوبت به کدنویسی رسیده است. در این مقاله اغلب بخشهای کد را به صورت دستی مینویسیم و از کتابخانهها استفاده نمیکنیم، زیرا میخواهیم بیشترین حد از یادگیری را داشته باشیم. در این کد از تابعهای پایتون مانند ()bin استفاده نکردهایم، زیرا میخواهیم همه چیز را به صورت دستی بنویسیم. در محیط پروداکشن معمولاً باید همه چیز در حد امکان کوتاه باشد و بنابراین به جای کدنویسی دستی همه بخشها از کتابخانهها برای حل مسائل استفاده میکنیم.
این برنامه صرفاً یک فایل به نام main.py است و همه کدها در یک سند قرار دارند. البته شما میتوانید آن را بسته به میل خود بسط دهید. مثلاً یک کلاس Number داشته باشید و حتی GUI نیز به آن اضافه کنید. پیشنهاد میکنیم در زمان کدنویسی با یک مبدل آنلاین سیستمهای عددی نیز کار کنید تا نتایج برنامه را با آن تطبیق بدهید. کد به صورت زیر است:
منطق پشت این کد آن است که سه آرگومان را به شرح زیر به تابع ()convert_number_system ارسال میکنیم:
- input_number عددی است که میخواهیم تبدیل شود.
- input_base مبنایی است که از آن تبدیل میکنیم.
- output_base مبنایی است که به آن تبدیل میکنیم.
()def menu
منویی به صورت زیر ایجاد میکنیم. این تابع کل رشته منو را بازگشت میدهد و از این رو میتوانیم آن را هر جایی که میخواهیم تنظیم کنیم. لازم نیست در مورد newline نیز نگران باشیم.
تابعهای اعتبارسنجی
به جای این که ورودی را در تابع مبدل بررسی کنیم، یک سری تابعهای اعتبارسنجی ایجاد میکنیم که این کار را برای ما انجام میدهند. بدین ترتیب کد ما قابلیت استفاده مجدد مییابد.
(def validate_bin(check_number
این تابع بررسی میکند آیا عدد یک عدد دودویی معتبر است یا نه.
ما نمیخواهیم یک رشته طولانی را که شامل چندین بار تکرار یک عدد است، بررسی کنیم. اگر آن را به یک set تبدیل کنیم، موارد تکراری پاک میشوند. Set-ها نمیتوانند چند وهله تکراری از یک آیتم را نگهداری کنند. ما از «خلاصهسازی لیست» (list comprehension) برای تبدیل ورودی به یک int و بررسی آن با [0,1] استفاده میکنیم:
همچنین رشتهها را از ورودی کاربر میگیریم و مقادیر integer را از ورودی مورد نیاز میسازیم. (int(item موجب میشود مطمئن شویم که همه اعداد به صورت عدد صحیح (Integer) هستند. عملکرد ما در پسزمینه به صورت زیر است:
‘10010011’ → [‘1’,’0',’0',’1',’0',’0',’1',’1'] → [0,1]
اگر 0 در [0,1] باشد، بررسی نتیجه مطلوب را به دست میدهد، اگر 1 در بازه [0,1] باشد باز نتیجه مطلوب به دست میآید. اگر عددی مانند 23 داشته باشیم که هر دو رقم شکست بخورند، تابع مقدار false را با استفاده از تابع Return در پایتون بازگشت میدهد.
کد فوق نکته خاصی ندارد و بررسی میکند آیا ورودی شامل کاراکترهای منطقی تعریف شده است یا نه. ما با ارقام 0-9 و به دلیل وجود سیستم HEX از حروف a-f نیز به عنوان ورودیهای معتبر استفاده میکنیم.
(def validator(input_number,input_base,output_base
این تابع از تابعهای اعتبارسنجی دیگر استفاده کرده و همه ورودیها را اعتبارسنجی میکند. بدین ترتیب اگر نکتهای در پیش از ادامه کار وجود داشته باشد متوجه میشویم.
- ابتدا بررسی میشود آیا یک عدد یا یک مقدار HEX وارد شده است.
- سپس مبناها بررسی میشود. اگر برای input_base مقدار 2 وارد شده باشد، یعنی عدد باینری را تبدیل میکنیم. از این رو input_number باید صرفاً شامل ارقام 0 و 1 باشد.
- اگر ورودی شامل حرف و رقم باشد یک عدد شانزدهشانزدهی است. بنابراین اگر input_base مقدار 16 نباشد، نمیتوانیم آن را تبدیل کنیم. HEX یک عدد با مبنای 16 است و هر مبنای دیگری محاسبه نادرستی به دست میدهد. میتوانیم یک ورودی از پیش کامل شده در طی تعامل با کاربر و زمانی که عدد شانزدهشانزدهی وارد میکند نیز داشته باشیم.
- در نهایت بررسی میکنیم آیا کاربر تلاش کرده از مبنای 1 یا به آن تبدیل کند یا نه. این کار امکانپذیر نیست و از این رو به جای کرش کردن برنامه یک خطا بازگشت میدهیم.
تبدیل
اکنون به بخش اصلی کد خود میرسیم. این همان جایی است که باید از مهارتهای ریاضیاتی خود استفاده کنیم. این تابع عملیاتی که قبلاً بررسی کردیم را اجرا میکند و چند گام امنیتی نیز دارد تا مطمئن شویم که خروجی صحیحی ارائه میکنیم.
(...,def convert_number_system(input_number
- remainder_list تعدادی که میخواهیم بازگشت دهیم را شامل میشود. اگر بخش مباحث ریاضیاتی ابتدای مقاله را به خاطر داشته باشید، بیدرنگ میتوانید کلیدواژه remainder یعنی باقیمانده را تشخیص دهید.
- sum_base_10 ما میخواهیم از مبنای 10 به عنوان یک گام میانی استفاده کنیم. مقدار اولیه را روی 0 تنظیم میکنیم و سپس همه مقادیر را بر مبنای آن محاسبه میکنیم.
خروجی دودویی
اگر کاربر بخواهد یک خروجی دودویی داشته باشد، میتوانیم از تابع داخلی ()bin استفاده کنیم. لزومی به این کار وجود ندارد، زیرا مبانی ریاضیات آن یکسان است و مهم نیست چه مقداری ارسال میکنیم، اما بررسی آن ضرری ندارد.
()bin مقدار 0b بازگشت میدهد که دودویی است. مقدار 0b به ما اعلام میکند که یک عدد دودویی است. ما باید یک عدد واقعی بازگشت دهیم. به همین جهت از [2:] استفاده میکنیم. اگر کد را در ترمینال اجرا کنید، تفاوت را میبینید:
>>> check_number = 23 >>> print(bin(check_number)) 0b10111 >>> print(bin(check_number)[2:]) 101114
این کد از اندیس 2 به بعد را پرینت میکند.
توجه کنید که نسخه ()hex را در کد قرار دادهایم، اما کامنت شده است. شما میتوانید در صورت نیاز از آن استفاده کنید.
زمانی که مبنا 10 نباشد
اگر مبنا 10 نباشد، از گام میانی خود استفاده میکنیم. ابتدا لیست را معکوس میکنیم. معکوس کردن لیست در پایتون از طریق کد داخلی [::-1] میسر است:
reversed_input_number = input_number[::-1]
hex_helper_dict به ما کمک میکند که اعداد بالاتر از 9 را در صورت ورود مقدار hex مدیریت کنیم. اگر فرمول را به خاطر داشته باشید، باید اعداد را در هر موقعیت با اندیس مبنا به توان موقعیت ضرب کنیم. برای تبدیل عدد 256 از مبنای هشت به ده به صورت زیر عمل میکنیم:
2*(8²) + 5*(8¹) + 6*(8⁰) = 174
اگر عدد مانند 23e در مبنای شانزده باشد، در عمل به صورت 2، 3 و 14 خوانده میشود:
2*(16²) + 3*(16¹) + 14*(16⁰) = 574
این حلقه همه این کارها را برای ما انجام میدهد:
در کد فوق میبینید که هم از enumerate و هم از ()items. استفاده کردهایم تا مطمئن شویم که میتوانیم به همه موارد لازم برای اجرای عملیات دسترسی داشته بباشیم.
با استفاده از enumerate میتوانیم هم به مقدار و هم اندیس متغیر دسترسی پیدا کنیم. زمانی که روی متغیر حلقه تعریف میکنیم بررسی میکنیم آیا عدد معادل کلیدی در دیکشنری است یا نه. در مورد 23e چنین است و باید عدد را به جای آن روی 14 تنظیم کنیم تا بتوانیم از محاسبات واقعی بهره بگیریم.
sum_base_10 += (int(number)*(int(input_base)**index))
اکنون که مطمئن شدیم هیچ عددی در واقع حرف نیست، میتوانیم روی ارقام بچرخیم و عملیات را اجرا کنیم. برای هر رقم یک بار حلقه را اجرا کرده و ضرب را به طوری که قبلاً مطرح شد اجرا میکنیم.
این مقادیر در نهایت به sum_base_10 اضافه میشوند
زمانی که مبنا 10 باشد
تا اینجا ما موفق شدهایم عدد ورودی را به مبنای 10 (sum_base_10) ببریم.
عملیات ریاضی
اکنون که مقدار را در مبنای 10 داریم، میتوانیم تقسیم را اجرا کنم تا باقیماندهها را یافته و عدد جدید را به دست آوریم.
ما باید تا زمان رسیدن به باقیمانده 0 تقسیم را ادامه بدهیم. این حلقه حصول چنین نتیجهای را تضمین میکند.
با استفاده از تقسیم floor یعنی عملگر // اعداد تقسیم میشوند و بخش صحیح خارجقسمت جدا میشود. این بدان معنی است که تنها عدد 21 انتقال مییابد و بخش اعشاری 0.75 در مثال اولیه مقاله محاسبه نمیشود. با انتساب این عدد به مقسوم میتوانیم این مقدار را به sum_base_10 بفرستیم تا در چرخه بعدی تقسیم استفاده شود.
با استفاده از عملگر % باقیمانده را به دست میآوریم. این مقدار به remainder_list الحاق میشود و میتوانیم آن را بعداً در خروجی ارائه کنیم. این یک حلقه while است که تا زمان رسیدن به مقدار صفر ادامه مییابد.
زمانی که مبنا 16 باشد
اگر مقدار output_base برابر با 16 باشد، به آن معنی است که خروجی باید شانزدهشانزدهی باشد. بدین ترتیب باید همه ارقام بالاتر از 9 را به حرف تبدیل کنیم:
همانند قبل یک دیکشنری برای تسهیل تبدیل ایجاد میکنیم. اگر یک مورد تطبیق پیدا شد، رقم را به حرف تبدیل کرده و به لیست الحاق میکنیم.
در نهایت خروجی به صورت زیر ارائه میشود:
return ''.join(remainder_list[::-1])
کد فوق لیست را معکوس میسازد و به صورت رشتهای درمیآورد که امکان بازگشت آن وجود دارد. اگر بخواهید یک مقدار int بازگشت دهید، میتوانید از ()int استفاده کنید. در هر حال در صورتی که کاربر عددی در مبنای شانزده میخواهید مطمئن شوید که ارقام Hexadecimal را لحاظ کردهاید.
()def execute_converter
اینک بخش غالب کد ما به پایان رسیده است. بخش آخر پازل تعامل با کاربر است. این بخش شامل یک حلقه while سرراست است که کاربر اعدادی را در آن وارد میکند و ما آنها را اعتبارسنجی کرده و در صورت پاس شدن اعتبارسنجی مبدل را اجرا میکنیم. کاربر این گزینه را در اختیار دارد که هر چه قدر میخواهد اعداد را وارد کند.
معمولاً بهتر است تعاملهای با کاربر را در حلقههای while با استفاده از یک متغیر مانند proceed که اینجا استفاده کردهایم اجرا کنیم. بدین ترتیب کاربر میتواند هر چه قدر میخواهد عدد وارد کند. این تعامل دو سطح دارد. حلقه while اول حلقه main را با کد 'proceed.lower() == ‘y مقداردهی میکند.
حلقه دوم valid_input=False را اعلان میکند، زیرا میخواهیم همه ورودیها را پیش از ارسال آن به ()convert_number_system اعتبارسنجی کنیم. تا زمانی که نتیجه مقدار False دارد باید ورودی جدیدی وارد شود. پس از آن که کاربر ورودی خود را ارائه کرد، تابع اعتبارسنجی اجرا میشود. اگر اعتبارسنجی پاس شود، میتوانیم ()convert_number_system را مقداردهی کنیم.
در نهایت از کاربر میخواهیم که اگر تمایل دارد عدد دیگری را امتحان کند. اگر کاربر مقدار y وارد کند کد دوباره اجرا میشود. با وارد کردن هر مقدار دیگری برنامه پایان مییابد.
به عنوان یک روش جایگزین میتوانیم از کاربر بخواهیم که عدد مورد نظر خود برای تبدیل را وارد کرده و یا از برنامه خارج شود. این کار در متغیر number_input انجام میشود.
سخن پایانی
برنامه ما چندین جنبه مهم دارد. نخست باید مسئله را پیدا کنیم. مسئله این است که تبدیل دستی اعداد به سیستمهای عددی مختلف کاری زمانگیر است. سپس روش ریاضیاتی حل مسئله را مییابیم. چنان که مشخص شد با استفاده از فرمول مورد بحث میتوانیم هر عددی را به عدد دیگر تبدیل کنیم. کافی است ورودی دودویی و شانزدهشانزدهی را مدیریت کنیم و مطمئن شویم که مبنای 1 را محاسبه نمیکنیم. این بدان معنی است که از تابعها برای مدیریت کد استفاده میکنیم. همه کد برنامه درون یک منو قرار گرفته است که با کاربر تعامل مییابد و کد اجرایی زیرین در پسزمینه اجرا میشود. امیدواریم از مطالعه این مقاله لذت برده باشید.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای پایتون
- مجموعه آموزشهای برنامهنویسی
- گنجینه آموزشهای برنامهنویسی پایتون (Python)
- نصب پایتون — از صفر تا صد
- بهترین مفسر پایتون برای برنامه نویسی — راهنمای کاربردی
==