وبلاگ شخصی
محمّدرضا
علی حسینی

جایی که تجربیات, علایق
و چیزهایی که یادگرفته‌ام را
با هم مرور می‌کنیم.

آموزش زبان برنامه‌نویسی Rust – قسمت ۲: انواع داده‌های عددی و عملگرهای آن‌ها

آموزش زبان برنامه‌نویسی Rust – قسمت ۲: انواع داده‌های عددی و عملگرهای آن‌ها

حالا که در جلسه‌ی قبل با متغیّرها و ثابت‌ها آشنا شدیم، زمان آن است که به سراغ انواع داده‌ها در زبان برنامه‌نویسی Rust برویم.

انواع داده در زبان Rust

در زبان Rust ما دو نوع داده مختلف داریم: عددی (Scalar) و ترکیبی (compound). از آنجایی که زبان Rust یک زبان statically typed است، یعنی نوع هر متغیّر یا ثابت باید در زمان کامپایل کاملاً مشخّص باشد، دانستن نوع داده های مختلف ضروری است.

به‌علاوه استفاده از یک نوع داده‌ی خاص ممکن است تأثیر خیلی مهمی روی کارایی نرم‌افزار ما داشته باشد.

در این قسمت فقط به نوع داده های عددی می‌پردازیم. در قسمت بعد با یکدیگر انواع دیگر را بررسی خواهیم کرد.

قبل از اینکه به نوع عددی در زبان Rust بپردازیم، یک مفهوم مهم در دنیای کامپیوتر را باید مرور کنیم.

اعداد علامت‌دار و بدون علامت

ما دو جور عدد داریم: اعدادی که علامتشان مهم است، یعنی مثبت و منفی دارند. و اعدادی که علامتشان مهم نیست و می‌توان همیشه آن‌هارا مثبت درنظر گرفت.

شاید بپرسید که نوع بدون علامت به چه دردی می‌خورد؟ تفاوت این دو عدد در نحوه‌ی ذخیره‌سازی آن‌ها است.

اعداد مثل هر داده‌ی دیگری به صورت صفر و یکی ذخیره می‌شوند. اسم هرکدام از این صفر و یک ها هم بیت است. برای اینکه بخواهیم مشخّص کنیم که عددی مثبت است یا منفی، مجبوریم که علامت آن را هم ذخیره کنیم. پس به یک بیت اضافی نیاز است. درنتیجه اعداد علامت‌دار فضای بیشتری را در حافظه اشغال می‌کنند.

بگذارید با یک مثال جلو برویم. نحوه‌ی نمایش بدون علامت عدد ۲ به صورت دودویی اینگونه است: 10. حالا اگر بخواهیم عدد ۲+ را نمایش بدهیم، باید آن را به شکل 010 نمایش دهیم. آن صفری که قبل از ۱ آمده است یعنی اینکه این عدد مثبت است.

اگر هم بخواهیم عدد ۲- را نمایش دهیم حاصل 110 خواهد شد. معنای چپ‌ترین 1 این است که این عدد منفی است.

حالا در کامپیوتر ما مجبوریم که برای نمایش اعداد از بخش‌های هم‌اندازه‌ای از حافظه استفاده کنیم. مثلاً اعداد را در بخش‌های ۸ بیتی نمایش دهیم.

وقتی عددی بدون علامت است، از همه‌ی این ۸ بیت برای نمایش آن عدد می‌توان استفاده کرد. یعنی می‌توان اعداد 0 تا 255 را نشان داد.

امّا وقتی عدد علامت‌دار است، چپ‌ترین بیت به نمایش علامت عدد اختصاص داده می‌شود. پس برای خود عدد تنها ۷ بیت باقی می‌ماند. با این حساب می‌توان اعداد ۱۲۸- تا ۱۲۷ را نشان داد. همانطور که می‌بینید اندازه‌ی اعدادی که می‌توان نمایش داد کاهش می‌یابد.

به‌طور کلّی استفاده از اعداد بدون علامت وقتی که داده‌ی منفی نداریم راه بهتری است. چون می‌توانیم با فضای یکسان اعداد بیشتری را ذخیره کنیم.

حالا که تفاوت اعداد علامت‌دار و بدون علامت را فهمیدیم، وقت آن است که به سراغ زبان Rust برویم.

اعداد صحیح (Integer) در زبان Rust

اعداد صحیح اعدادی هستند که قسمت اعشاری ندارند و مقدارشان مشخّص است. در زبان Rust دو نوع کلّی از اعداد صحیح وجود دارند: اعداد صحیح علامت‌دار (Signed) و اعداد صحیح بدون علامت (Unsigned).

فرمت کلّی برای یک نوع داده integer در زبان Rust این طوری است:

Type size

در جدول زیر 6 نوع مختلف برای اعداد علامت‌دار و بدون علامت نمایش‌داده شده اند:

