GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing python.github.io — 15. حساب ممیز شناور: مشکلات و محدودیتها 15. حساب ممیز شناور: مشکلات و محدودیتها¶اعداد ممیز شناور در سختافزار رایانه بهصورت کسرهای مبنای ۲ (دودویی) نمایش داده میشوند. برای مثال، کسر دهدهی متأسفانه، بیشتر کسرهای دهدهی را نمیتوان بهطور دقیق بهصورت کسرهای دودویی نمایش داد. پیامد این امر آن است که، بهطور کلی، اعداد ممیز شناور دهدهی که وارد میکنید، تنها تقریبهایی از اعداد ممیز شناور دودویی هستند که واقعاً در ماشین ذخیره میشوند. درک این مشکل ابتدا در مبنای ۱۰ آسانتر است. کسر 1/3 را در نظر بگیرید. میتوانید آن را بهصورت یک کسر دهدهی تقریبی نمایش دهید: ۰.۳
یا حتی بهتر،: ۰.۳۳
یا حتی بهتر،: ۰.۳۳۳
و به همین ترتیب ادامه دهید. مهم نیست چند رقم را بنویسید، نتیجه هرگز دقیقاً برابر با 1/3 نخواهد شد، اما هرچه ارقام بیشتری بنویسید، تقریب بهتری از 1/3 به دست میآید. به همین شکل، مهم نیست چند رقم مبنای ۲ استفاده کنید، مقدار دهدهی 0.1 نمیتواند بهطور دقیق بهصورت یک کسر مبنای ۲ نمایش داده شود. در مبنای ۲، 1/10 کسری بینهایت تکرارشونده است: ۰.۰۰۰۱۱۰۰۱۱۰۰۱۱۰۰۱۱۰۰۱۱۰۰۱۱۰۰۱۱۰۰۱۱۰۰۱۱۰۰۱۱۰۰۱۱۰۰۱۱... اگر در هر تعداد محدود بیت متوقف شوید، یک مقدار تقریبی به دست میآورید. در بیشتر ماشینهای امروزی، اعداد ممیز شناور با استفاده از یک کسر دودویی تقریبی نمایش داده میشوند که صورت آن از ۵۳ بیت اول، از مهمترین بیت شروعشده، تشکیل شده و مخرج آن توانی از ۲ است. در مورد 1/10، کسر دودویی برابر با بسیاری از کاربران به دلیل نحوهی نمایش مقادیر، از این تقریب آگاه نیستند. پایتون تنها یک تقریب دهدهی از مقدار دهدهی واقعیِ مقدار دودویی ذخیرهشده در ماشین را چاپ میکند. در بیشتر ماشینها، اگر پایتون مقدار دهدهی واقعیِ تقریب دودویی ذخیرهشده برای 0.1 را چاپ میکرد، مجبور بود مقدار زیر را نمایش دهد: >>> 0.1 0.1000000000000000055511151231257827021181583404541015625 این تعداد رقم بیشتر از چیزی است که بیشتر افراد مفید میدانند، بنابراین پایتون با نمایش یک مقدار گرد شده، تعداد ارقام را در حد قابلمدیریت نگه میدارد: >>> 1 / 10 0.1 فقط به یاد داشته باشید که با وجود اینکه نتیجهی چاپشده مانند مقدار دقیق 1/10 به نظر میرسد، مقدار واقعی ذخیرهشده نزدیکترین کسر دودویی قابل نمایش است. جالب اینجاست که، عددهای دهدهیِ بسیار متفاوتی وجود دارند که نزدیکترین کسر دودوییِ تقریبی یکسانی دارند. برای مثال، عددهای در گذشته، اعلان پایتون و تابع توکار توجه داشته باشید که این موضوع ذاتاً بخشی از ممیز شناور دودویی است: این یک باگ در پایتون نیست و در کد شما نیز اشکالی وجود ندارد. همین رفتار را در تمام زبانهایی که از محاسبات ممیز شناور سختافزار شما پشتیبانی میکنند خواهید دید (اگرچه برخی زبانها ممکن است این تفاوت را بهصورت پیشفرض یا در همهی حالتهای خروجی نمایش ندهند). برای خروجی خواناتر، میتوانید از قالببندی رشتهها استفاده کنید تا تعداد محدودی رقم معنادار تولید شود: >>> 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 نشود: >>> 0.1 + 0.1 + 0.1 == 0.3 False همچنین، چون 0.1 نمیتواند به مقدار دقیق 1/10 نزدیکتر شود و 0.3 نیز نمیتواند به مقدار دقیق 3/10 نزدیکتر شود، گرد کردن اولیه با تابع >>> round(0.1, 1) + round(0.1, 1) + round(0.1, 1) == round(0.3, 1) False اگرچه این اعداد نمیتوانند به مقادیر دقیق موردنظرشان نزدیکتر شوند، تابع >>> math.isclose(0.1 + 0.1 + 0.1, 0.3) True روش دیگر این است که برای مقایسهی تقریبهای کلی از تابع >>> round(math.pi, ndigits=2) == round(22 / 7, ndigits=2) True محاسبات ممیز شناور دودویی شگفتیهای زیادی مانند این دارند. مشکل مربوط به "0.1" در ادامه، در بخش «خطای نمایش» با جزئیات دقیق توضیح داده شده است. همچنین Examples of Floating Point Problems را ببینید که خلاصهای قابلفهم از نحوهی کار ممیز شناور دودویی و مشکلات رایج آن در عمل ارائه میدهد. برای توضیح کاملتر سایر شگفتیهای رایج نیز The Perils of Floating Point را ببینید. همانطور که در پایان آن توضیح داده شده است، «پاسخ سادهای وجود ندارد.» با این حال، نباید بیش از حد از اعداد ممیز شناور بترسید! خطاهای موجود در عملیات float پایتون از سختافزار محاسبات ممیز شناور به ارث میرسند و در بیشتر ماشینها برای هر عملیات، در حدود حداکثر ۱ واحد از هر 2**53 واحد هستند. این مقدار برای بیشتر کارها کاملاً کافی است، اما باید در نظر داشته باشید که این محاسبات، حساب دهدهی نیستند و هر عملیات ممیز شناور میتواند با یک خطای گرد کردن جدید همراه شود. با وجود اینکه حالتهای غیرعادی و مشکلزا وجود دارند، در بیشتر استفادههای معمول از محاسبات ممیز شناور، اگر نمایش نتایج نهایی خود را به تعداد ارقام اعشاری مورد انتظارتان گرد کنید، در نهایت نتیجهای را که انتظار دارید مشاهده خواهید کرد. تابع برای مواردی که به نمایش دقیق اعشاری نیاز دارند، استفاده از ماژول شکل دیگری از محاسبات دقیق توسط ماژول اگر استفاده زیادی از عملیات ممیز شناور دارید، بهتر است نگاهی به بسته NumPy و بسیاری از بستههای دیگر برای محاسبات ریاضی و آماری که توسط پروژه SciPy ارائه شدهاند بیندازید. به https://scipy.org مراجعه کنید. پایتون ابزارهایی ارائه میدهد که در آن موارد نادری که واقعاً میخواهید مقدار دقیق یک عدد ممیز شناور را بدانید، میتوانند کمککننده باشند. متد >>> 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 از آنجا که این نمایش دقیق است، برای انتقال مطمئن مقادیر بین نسخههای مختلف پایتون (مستقل از پلتفرم) و تبادل داده با زبانهای دیگری که از همین قالب پشتیبانی میکنند (مانند Java و C99) مفید است. یکی دیگر از ابزارهای مفید، تابع >>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 == 1.0 False >>> sum([0.1] * 10) == 1.0 True تابع >>> arr = [-0.10430216751806065, -266310978.67179024, 143401161448607.16, ... -143401161400469.7, 266262841.31058735, -0.003244936839808227] >>> float(sum(map(Fraction, arr))) # Exact summation with single rounding 8.042173697819788e-13 >>> math.fsum(arr) # Single rounding 8.042173697819788e-13 >>> sum(arr) # Multiple roundings in extended precision 8.042178034628478e-13 >>> total = 0.0 >>> for x in arr: ... total += x # Multiple roundings in standard precision ... >>> total # Straight addition has no correct digits! -0.0051575902860057365 15.1. خطای نمایش¶این بخش مثال "0.1" را با جزئیات توضیح میدهد و نشان میدهد که چگونه میتوانید خودتان تحلیل دقیقی از چنین مواردی انجام دهید. فرض بر این است که با نحوه نمایش اعداد ممیز شناور دودویی آشنایی ابتدایی دارید. Representation error به این واقعیت اشاره دارد که برخی (در واقع بیشتر) کسرهای دهدهی نمیتوانند بهطور دقیق به شکل کسرهای دودویی (مبنای ۲) نمایش داده شوند. این دلیل اصلی آن است که پایتون (یا Perl، C، C++، Java، Fortran و بسیاری زبانهای دیگر) اغلب عدد اعشاری دقیقی را که انتظار دارید نمایش نمیدهد. چرا چنین است؟ 1/10 نمیتواند بهطور دقیق به شکل یک کسر دودویی نمایش داده شود. از سال ۲۰۰۰ میلادی تاکنون، تقریباً همه ماشینها از محاسبات ممیز شناور دودویی IEEE 754 استفاده میکنند و تقریباً همه پلتفرمها اعداد float پایتون را به مقادیر «دقت مضاعف» "IEEE 754 binary64" نگاشت میکنند. مقادیر IEEE 754 binary64 دارای ۵۳ بیت دقت هستند، بنابراین هنگام ورود مقدار، کامپیوتر تلاش میکند "0.1" را به نزدیکترین کسری که به شکل J/2** N است تبدیل کند؛ جایی که J یک عدد صحیح با دقیقاً ۵۳ بیت است. بازنویسی 1 / 10 ~= J / (2**N) به شکل J ~= 2**N / 10 و با یادآوری اینکه J دقیقاً ۵۳ بیت دارد (یعنی >>> 2**52 <= 2**56 // 10 < 2**53 True یعنی ۵۶ تنها مقدار N است که باعث میشود J دقیقاً ۵۳ بیت داشته باشد. سپس بهترین مقدار ممکن برای J، همان خارجقسمت گرد شده است: >>> q, r = divmod(2**56, 10) >>> r 6 از آنجا که باقیمانده بیشتر از نصف ۱۰ است، بهترین تقریب با گرد کردن به بالا به دست میآید: >>> q+1 7205759403792794 بنابراین بهترین تقریب ممکن برای "1/10" در دقت مضاعف IEEE 754 برابر است با: 7205759403792794 / 2 ** 56 تقسیم صورت و مخرج بر دو، این کسر را به شکل زیر ساده میکند: 3602879701896397 / 2 ** 55 توجه کنید که چون به بالا گرد کردیم، این مقدار در واقع کمی بزرگتر از 1/10 است؛ اگر به بالا گرد نمیکردیم، خارجقسمت کمی کوچکتر از 1/10 میشد. اما در هیچ حالتی نمیتواند دقیقاً برابر 1/10 باشد! بنابراین کامپیوتر هرگز مقدار 1/10 را «نمیبیند»؛ چیزی که میبیند همان کسر دقیق بالا است، یعنی بهترین تقریب دقت مضاعف IEEE 754 که میتواند به دست آورد: >>> 0.1 * 2 ** 55 3602879701896397.0 اگر آن کسر را در 10**55 ضرب کنیم، میتوانیم مقدار را تا ۵۵ رقم اعشار مشاهده کنیم: >>> 3602879701896397 * 10 ** 55 // 2 ** 55 1000000000000000055511151231257827021181583404541015625 یعنی مقدار دقیق ذخیرهشده در کامپیوتر برابر با مقدار اعشاری زیر است: 0.1000000000000000055511151231257827021181583404541015625 بهجای نمایش مقدار کامل اعشاری، بسیاری از زبانها (از جمله نسخههای قدیمیتر پایتون) نتیجه را تا ۱۷ رقم معنادار گرد میکنند: >>> 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'
© حق نشر 2001 Python Software Foundation.
این صفحه تحت مجوز بنیاد نرمافزار پایتون نسخه ۲ منتشر شده است. مثالها، دستورالعملها، و سایر کدهای موجود در مستندات همچنین تحت مجوز بدون بند BSD منتشر شدهاند. برای اطلاعات بیشتر به تاریخچه و مجوز مراجعه کنید. بنیاد نرمافزار پایتون یک شرکت غیرانتفاعی است. لطفاً کمک مالی کنید. آخرین بهروزرسانی در نوامبر 12, 2026 (06:19 UTC). آیا اشکالی یافتید؟ ساخته شده با Sphinx 9.1.0. |
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |