۴ - ۴تایپهای عددی
اعداد رو کمی در فصلهای گذشته دیدیم، حالا اینجا با جزئیات بیشتری بررسیشون میکنیم. مهمه که بدونیم هسکل فقط از یک تایپ برای اعداد استفاده نمیکنه. تایپِ عددهایی که بیشتر باهاشون سر و کار داریم، اینها هستن:
اعدادِ صحیح
اعدادِ تام یا کاملاند، چه مثبت و چه منفی.
۱.
تایپِ Int
: این تایپ، عددِ صحیح با دقتِ ثابت ِه. منظور از دقتِ ثابت اینه که محدوده دارن، با یه مقدارِ حداکثر و یه مقدارِ حداقل؛ و نمیتونن هر چقدر که میخوایم بزرگ یا کوچک باشن – کمی جلوتر بیشتر توضیح میدیم.
۲.
تایپِ Integer
: این تایپ هم برای اعدادِ صحیح ِه، ولی اعدادش محدودهای ندارن (هر چقدر که بخوایم بزرگ یا کوچک میشن).
اعدادِ کسری
این اعداد صحیح نیستن. مقادیرِ Fractional
شامل این چهار تایپاند:
۱.
تایپِ Float
: این تایپ برای اعداد با ممیزِ شناور ِ تک-دقتی به کار میره. اعدادِ ممیز-ثابت، تعدادِ ثابتی از ارقام برای قبل و بعد از ممیز دارن. در مقابل، ممیز شناور میتونه تعداد بیتهایی که برای اعداد قبل و بعد از ممیز استفاده میشن رو جابجا کنه. البته این انعطافپذیری منجر به نقضِ بعضی فرضیههای مشترک برای اعداد میشه، و بهتره با نهایتِ دقت از اونها استفاده بشه. به طور کلی، در کاربردهای تجاری اصلاً نباید از اعداد با ممیز شناور استفاده کرد.
۲.
تایپِ Double
: تایپی برای اعداد با ممیز شناور ِ دو-دقتی. مشابهِ تایپِ Float
، با تفاوت اینکه دو برابر بیت برای توصیفِ اعداد دارن.
۳.
تایپِ Rational
: نوعی عددِ کسری که نسبت ِ دو عددِ صحیح رو نشون میده. مقدارِ 5 / 7 :: Rational
حامِل دو مقدارِ Integer
ِه: یکی صورتِ ۵، و اون یکی مخرجِ ۷. Rational
دقتِ نامحدود داره، ولی به اندازهی Scientific
مقرون به صرفه نیست.
۴.
تایپِ Scientific
: نوعی از اعداد که فضای مناسبی رو اشغال میکنن، و دقت تقریباً نامحدودی دارند. اعدادِ Scientific
با نماد علمی نشون داده میشن، و متشکل از یک ضریب از تایپِ Integer
، و یک توان از تایپِ Int
هستن. به خاطرِ محدودیتِ Int
، در حقیقت اعدادِ Scientific
هم محدود اند، ولی خیلی کم پیش میاد به اون حد برسیم. تایپِ Scientific
در یه کتابخونه تعریف شده، و میشه با دستورِ cabal install
یا stack install
نصبش کرد.
همهی این نوعدادهها یک نمونه از تایپکلاسی به اسمِ Num
دارن. ما در فصلهای بعدی تایپکلاسها رو توضیح میدیم، ولی تو این بخش Num
رو در اطلاعاتِ تایپها میبینین.
با تایپکلاسها میشه قابلیتهایی رو به تایپها اضافه کرد که برای همهی تایپهای دارای یه نمونه از اون تایپکلاس، قابلِ استفاده باشن. Num
تایپکلاسیه که اکثر تایپهای عددی یک نمونه ازَش دارند، دلیلش وجودِ توابع استانداردیه که برای همهی تایپهای عددی قابلِ استفادهاند. تایپکلاسِ Num
چیزیه که عملگرهای استاندارد مثل (+)
، (-)
، و (*)
و چندتای دیگه رو در اختیارِ اعداد قرار میده. میشه این توابع رو به هر تایپی که یه نمونه از تایپکلاس Num
داره اعمال کنیم. یک نمونه، چگونگیِ عملکردِ توابع برای یک تایپِ بخصوص رو تعریف میکنه. به زودی تایپکلاسها رو با جزئیاتِ خیلی بیشتر توضیح میدیم.
اعداد صحیح
همونطور که بالاتر گفتیم، اعدادِ صحیح، دو تایپِ اصلی دارن: Int
و Integer
.
اعداد صحیح، اعدادِ کامل و بدونِ اعشارند. مثل اینها:
1 2 199 32442353464675685678
اما اینها integral نیستند:
1.3 1/2
Integer
اینها همون اعدادِ کاملیاند که بهشون عادت داریم. میتونن مثبت یا منفی باشن، و هر چقدر هم لازم باشه بزرگ یا کوچک میشن.
تایپِ Bool
فقط دو مقدار داره که میشه صراحتاً به عنوانِ دادهساز بنویسیمِشون. در موردِ Integer
، و بیشترِ نوعدادههای عددی که بینهایت مقدار دارن، دادهسازهاشون نوشته نمیشن. از نظر تئوری، یک Integer
رو میشه با جمع ِ سه حالتِ: صفر، سازندههای بازگشتی به سمتِ منفیِ بینهایت، و سازندههای بازگشتی به سمتِ مثبتِ بینهایت تعریف کرد. ولی چنین رَوِشی اصلاً مقرون به صرفه نیست، به همین خاطر، در این مورد GHC یه کم جادو میکنه.
چرا Int
داریم؟
تایپِ عددیِ Int
در واقع بازموندهای از امکاناتِ اولیهی کامپیوترهاست. اکثر برنامهها باید با Integer
کار کنن، نه Int
؛ مگر اینکه برنامهنویس احاطهی کامل به محدودیتهای Int
داشته باشه، و اینکه عملکرد ِ اضافهی حاصل از اونها واقعاً مطلوب باشه.
خطرِ استفاده از Int
(و تایپهای مشابه مثلِ Int8
، Int16
و غیره) اینه که نمیتونن اطلاعاتِ خیلی بزرگ رو بَیان کنن. پس از اونجا که اینها اعدادِ صحیح هستن، نمیتونن در جهت مثبت یا منفی اندازهی دلخواه داشته باشن.
تو این مثال میبینید که وقتی از عددی بزرگتر از حدّ Int8
استفاده میکنیم، چه اتفاقی میوفته:
Prelude> import GHC.Int
Prelude> 127 :: Int8
127
Prelude> 128 :: Int8
<interactive>:11:1: Warning:
Literal 128 is out of the
Int8 range -128..127
If you are trying to write a large
negative literal,
use NegativeLiterals
-128
Prelude> (127 + 1) :: Int8
-128
گرامری که اینجا میبینید، :: Int8
، وظیفهی تخصیصِ تایپِ Int8
به این اعداد رو داره. همونطور که در فصلِ بعد میبینیم، اعداد در باطن پلیمورفیک اند، و کامپایلر تا زمانی که مجبور نباشه، هیچ تایپِ معیّنی بهشون اختصاص نمیده. یه کم عجیب و غیرمنتظره میشد اگه پیشفرض ِ کامپایلر تخصیص تایپِ Int8
به همهی اعداد بود. ما هم به همین خاطر با اون گرامر، تایپِ معیّن ِ Int8
رو به اعدادمون اختصاص دادیم.
همونطور که میبینید، ۱۲۷ در بازهی Int8
هست و موردی نداره. ۱۲۸ یه هشدار از سرریز شدن داد، ۱ + ۱۲۷ هم سرریز شد و برگشت به کوچکترین مقدارِ عددی. به خاطرِ محدود بودنِ حافظهای که مقادیرِ Int8
میتونن اشغال کنن، توانایی تطابق با عددی مثل ۱۲۸ رو هم ندارن (برعکسِ Integer
). عددِ ۸ نشون دهندهی تعداد بیتهاییه که تایپ برای ارائهی اعدادِ صحیح استفاده میکنه.* گاهی اوقات ممکنه اندازهی ثابت ِ این تایپها مفید باشه، ولی عمدتاً Integer
ارجحه.
تایپهای Int
که اندازهی ثابت هستن، با رَوِشِ مکملِ دو اعدادشون رو ارائه میدن.
بیشترین و کمترین ِ مقادیرِ تایپهای عددی رو میتونین با استفاده از minBound
و maxBound
از تایپکلاسِ Bounded
پیدا کنین. در زیر چندتا مثال زدیم:
Prelude> import GHC.Int
Prelude> :t minBound
minBound :: Bounded a => a
Prelude> :t maxBound
maxBound :: Bounded a => a
Prelude> minBound :: Int8
-128
Prelude> minBound :: Int16
-32768
Prelude> minBound :: Int32
-2147483648
Prelude> minBound :: Int64
-9223372036854775808
Prelude> maxBound :: Int8
127
Prelude> maxBound :: Int16
32767
Prelude> maxBound :: Int32
2147483647
Prelude> minBound :: Int64
9223372036854775807
اگه تایپی، این تایپکلاسِ بخصوص رو داشته باشه، میتونین محدودهی مقادیرِ ممکن اون تایپ رو پیدا کنین. در مثال بالا دیدیم که بازهی مقادیرِ قابل ارائه با Int8
از ۱۲۸- تا ۱۲۷ بود.
با دستورِ :info
، میتونین همهی تایپکلاسهای یه تایپ رو تو GHCi ببینین. این دستور، تعریفِ نوعداده ای هم که استعلام کردین رو نشون میده. برای مثال، میخوایم بدونیم که آیا تایپِ Int
نمونهای از تایپکلاسِ Bounded
داره یا نه:
Prelude> :i Int
data Int = GHC.Types.I# GHC.Prim.Int#
-- Defined in ‘GHC.Enum’
instance Bounded Int
البته Int
نمونههای تایپکلاسیِ خیلی بیشتری داره که اینجا ننوشتیم.
اعدادِ کسری
چهار تایپِ رایجِ اعدادِ Fractional
در هسکل Float
، Double
، Rational
، و Scientific
اند. Float
، Double
، و Rational
از ابتدا توی GHC هستن. اما Scientific
، همونطور که گفتیم از یه کتابخونه میاد. Rational
و Scientific
هر دو دقتِ نامحدود دارن، ولی Scientific
بهینهتَر ِه. منظور از این نامحدود بودن اینه که از این دو تایپ، بر خلافِ Float
و Double
که به یه دقتِ مشخص محدوداند، میشه برای محاسباتی که نیاز به دقتهای خیلی بالا دارن استفاده کرد. تقریباً هیچ وقت Float
لازم نمیشه، مگر برای کارهایی مثل برنامهنویسیِ گرافیکی با OpenGL.
بعضی محاسبههای عددی با اعدادِ کسری کار میکنن. یه مثالِ خوب، تابع تقسیم (/)
ِه، که تایپِش اینه:
Prelude> :t (/)
(/) :: Fractional a => a -> a -> a
نوشتهی Fractional a =>
یه محدودیتِ تایپکلاسی ِه. یعنی متغیرِ a
مقیّد به داشتنِ تایپکلاسِ Fractional
ِه. مهم نیست a
چه تایپ عددی باشه، فقط باید یک نمونه از تایپکلاسِ Fractional
داشته باشه؛ به عبارت دیگه، باید یک تعریف وجود داشته باشه که عملیاتهای اون تایپکلاس رو برای اون تایپ توصیف کنه. تابعِ /
یک عددی که تایپکلاس Fractional
داره رو میگیره، به یه عددِ دیگه از همون تایپ تقسیم میکنه، و یه مقدار از همون تایپ رو به عنوان جواب برمیگردونه.
Fractional
تایپکلاسی ِه که تایپهاش ملزم به داشتنِ یک نمونه از تایپکلاسِ Num
هستن. در چنین رابطهای، میگیم Num
یه سوپرکلاس از Fractional
ِه. بنابراین، (+)
و توابعِ دیگه از تایپکلاسِ Num
رو میشه با اعدادِ Fractional
استفاده کرد، ولی توابعِ تایپکلاسِ Fractional
رو نمیشه با همهی تایپهای Num
استفاده کرد.
این نتیجهی استفاده از (/)
در REPL ِه:
Prelude> 1 / 2
0.5
Prelude> 4 / 2
2.0
دقت کنید با اینکه جوابمون یه عدد کامل بود، باز هم کسری شد. دلیل ِش اینه که مقادیرِ Fractional a => a
به صورتِ پیشفرض به تایپِ Double
تبدیل میشن. اکثر مواقع، بهتره صراحتاً از Double
استفاده نکنین، Scientific
با دقت نامحدودی که داره (میشه گفت داداشِ Integer
ِه!)، گزینهی بهتریه. اکثر مردم با محاسباتِ ممیز شناور، به خاطرِ رفتارهای نامتعارفش، راحت کنار نمیان (اون رفتارها به عمد طراحی شدن، ولی اون یه مبحثِ دیگهست)؛ شما هم برای آسودگیِ خاطر، از تایپهای با دقتِ نامحدود استفاده کنین.