محاسبات ممیز شناور(Floating-point) در Python
درک مسئله ابتدا در مبنای 10 ساده تر است. کسر 1/3 را در نظر بگیرید. می توانید آن را به عنوان کسر مبنای 10 به صورت 0.3 در نظر بگیرید. یا بهتر 0.33 ، یا بهتر 0.333 و غیره. صرف نظر از اینکه چند رقم را بنویسید، هرگز دقیقا 1/3 نمی شود، اما تقریب بهتری از 1/3 می شود.
به همین ترتیب، صرف نظر از هر تعداد رقم مبنای 2 که استفاده کنید، مقدار اعشاری 0.1 را نمی توان در کسر مبنای 2 دقیقا نمایش داد. در مبنای 2 ، 1/10 یک کسر تکرار شونده تا بی نهایت است
0.0001100110011001100110011001100110011001100110011…
بسیاری از کاربران به دلیل شیوه نمایش مقادیر، از تقریب زدن بی اطلاع هستند. پایتون تنها یک تقریب اعشاری از مقدار واقعی اعشار مربوط به تقریب دودویی ذخیره شده در ماشین را چاپ می کند. در بیشتر ماشین ها، اگر پایتون میخواست مقدار واقعی اعشار مربوط به تقریب دودویی ذخیره شده برای 0.1 را چاپ کند، باید خروجی زیر را نمایش دهد.
>>> 0.1
0.1000000000000000055511151231257827021181583404541015625
این تعداد ارقام بیشتر از چیزی است که اکثر مردم مفید می پندارند، بنابراین، پایتون با نمایش یک مقدار گرد شده(به جای عدد اصلی)، تعداد ارقام را قابل مدیریت می کند.
>>> 1 / 10
0.1
به یاد داشته باشید، حتی با وجود اینکه نتیجه چاپ شده شبیه مقدار دقیق 1/10 به نظر می رسد، مقدار واقعی ذخیره شده، نزدیک ترین کسر دودویی قابل نمایش است.
بر اساس تاریخچه، prompt پایتون و تابع داخلی repr() ، یک 1 به همراه 17 رقم پر ارزش را انتخاب خواهد کرد: 0.10000000000000001 . شروع با پایتون 3.1، امروزه پایتون روی اکثر سیستم ها قادر به انتخاب کوتاهترین گزینه است و 0.1 را نمایش می دهد.
توجه !
توجه داشته باشید، این در ذات ممیز شناور دودویی است: این نه یک اشکال در پایتون و نه یک اشکال در کد شما است. شما همین مسئله را در همه زبان هایی که از محاسبات ممیز شناور سخت افزار شما پشتیبانی می کند، پیدا خواهید کرد (اگر چه ممکن است برخی از زبان ها این تفاوت ها را به صورت پیش فرض (یا در تمامی حالات خروجی) نمایش ندهند.)
برای داشتن خروجی خوشایندتر، ممکن است بخواهید از قالب بندی رشته برای تولید تعداد محدودی از ارقام پر ارزش استفاده کنید.
>>> format(math.pi, ‘.12g’) # give 12 significant digits
‘3.14159265359’
>>> format(math.pi, ‘.2f’) # give 2 digits after the point
‘3.14’
>>> repr(math.pi)
‘3.141592653589793’
مهم است که توجه داشته باشید، در واقع این یک توهم است: شما دارید نمایش مقدار واقعی ماشین را گرد می کنید. یک توهم ممکن است دیگری را به وجود آورد. برای مثال، از آنجایی که 0.1 (اعشار) دقیقا 1/10 (کسر) نیست، حاصل جمع سه مقدار 0.1 دقیقا 0.3 نمی شود، یا :
>>> .1 + .1 + .1 == .3
False
همچنین، از آنجایی که 0.1 نمی تواند بیشتر از این به مقدار دقیق 1/10 نزدیک شود، و نیز 0.3 نمی تواند بیشتر از این به مقدار دقیق 3/10 نزدیک شود، از قبل گرد کردن (pre-rounding) با استفاده از تابع round() نمی تواند کمکی کند.
>>> round(.1, 1) + round(.1, 1) + round(.1, 1) == round(.3, 1)
False
با وجود اینکه اعداد به مقادیر دقیق مورد نظر نمی توانند نزدیک تر شوند، تابع round() برای بعد-گرد کردن (post-rounding) مناسب است، در نتیجه، نتایج با مقادیر دقیق، قابل مقایسه با یکدیگر خواهند بود.
>>> round(.1 + .1 + .1, 10) == round(.3, 10)
True
محاسبات ممیز شناور دودویی شگفتی های بسیاری مانند این دارد. مشکل مربوط به “0.1”، با جزییات دقیق در ادامه در بخش نمایش خطا، توضیح داده شده است.
>>> x = 3.14159
>>> x.as_integer_ratio()
(3537115888337719, 1125899906842624)
از آنجایی که این نسبت دقیق است، می توان از آن برای بازگرداندن بی ضرر مقدار اصلی استفاده کرد:
>>> x == 3537115888337719 / 1125899906842624
True
>>> x.hex()
‘0x1.921f9f01b866ep+1’
>>> x == float.fromhex(‘0x1.921f9f01b866ep+1’)
True
>>> >>>
>>> math.fsum([0.1] * 10) == 1.0
True
نمایش خطا در برنامه نویسی Python
این بخش مثال “0.1” را با جزییات توضیح می دهد و نشان می دهد چگونه می توانید یک ارزیابی دقیق از مواردی مانند این داشته باشید. فرض شده است با نمایش ممیز شناور دودویی آشنایی اولیه دارید.
1 / 10 ~= J / (2**N)
به صورت
J ~= 2**N / 10
باز نویسی می کنیم، و یاد آوری می کنیم که J دقیقا 53 بیت دارد (is >= 2**52 but <2**53)،>
>>> 2**52 <=>
56 تنها مقدار برای N است که باعث می شود J دقیقا 53 بیت باشد. پس بهترین مقدار ممکن برای J خارج قسمت گرد شده است:
>>> q, r = divmod(2**56, 10)
>>> r
6
از آنجایی که باقی مانده بیشتر از نصف 10 است، بهترین تقریب با گرد کردن رو به بالا به دست می آید:
>>> q+1
7205759403792794
بنابراین، بهترین تقریب ممکن برای 1/10 در دقت دو برابر 754 (754 double precision) این است:
7205759403792794 / 2 ** 56
تقسیم صورت و مخرج کسر به 2 کسر را به مقدار زیر کاهش می دهد:
3602879701896397 / 2 ** 55
توجه داشته باشید، از آنجایی که رو به بالا گرد کرده ایم، این در واقع کمی بزرگتر از 1/10 است. اگر رو به بالا گرد نکرده بودیم، خارج قسمت کمی کوچکتر از 1/10 می شد. اما در هیچ شرایطی دقیقا 1/10 نمی شود.
بنابراین کامپیوتر هرگز 1/10 را نمی بیند: چیزی که می بیند، کسر دقیقی است که در بالا داده شده است، بهترین تقریب 754 double که می تواند داشته باشد.
>>> 0.1 * 2 ** 55
3602879701896397.0
اگر کسر را در 10**55 ضرب کنیم، می توانیم مقدار خروجی را در 55 رقم اعشار ببینیم:
>>> 3602879701896397 * 10 ** 55 // 2 ** 55
1000000000000000055511151231257827021181583404541015625
به این معنی که عددی که دقیقا در کامپیوتر ذخیره شده است، برابر است با مقدار اعشاری 0.1000000000000000055511151231257827021181583404541015625 . به جای نمایش کامل این مقدار اعشاری، بسیاری از زبان ها( از جمله نسخه های قدیمی تر پایتون) نتیجه را تا 17 رقم پر ارزش گرد می کنند.
>>> format(0.1, ‘.17f’)
‘0.10000000000000001’
>>> from decimal import Decimal
>>> from fractions import Fraction
>>> Fraction.from_float(0.1)
Fraction(3602879701896397, 36028797018963968)
>>> (0.1).as_integer_ratio()
(3602879701896397, 36028797018963968)
>>> Decimal.from_float(0.1)
Decimal(‘0.1000000000000000055511151231257827021181583404541015625’)
>>> format(Decimal.from_float(0.1), ‘.17’)
‘0.10000000000000001’
بر خلاف برخی زبانهای برنامهنویسی رایج دیگر که بلاکهای کد در آکولاد تعریف میشوند (بهویژه زبانهایی که از گرامر زبان سی پیروی میکنند) در زبان پایتون از نویسه فاصله و جلوبردن متن برنامه برای مشخص کردن بلاکهای کد استفاده میشود. به این معنی که تعدادی یکسان از نویسه فاصله در ابتدای سطرهای هر بلاک قرار میگیرند، و این تعداد در بلاکهای کد درونیتر افزایش مییابد. بدین ترتیب بلاکهای کد به صورت خودکار ظاهری مرتب دارند.
پایتون مدلهای مختلف برنامه نویسی (از جمله شی گرا و برنامه نویسی دستوری و تابع محور) را پشتیبانی میکند و برای مشخص کردن نوع متغییرها از یک سامانه پویا استفاده میکند. این زبان از زبانهای برنامه نویسی مفسر بوده و به صورت کامل یک زبان شیگرا است که در ویژگیها با زبانهای تفسیری پرل، روبی، اسکیم، اسمالتاک و تیسیال مشابهت دارد و از مدیریت خودکار حافظه استفاده میکند.
پایتون پروژهای آزاد و متنباز توسعهیافتهاست و توسط بنیاد نرمافزار پایتون مدیریت میگردد.
پایتون قابل توسعه است:
). زمانی که واقعا درگیر شوید، می توانید مفسر پایتون را به یک برنامه نوشته شده به زبان C اتصال دهید و از آن به عنوان یک افزونه یا دستور زبان برای آن برنامه استفاده کنید. ضمنا نام این زبان برگرفته از برنامه “Monty Python’s Flying Circus در BBC است و هیچ ارتباطی با خزندگان ندارد(مار پیتون). ارجاع به مستندات Monty Python skits نه تنها مجاز است ، بلکه مورد تشویق نیز واقع می شود.
حال که همه شما درباره پایتون مشتاق و هیجان زده هستید، میخواهید آن را با جزییات بیشتری امتحان کنید. از آنجایی که بهترین راه برای یادگیری یک زبان استفاده از آن است، این آموزش همزمان با خواندن، شما را دعوت به بازی با مفسر پایتون می کند.
شما با شرکت در کلاس های آموزش پایتون در اصفهان می توانید یک فرد حرفه ای شوید
در بخش بعدی، مکانیزم استفاده از مفسر توضیح داده می شود که ممکن است اطلاعات کسل کننده ای باشد اما برای انجام مثال هایی که بعدا نشان داده می شوند ضروری است. در ادامه این آموزش، ویژگی های متنوع زبان و سیستم پایتون از طریق مثال ها معرفی می شود. از اصطلاحات ساده شروع می کنیم، سپس عبارات و انواع\[\pi=3.14159265\cdots\]
\[e=2.71828\cdots \]
\[{1.0}_{two}\times2^{-1}\]
دقیقا همانند نمادِ علمیِ مبنای ده، اعداد با نماد علمی مبنای 2 نیز به گونهای نمایش داده میشوند که تنها یک رقم دودویی در سمت چپ ممیز دودویی قرار بگیرد. در مبنای 2، اعداد در نمادِ علمیِ نرمالیزه به شکل زیر هستند:
\[{1.xxx\cdots x x\ }_{two}\times2^{yyy\cdots y y}\]
داشتن استانداردی برای نماد علمی در شکل نرمالیزه (بهنجار شده) 3 مزیت دارد:
در ادامهی مقاله بیشتر راجع به عدد نرمال شده (بهنجار شده) در مبنای دو صحبت خواهیم کرد.
به طور کلی دو روش برای نمایش اعداد اعشاری در وجود دارد:
- اعداد ممیز ثابت (Fixed point)
- اعداد ممیز شناور (Floating point)
با انتخاب مقادیر بالا برای فیلد نما و فیلد کسری (مانتیس) کران بسیار بالایی برای محاسبات در کامپیوترها فراهم آمده است. به عنوان مثال با این قالب، اعداد کسری قابل نمایش در کامپیوتر MIPS از اعداد کوچکی در کران \({2.0}_{ten}\times{10}^{-38}\) آغاز و به اعداد بزرگی در کران \({2.0}_{ten}\times{10}^{+38}\) ختم میشود. با این اوصاف به دلیل پهنه نامتناهی اعداد حقیقی، همچنان اعدادی هستند که در چنین فضای در قالب ممیز شناور نمیگنجند. از این رو دقیقاً همانند سرریز در محاسبات صحیح، وقفه ناشی از سرریز در محاسبات ممیز شناور احتمال وقوع دارد.
دقت کنید که سرریز در محاسبات ممیز شناور بدین معناست که نمای عدد آنقدر بزرگ شده و یا آنقدر کوچک شده که در فیلد Exponent قابل نمایش نیست.
اگر دنبال بهترین آموزشگاه پایتون در اصفهان هستید می توانید به سایت ما مراجعه کنید
نرمال سازی اعداد ممیز شناور
در نسخه ابتدایی، یک عدد ممیز شناور به صورت زیر نمایش داده میشد:
\[X={\left(-1\right)}^S\times M\times B^E\]
یکی از مشکلات نمایش ممیز شناور به این شکل، افزونگی این سیستم است، به عبارت دیگر برای یک عدد میتوان نمایشهای مختلفی داشت.
مثال: فرض کنید 2 = B است، دو نمایش مختلف ممیز شناور برای عدد 1 ارائه دهید.
اگر 4 = r باشد، مانتیس در مبنای 4 نمایش داده میشود و رقمهای مجاز \( \left\{\mathrm{\circ},\ \mathrm{1,\ 2,\ 3}\right\} \) میباشند که نمایش دودویی این رقمها به صورت \(\left\{\mathrm{\circ}\circ,\ \mathrm{01,\ 10,\ 11}\right\}\) خواهد بود. بنابراین اگر رقم پرارزش مانتیس برابر 01 یعنی رقم 1 در مبنای 4 باشد، با وجود \(\mathrm{\circ}\ \) بودن بیت پرارزش مانتیس در مبنای 2، مانتیس همچنان نرمال محسوب میشود.
علت اصلی استفاده از نمایش نرمال شده، جلوگیری از اتلاف فضا است.
پیشتر به این اشاره کردیم که نما یک عدد علامتدار است که به صورت مکمل دو یا سیستم بایاس نمایش داده میشود، اکنون قصد داریم کمی بیشتر راجع به سیستم بایاس صحبت کرده و علت استفاده از آن را بیان کنیم.
\[\left(-1\right)^0\times1.000\cdots00\times2^0=1\]
مثال: فرض کنید که 3 بیت در اختیار داریم و میخواهیم عدد 3- را در سیستم بایاس نمایش دهیم.
علت این امر که مقدار بایاس را برابر \(2^{n\ -\ 1}\) (n = تعداد بیتها) در نظر میگیرند نیز واضح است؛ زیرا اگر n بیت در اختیار داشته باشیم، اعداد ما در بازهی زیر قرار میگیرند:
\(-2^{n\ -\ 1}\le E\le2^{n\ -\ 1}-\ 1\)
حال اگر به طرفین تساوی مقدار \(2^{n\ -\ 1}\) اضافه کنیم، کلیه اعداد مثبتِ قابل ساخت با n بیت را به صورت مرتب (به منظور ذخیره سازی) خواهیم داشت.
در نتیجه میتوان عدد ممیز شناور نرمال شده بایاس شده را به صورت زیر نمایش داد:
پیش از این گفته بودیم که قرار بر این شد که اگر تمامی بیتها صفر باشند، عدد را معادل 0 در نظر میگیریم. این قرار داد در سیستم اعداد ممیز شناور نرمال شده بایاس شده معادل عدد زیر خواهد شد:
در نتیجه عدد 0 را جایگزین عدد \(2^{-\ 2^{n\ -\ 1}}\) کردیم که این امر مشکلی ایجاد نمیکند زیرا میزان استفاده از عدد 0 بسیار بیشتر از عدد \(2^{-\ 2^{n\ -\ 1}}\) است.
دامنه تغییرات اعداد اعشاری
برای به دست آوردن بزرگترین (کوچکترین) عدد مثبت قابل نمایش، باید مانتیس و توان عدد بزرگترین (کوچکترین) مقدار خود را اختیار کنند. به طور کلی فرض میکنیم مبنای نمایش مانتیس r است و برای نمایش مانتیس، m رقم در اختیار داریم که از آن ارقام، f رقم کسری و f – m رقم صحیح است. بنابراین محدوده نمایش اعداد مثبت برابر است با:
محدوده نمایش اعداد منفی نیز به سادگی با معکوس کردن این محدوده و منفی کردن کرانهها به دست میآید:
هر چقدر تعداد بیتهای توان بیشتر باشد، محدوده نمایش بیشتر میشود.
دقت نمایش ممیز شناور
بیشترین دقت نمایش را به صورت فاصله دو مانتیس متوالی در کوچکترین توان ممکن تعریف میکنیم. بنابراین دقت نمایش برابر است با:
هر چقدر تعداد بیتهای مانتیس بیشتر باشد، دقت نمایش بیشتر میشود.
مزیت ممیز شناور
جمعبندی
از آنجایی که حافظه کامپیوتر محدود است نمیتوانید اعداد را با دقت بینهایت ذخیره کنیم در نتیجه میبایست از سیستم دیگری به نام سیستم نمایش ممیز شناور برای نمایش اعداد بسیار بزرگ یا بسیار کوچک استفاده کرد. از این رو در این مقاله روی این موضوع تمرکز داشتیم که شما را با نمایش اعداد ممیز شناور و دقت نمایش آن آشنا کنیم تا زمینهی لازم جهت فراگرفتن روش انجام محاسبات در این سیستم را در شما ایجاد کرده باشیم.
بیشتر بخوانید: آموزش پایتون در یوتوب
داده، توابع و ماژول ها، و در نهایت مفاهیم پیشرفته مانند استثناها و کلاس های تعریف شده توسط کاربر معرفی می شوند.