مقدمه

زبان C# ابزارها و امکانات متعدد و متنوعی برای انجام محاسبات و عملیات روی اعداد دارد که می‌توانند در طیف وسیعی از برنامه‌های کاربردی، از محاسبات ساده گرفته تا تحلیل‌های عددی پیچیده، استفاده شوند. نوع‌های داده‌ای عددی را در فصل قبل معرفی کردیم و با انواع عملگرهای عددی و به ویژه، عملگرهای حسابی نیز آشنا شدیم و حالا قصد داریم با روش‌های مختلف کار با اعداد و مقادیر عددی در C# آشنا شویم. کلاس استاتیک Math و مهمترین اعضای آن را در این درس معرفی می‌کنیم و به موضوعات دیگری مانند تولید اعداد تصادفی با استفاده از کلاس Random نیز خواهیم پرداخت.

اعضای عددی کلاس Math

کلاس Math مجموعه‌ای از ثابت‌های ریاضی و متدهای مربوط به محاسبات ریاضی را ارائه می‌دهد. قبل از هر چیز، توجه داشته باشید که Math یک کلاس استایک است؛ یعنی همه‌ی اعضای آن استاتیک هستند و بنابراین، دسترسی به یک عضو مانند X از آن به صورت Math.X انجام می‌شود. به عبارت دیگر، یک عضو استاتیک روی خود کلاس فراخوانی می‌شود و نه روی نمونه‌های کلاس.

کار بررسی اعضای کلاس Math را با معرفی سه ثابت عددی از نوع double با نام‌های PI، E و Tau شروع می‌کنیم.

  • ثابت Math.PI که در ریاضیات با نماد π نمایش داده می‌شود، از تقسیم محیط بر قطر دایره بدست می‌آید. این ثابت یک عدد گنگ یا غیر گویاست و در C# از نوع double تعریف شده و بنابراین تا 15 رقم اعشار دقت دارد و دارای مقدار تقریبی 3.141592653589793 است.
  • ثابت Math.E که در ریاضیات با نماد e نشان داده می‌شود و عدد نِپِر نیز نامیده می‌شود، پایه یا مبنای لگاریتم طبیعی است و دارای مقدار تقریبی 2.718281828459045 است.
  • ثابت Math.Tau که در ریاضیات با نماد τ نمایش داده می‌شود، بیانگر تعاد رادیان‌های موجود در یک دور (turn) است. در واقع، این ثابت معادل است و استفاده از آن، برخی از محاسبات را ساده‌تر می‌کند.

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

Copy Icon Program.cs
Console.Write("Please Enter Radius: ");
double radius = Convert.ToDouble(Console.ReadLine());
            
double area = Math.PI * radius * radius;
            
Console.WriteLine($"The area is {area}");

در ادامه، با استفاده از متدهای کلاس Math این برنامه را بهبود خواهیم داد.

متدهای مربوط به محاسبات توانی

کلاس Math یک متد مفید با نام Math.Pow() را برای انجام محاسبات توانی ارائه داده است. ابتدا به امضای این متد نگاه کنید.

double Math.Pow(double x, double y)

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

در مثال مربوط به مساحت دایره، برای محاسبه‌ی مجذور شعاع، آن را در خودش ضرب کردیم. حالا اجازه دهید به جای این کار، از متد Math.Pow() استفاده کنیم.

Copy Icon Program.cs
Console.Write("Please Enter Radius: ");
double radius = Convert.ToDouble(Console.ReadLine());
                        
double area = Math.PI * Math.Pow(radius, 2);
                        
Console.WriteLine($"The area is {area}");

دقت کنید که پارامترهای متد Math.Pow() از نوع double هستند اما در مثال بالا ما آرگومان صحیح 2 را وارد کرده‌ایم که با یک تبدیل ضمنی، از نوع double در نظر گرفته می‌شود. یادآوری می‌کنم که تبدیل ضمنی فقط وقتی رخ می‌دهد که امکان از دست رفتن اطلاعات وجود نداشته باشد. حالا به مثال زیر نگاه کنید.

Copy Icon Program.cs
double a = Math.Pow(36, 1.0 / 2.0);  // 6.0 