طولعلامت‌داربدون علامت
۸ بیتi8u8
۱۶ بیتi16u16
۳۲ بیتi32u32
۶۴ بیتi64u64
وابسته به معماری سیستمIsizeusize

همانطوری که می‌بینید علاوه بر حالاتی که خودمان دقیقاً اندازه‌ی عدد را انتخاب می‌کنیم، مقادیر isize و usize هم وجود دارند که از معماری سیستم پیروی می‌کنند. اگر معماری ماشین شما ۶۴ بیتی باشد، اندازه عدد ۶۴ بیت خواهد بود. اگر معماری ۳۲ بیتی باشد، اندازه‌ی عدد هم ۳۲ بیت خواهد بود.

مقدار پیش‌فرضی که Rust برای اعداد صحیح درنظر می‌گیرد i32 است. اگر یادتان باشد، قبلاً دیدیم که خیلی وقت‌ها لازم نیست ما به صورت صریح نوع متغیّرها را مشخّص کنیم و خود کامپایلر از مقداری که آن متغیّر گرفته است نوع را تشخیص می‌دهد.

در قطعه کد زیر چند مثال از متغیّرهایی با نوع‌های مختلف را می‌بینیم:

let  a: u64 = 1024;
let b: i8 = -7;
let c : usize = 800;
let d = -64;          // به صورت پیش•فرض نوع این متغیّر برابر با i32 در نظر گرفته می•شود

نمایش مقادیر عددی

در زبان Rust شما می‌توانید اعداد را در مبنای ۱۰، ۸، ۱۶ و ۲ نمایش دهید. همچنین شیوه‌ی به‌خصوص بایت هم وجود دارد که می‌تواند در متغیّرهایی از نوع u8 کاراکترها را ذخیره کند.

همچنین برای افزایش خوانایی می‌توانید بین ارقام علامت _ را قرار دهید.

به مثال‌های زیر توجّه کنید:

let a = 123_456;
let b = 0xf2; //  hexadecimal
let c = 0o71;   // octal
let d = 0b1110_0001;    // binary
let c = b'C';   // byte

محدوده‌ی هر نوع و سرریز (Overflow)

هر نوع عددی با توجّه به علامت‌دار بودن یا نبودن و اندازه‌ی آن می‌تواند محدوده‌ی خاصی از اعداد را نمایش دهد. اگر عددی کوچک‌تر یا بزرگتر از آن محدوده را به آن نسبت دهیم سرریز (overflow) رخ می‌دهد. یعنی چون نمی‌توانیم مقدار کامل را نمایش دهیم، مقداری که نمایش‌داده می‌شود اشتباه خواهد بود.

در جدول زیر حداقل و حداکثر مقداری که هر نوع داده نگهداری می‌کند را می‌بینید:

نوع داده عددیحداقل عدد قابل نمایشحداکثر عدد قابل نمایش
i8128-127
u80255
i16-32,76832,767
u16065,535
i32-2,147,483,6482,147,483,647
u3204,294,967,295
i64-9,223,372,036,854,775,8089,223,372,036,854,775,807
u64018,446,744,073,709,551,615

حالا اگر overflow رخ دهد چه اتّفاقی می‌افتد؟ بیایید امتحان کنیم.

یک برنامه می‌نویسم و در آن مقدار ۲۵۵ را به متغیّری از نوع i8 نسبت می‌دهیم.

 fn main() {
     let a:i8 = 0xf_f;
    println!("{}\n", a);
}
وقتی که این برنامه را اجرا کنیم، می‌بینیم که کامپایلر به ما این خطا را نشان می‌دهد:
 error: literal out of range for i8
  --> src/main.rs:3:16
   |
 3 |     let a:i8 = 0xf_f;
   |                ^^^^^
   |
   = note: #[deny(overflowing_literals)] on by default
   = note: the literal `0xf_f` (decimal `255`) does not fit 
into an `i8` and will become `-1i8`
   = help: consider using `u8` instead
چرا این اتّفاق می‌افتد؟ چون بعد از overflow مقداری که در متغیّر قرار می‌گیرد چیزی نیست که ما می‌خواهیم و این یعنی منطق و عملکرد برنامه کاملاً خراب می‌شود.

رخ‌دادن overflow اتّفاق نسبتاً رایجی است و باید خیلی حواستان به آن باشد. اگر در حالتی متغیّری overflow کند, تمامی روند برنامه‌تان مختل می‌شود و نتیجه‌ای که کاربر می‌بیند اشتباه می‌شود.

بزرگترین مشکل overflow این است که عموماً در حالات خاصی از روند اجرای برنامه رخ می‌دهد، به همین دلیل شما هنگام نوشتن برنامه از درستی کارکرد آن مطمئن هستید، امّا هنگامی که برنامه به دست کاربران می‌افتد می‌بینید که خیلی‌ها به خاطر کارکرد اشتباه برنامه شاکی شده اند.

اعداد اعشاری (floating point) در زبان Rust