double b = Math.Pow(36, 1.0 / 2);    // 6.0
            
double c = Math.Pow(36, 1 / 2);      // 1.0

همانطور که می‌بینید، با استفاده از کسرهای اعشاری برای توان، می‌توانیم از متد Math.Pow() برای محاسبه‌ی ریشه‌های اعداد استفاده کنیم. بنابراین، گزاره‌ی اول، ریشه‌ی دوم یا جذر عدد 36 یعنی 6 را در متغیر a ذخیره می‌کند. در کزاره‌ی دوم، یکی از عملوندهای عمل تفسیم از نوع double است و بنابراین، دیگری هم به طور ضمنی به double تبدیل می‌شود. پس، در اینجا هم عدد 6 در متغیر b ذخیره می‌شود. اما در مورد گزاره‌ی سوم، باید دقت داشته باشید که هر دو عملوند عمل تقسیم از نوع int هستند و بنابراین، حاصل این تقسیم برابر با صفر خواهد بود و نتیجتاً مقدار 1 که حاصل 360 است، در متغیر c ذخیره می‌شود. پس، مراقب نوع آرگومان‌ها باشید تا با نتایج غیر منتظره روبرو نشوید.

با وجودی که می‌توانیم به روشی که در بالا دیدیم، ریشه‌های اعداد را با استفاده از متد Math.Pow() محاسبه کنیم اما برای محاسبه‌ی ریشه‌ی دوم و سوم اعداد، دو متد مجزا با نام‌های Math.Sqrt() و Math.Cbrt() تدارک دیده شده است. هر دوی این متدها یک پارامتر از نوع double دارند.

double Math.Sqrt(double x)  // returns √x

double Math.Cbrt(double x)  // returns ∛x

متدهای مربوط به گِرد کردن اعداد

با توجه به اینکه یک عدد اعشاری را می‌توان رو به بالا، رو به پایین یا به نزدیکترین عدد صحیح گِرد کرد، کلاس Math چند متد برای این منظور ارائه می‌دهد.

  • متد Math.Ceiling() عدد را رو به بالا گرد می‌کند.
  • متد Math.Floor() عدد را رو به پایین گرد می‌کند.
  • متد Math.Round() عدد را به نزدیکترین عدد صحیح گرد می‌کند.
  • متد Math.Truncate() بخش اعشار عدد را حذف می‌کند.

امضای این متدها شبیه هم است. برای نمونه، متد Math.Floor() دارای فرم زیر است.

double Math.Floor(double x)

البته آرگومان ورودی این متدها می‌تواند از نوع decimal هم باشد که در این صورت، نوع بازگشتی متد هم decimal خواهد بود. به علاوه، اگر یک مقدار float به عنوان آرگومان فراهم کنیم، به طور ضمنی به double تبدیل می‌شود. مثال زیر، کارکرد این متدها را نشان می‌دهد.

Copy Icon Program.cs
double a = Math.Ceiling(2.4);     // 3.0
double b = Math.Ceiling(-2.7);    // -2.0
decimal c = Math.Ceiling(2.7m);   // 3.0m 
          
double d = Math.Floor(2.4);       // 2.0
double e = Math.Floor(-2.7);      // -3.0
decimal f = Math.Floor(2.7m);     // 2.0m 
          
double g = Math.Round(2.4);       // 2.0
double h = Math.Round(2.7);       // 3.0
decimal i = Math.Round(2.7m);     // 2.0m 
          
double j = Math.Truncate(2.4);    // 2.0
double k = Math.Truncate(-2.4);   // -2.0
decimal l = Math.Truncate(2.7m);  // 2.0m 

از بین چهار متدی که برای گِرد کردن اعداد کاربرد دارند، متد Math.Round() به بررسی بیشتری نیاز دارد. در واقع، این متد می‌تواند دو پارامتر اضافه هم داشته باشد. این پارامترهای اختیاری عبارتند از:

  • یک پارامتر از نوع int که مشخص می‌کند که عدد مورد نظر تا چند رقم اعشار گرد شود. مقدار پیش‌فرض این پارامتر صفر است که باعث می‌شود خروجی این متد یک عدد صحیح (که البته از نوع double یا decimal است) باشد.
  • یک پارامتر برای تعیین روش گِردسازی در مواردی که عدد مورد نظر دقیقاً وسط دو عدد باشد. در واقع، مقدار این پارامتر تعیین می‌کند که عددی مثل 2.5 به 2 گرد شود یا 3. این پارامتر از نوع یک شمارشی (enum) با نام MidpointRounding است. مقدار پیش‌فرض این پارامتر MidpointRounding.ToEven است که باعث می‌شود در چنین مواردی، عدد مورد نظر به سمت نزدیکترین عدد زوج گرد شود. مثلاً عدد 2.5 به 2 گرد می‌شود و عدد 3.5 به 4. به‌ندرت نیاز به تغییر این مقدار پیش‌فرض پیدا می‌کنیم.

حالا اجازه دهید به مثال محاسبه‌ی مساحت دایره برگردیم. با استفاده از متد Math.Round() ترتیبی می‌دهیم که مساحت محاسبه‌شده به صورت گرد شده تا 2 رقم اعشار، نمایش داده شود.

Copy Icon Program.cs
Console.Write("Please Enter Radius: ");
double radius = Convert.ToDouble(Console.ReadLine());
                                    
double area = Math.PI * Math.Pow(radius, 2);
                                    
Console.WriteLine($"The area is {Math.Round(area, 2)}");

سایر متدهای کلاس Math

کلاس Math علاوه بر متدهایی که تا اینجا دیدیم، متدهای متعدد دیگری هم دارد که در اینجا از آنها نام می‌بریم.

  • متدهای مربوط به مثلثات: متدهای Math.Sin() و Math.Cos() و Math.Tan() برای توابع مثلثاتی سینوس، کسینوس و تانژانت در نظر گرفته‌شده‌اند. برای توابع معکوس مثلثاتی هم متدهای Math.Asin() و Math.Acos() و Math.Atan() تعریف شده‌اند. به علاوه، برای توابع هذلولوی (hyperbolic) نیز متدهای Math.Sinh() و Math.Cosh() و Math.Tanh() در نظر گرفته شده‌اند. همچنین، متدهای Math.Asinh() و Acosh() و Atanh() نیز برای توابع هذلولی معکوس تعریف شده‌اند.
  • متدهای مربوط به لگاریتم: متدهای Math.Log() و Math.Log10() و Math.Log2() به‌ترتیب برای توابع لگاریتم طبیعی، لگاریتم در مبنای 10 و لگاریتم در مبنای 2 تعریف شده‌اند. متد Math.Exp() نیز تابع نمایی را پیاده‌سازی کرده که معکوس تابع لگاریتم طبیعی است.
  • متدهای مربوط به توابع قدرمطلق و علامت: متد Math.Abs() تابع قدرمطلق را پیاده‌سازی کرده است. یعنی Math.Abs(x) اگر x مثبت باشد، خود x را برمی‌گرداند و اگر منفی باشد، قرینه‌ی x را. متد Math.Sign() نیز تابع علامت را پیاده‌سازی کرده که به ازای آرگومان‌های مثلت، مقدار 1 و به ازای آرگومان‌های منفی مقدار -1 و به‌ازای آرگومان صفر، خود صفر را برمی‌گرداند.
  • متدهای دیگر: کلاس Math متدهای دیگری هم دارد که کاربرد کمتری دارند. برای دسترسی به لیست کامل اعضای کلاس Math می‌توانید در vscode عبارت Math را تایپ کنید و یک نقطه بعد از آن قرار دهید تا این IDE لیست همه‌ی اعضای کلاس استاتیک Math را نمایش دهد.

تولید اعداد تصادفی

در برنامه‌های مختلفی مثل بازی‌ها، شبیه‌سازی‌ها و حتی در کاربردهایی مثل رمزنگاری به اعداد تصادفی نیاز پیدا می‌کنیم. C# کلاس مجزایی با نام Random را برای کار با اعداد تصادفی ارائه داده است. این کلاس متدهایی دارد که می‌توانیم از آنها برای تولید اعداد تصادفی صحیح و اعشاری در بازه‌های مختلف استفاده کنیم. متد Next() از کلاس Random برای تولید اعداد صحیح تصادفی کاربرد دارد و متد NextDouble() یک عدد تصادفی بین صفر و 1 تولید می‌کند.