خب کارمان با اعداد صحیح تمام شد. پس نوبتی هم که باشد، نوبت اعداد اعشاری است. اعدادی که از دو بخش «جزء صحیح» و «اعشار» تشکیل می‌شوند.

در زبان Rust ما تنها ۲ نوع عدد اعشاری داریم. f32 و f64. همانطوری که حدس می‌زنید حرف f مخفف floating point است و عدد مقابل آن هم اندازه‌ی عدد را نمایش می‌دهد.

از آنجایی که در اکثر دستگاه‌های امروزی سرعت پردازی اعداد اعشاری 32 بیتی و 64 بیتی تفاوت چندانی ندارد، مقدار پیش‌فرض در زبان Rust نوع f64 است. چون با سرعت پردازش برابر دقّت بیشتری در نمایش اعشار دارد. ساده‌ترش را بخواهید یعنی تعداد ارقام اعشاری بیشتری را نمایش می‌دهد.

let b: f32 = 2.95;   
​let a = 2.95;   // به صورت پیش•فرض نوع متغیّر برابر با f64 درتظر گرفته می•شود

کپی کردن علامت اعداد اعشاری

ما می‌توانیم علامت یک عدد اعشاری را روی دیگری کپی کنیم. برای این کار باید از متد copysign استفاده کنیم. (در جلسات بعدی می‌فهمیم که متدها چی هستند. فعلاً نگران آن نباشید)

مثلاً برنامه‌ی زیر را ببینید:

 fn main() {
     let negative_float = -3.16;
     println!("{}", 5.5_f32.copysign(negative_float));
 }
ما در این برنامه ابتدا یک عدد اعشاری منفی را تعریف کرده‌ایم. سپس علامت آن را روی عدد اعشاری ۵.۵ کپی کرده‌ایم.

برای این کار ابتدا عدد ۵.۵ را نوشته‌ایم. سپس بعد از علامت _ نوع عدد، در اینجا f32، را وارد کرده‌ایم. حالا برای فراخوانی متد copysign ابتدا یک نقطه گذاشته و سپس اسم متد را نوشته‌ایم. درون پرانتزهای مقابل نام متد هم عددی را که می‌خواهیم علامتش را روی عدد جدید کپی کنیم قرار داده‌ایم.

حالا اگر این برنامه را اجرا کنیم خروجی زیر را دریافت می‌کنیم:

 -5.5

عملگرهای انواع عددی

روی مقادیر عددی می‌توان عملیات جمع، تفریق، ضرب، تقسیم و mod(باقی‌مانده)گیری را انجام داد. مثل اکثر زبان‌های دیگر برای جمع از علامت + ، برای تفریق از علامت – ، برای ضرب از علامت * ، برای تقسیم از علامت / و برای باقی‌مانده گیری از علامت ٪ استفاده می‌شود.

فقط به خاطر داشته باشید که باید دو طرف عملگر هردو عدد صحیح یا عدد اعشاری باشند. شما نمی‌توانید یک عدد صحیح را با یک عدد اعشاری جمع کنید، آن‌ها را در هم ضرب کنید و… .

مثلاً برنامه‌ی زیر را اجرا می‌کنیم:

fn main() {
    let a = 5;
    let c = 2.5;
    let d = a - c;
    println!("{}\n", d);
}
وقتی بخواهیم این برنامه را کامپایل کنیم با ارور زیر مواجه می‌شویم:
error[[E0277\]](): cannot subtract `{float}` from `{integer}`
 [--> src/main.rs:4:15](https://play.rust-lang.org
/?version=nightly&mode=debug&edition=2018)
[  ](https://play.rust-lang.org/?version=nightly&mode=debug&
edition=2018)|
4 |     let d = a - c;
  |               ^ no implementation for `{integer} - 
{float}`
  |
  = help: the trait `std::ops::Sub<{float}>` is not 
implemented for `{integer}`
همانطوری که در متن ارور می‌بینید در زبان Rust امکان تفریق عدد صحیح و اعشار از هم وجود ندارد. همان‌طور که امکان هیچ عملیاتی که شامل هر دو نوع شود وجود ندارد.

خب به نظر جلسه طولانی شد و بهتر است بقیه‌ی انواع داده‌ی موجود در زبان Rust را در جلسه‌ی بعد دنبال کنیم.

اگر سؤالی داشتید، جایی از نوشته گنگ بود یا در مورد این مجموعه پیشنهادی دارید خوشحال می‌شوم من و دیگر خواننداگان را در بخش نظرات آگاه کنید.

اگر هم جلسات پیش را از دست داده‌اید با کلیک روی این نوشته به اوّلین جلسه منتقل می‌شود.

با کلیک روی این‌یکی متن هم به جلسه‌ی قبلی خواهید رفت.

«نوشته‌های مرتبط»

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

پانزده − سیزده =

«نوشته‌های ویژه»

«نوشته‌های محبوب»

«دیدگاه کاربران»