متد Next() می‌تواند صفر، یک یا دو آرگومان دریافت کند. اگر از این متد به صورت Next(a, b) استفاده کنیم که در آن a و b از نوع int هستند، یک عدد صحیح بین a و b (البته به جز خود b) تولید می‌شود. اگر فقط یک آرگومان از نوع int به این متد بدهیم، یعنی از آن به فرم Next(a) استفاده کنیم، یک عدد صحیح بین صفر تا a (به‌جز خود a) تولید می‌شود. و اگر از این متد بدون هیچ آرگومانی استفاده کنیم، یک عدد تصادفی بین صفر تا مقدار ماکزیمم نوع int تولید می‌شود.

فرض کنید بخواهیم یک تاس را شبیه‌سازی کنیم که هر بار عددی بین 1 تا 6 را نمایش می‌دهد. کافیست یک نمونه (instance) از کلاس Random ایجاد کنیم و متد Next() را روی این نمونه یا شیء فراخوانی کنیم.

Copy Icon Program.cs
Random rnd = new Random();
int diceRoll = rnd.Next(1, 7);
            
Console.WriteLine(diceRoll);

هر بار که این برنامه را اجرا کنیم، یک عدد صحیح بین 1 تا 6 در خروجی نمایش داده می‌شود.

از متد NextDouble() نیز می‌توانیم برای تولید اعداد تصادفی در بازه‌ی صفر و 1 استفاده کنیم. برای مثال، فرض کنید بخواهیم یک مقدار تصادفی درصدی تولید کنیم. این کار را می‌توانیم به صورت زیر انجام دهیم.

Copy Icon Program.cs
Random rnd = new Random();
double percentage = rnd.NextDouble() * 100;
            
Console.WriteLine(Math.Round(percentage));

اعداد تصادفی و شبه‌تصادفی

کامپیوترها قابلیت تولید اعداد واقعاً تصادفی را ندارند. دلیل این امر کاملاً واضح است: کامپیوترها ماشین‌های قطعی (deterministic) هستند؛ یعنی یک عملیات مشخص روی آنها همواره نتیجه‌ی یکسانی دارد. پس، اعداد تصادفی در زبان‌هایی مانند C# چطور تولید می‌شوند؟ در پاسخ باید گفت که این اعداد فقط ظاهراً تصادفی هستند و در واقع، بر اساس الگوریتم‌هایی ایجاد می‌شوند که به محاسبات ریاضی پیچیده متکی هستند. این الگوریتم‌ها به یک مقدار اولیه موسوم به seed یا بذر نیاز دارند. وقتی ما نمونه‌ای از کلاس Random ایجاد کنیم و مقداری را به عنوان بذر تعیین نکنیم، C# از زمان سیستم به عنوان بذر استفاده می‌کند. البته این زمان نه بر اساس ساعت سیستم، بلکه معمولاً بر اساس تعداد میلی‌ثانیه‌های گذشته از نیمه‌شب یکم ژانویه در 1970 تا کنون، مشخص می‌شود. اگر یک نمونه از کلاس Random را به صورت new Random(a) ایجاد کنیم که در آن a یک عدد صحیح دلخواه است، مقدار a به عنوان بذر در نظر گرفته می‌شود و بنابراین، چنین نمونه‌ای همیشه دنباله‌ی یکسانی از اعداد تصادفی را تولید می‌کند. به همین دلیل است که اعداد تصادفی تولید شده توسط زبان‌های برنامه‌نویسی مانند C# را گاهی اعداد شبه‌تصادفی می‌گویند. البته این موضوع نه تنها مشکلی ایجاد نمی‌کند (مگر اینکه بیش از یک نمونه از کلاس Random را ایجاد کنیم ) بلکه فوایدی هم دارد. یک نمونه از فواید شبه‌تصادفی بودن اعداد تصادفی تولید شده این است که در شبیه‌سازی‌ها می‌توانیم نتایج را با دقت بالایی تکرار کنیم.