میثم پورگنجی
میثم پورگنجی
خواندن ۴ دقیقه·۶ سال پیش

چرا ۰.۲+۰.۱ برابر با ۰.۳ نیست؟

کنسول پایتون: حاصل جمع یک دهم و دو دهم، سه دهم نیست!
کنسول پایتون: حاصل جمع یک دهم و دو دهم، سه دهم نیست!

یکی از سالم‌ترین سرگرمی‌های هر برنامه‌نویس Backendی فحش دادن به جاوااسکریپت است و پریروز در پایان ساعت کاری همین بحث شیرین در شرکت در جریان بود. از میان ویژگی‌های داغان بی‌شمار این زبان زیبا و دوست‌داشتنی، نوبت به این نکته رسید که جمع یک دهم و دو دهم در جاوااسکریپت، سه دهم نیست! شاید باورش برایتان سخت باشد اما این اتفاق کاملا درست و منطقی است!

چیح؟

بله! حاصل جمع ۰.۱ و ۰.۲ برابر با ۰.۳ نیست و این گزاره کاملا درست و منطقی است! :))).

اینکه ۰.۲+۰.۱ مساوی ۰.۳ نمیشه، منطقی‌ه؟ منطقیییی؟ :))

بله آقا! بله! منطقی است! :)). ماجرا به نحوه‌ی پردازش و ذخیره‌سازی اعداد در کامپیوتر بر می‌گردد. تکلیف اعداد طبیعی مثل ۴ و ۰ و ۱۲ روشن است، مستقیم نمایش دودویی آنها ذخیره می‌شود و به راحتی مقدار اصلی نیز بازیابی می‌شود. اعداد منفی نیز به همین شیوه و گرفتن مکمل۲ ذخیره می‌شوند. اما برای اعداد اعشاری که از دو قسمت صحیح و اعشار تشکیل شده است چه می‌توان کرد؟ چگونه ۱۲۳.۴۵۶۷۸۹ را ذخیره کنیم؟

بِبُر!

پاسخ اول به این سوال یک راه‌حل ساده است! محل ذخیره را سه قسمت کن! قسمت اول که علامت عدد را نشان می‌دهد (صفر یعنی عدد ذخیره‌شده مثبت است و یک یعنی منفی است)، قسمت دوم را برای ذخیره‌سازی قسمت صحیح عدد اعشاری استفاده کن و قسمت سوم را برای ذخیره‌سازی اعشار آن. یعنی اگر ۸ بیت فضا به این شکل تقسیم شود که ۳ بیت برای قسمت صحیح عدد و ۴ بیت برای ذخیره‌ی اعشار، در اینصورت بزرگترین عدد اعشاری‌ای که با این روش می‌توان ذخیره کرد ۸.۲۳ است! به بیان دیگر این روش همیشه یک پنجره در دست دارد که هر رقمی که بیرون از این پنجره قرار بگیرد را می‌بُرَد و بدور می‌ریزد!

به روش قبل، روش ممیز ثابت (fixed point) برای ذخیره‌سازی اعداد اعشاری می‌گویند.

تکون بده! ممیز رو تکون بده!

راه‌حل دوم اما اهل مدارای بیشتری است! ایده‌ی کلی این است که قبل از ذخیره‌ی عدد اعشاری، آنرا نرمال کنیم و بصورت نمایش علمی بنویسیم. مثلا نمایش علمی ۱۱.۵ میشود ۱.۱۵ در ده به توان ۱، به معنای دیگر، بجای آنکه پنجره بدست همه را با یک چشم بسنجیم، اول آنها را در شرایط نرمال قرار می‌دهیم و سپس آنها را ذخیره می‌کنیم! به این روش، روش ممیز شناور می‌گویند.

تا حالا فکر می‌کردم ریاضی‌دان‌ها بی‌سلیقه‌ن! تا اینکه الان دیدم بلد نیستم با ابزار رسم تصویر کار کنم! :|
تا حالا فکر می‌کردم ریاضی‌دان‌ها بی‌سلیقه‌ن! تا اینکه الان دیدم بلد نیستم با ابزار رسم تصویر کار کنم! :|

طبق تصویر بالا می‌خواهیم عدد ۰.۱ را ذخیره کنیم. ابتدا آنرا به صورت دودویی‌اش تبدیل می‌کنیم و سپس آنرا نرمال می‌کنیم. متناظر با هر رنگ جدول زیر را در نظر می‌گیریم:

جدول رنگی بیت‌ها!
جدول رنگی بیت‌ها!

از سمت راست، رنگ طلایی متناظر با ارقام طلایی است، به این ارقام، ارقام بامعنا می‌گوئیم. منفی چهار در نمایش نرمال ۰.۱ را بصورت مکمل دو در قسمت سبزرنگ ذخیره می‌کنیم و در نهایت اون آبی روشن خوشگله هم نمایانگر مثبت یا منفی بودن عدد است!

خخخببببب؟

تا اینجا دیدیم که چگونه یک عدد ناصحیح را می‌توان در کامپیوتر ذخیره کرد. به عنوان پست بر می‌گردیم و در نمایش باینری، جمع را انجام می‌دهیم:

به نظر شما نمایش دهدهی عدد سبز رنگ چه مقداری است؟ :))). دقیقا! ۰.۳۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۴ :)).

یعنی چی این؟ :)). ریاضی کشک؟ همه چی کشک؟

نه! یک قضیه در آنالیز ریاضی وجود دارد که هر عدد گویا دارای بسط دودویی مختوم یا تکراری است و هر بسط مختومی با یک بسط نامختوم تکراری معادل است! از عکس نقیض این گزاره نتیجه می‌شود که اعداد گنگ بسط دودویی نامختوم و ناتکراری دارند!‌ بنابراین هر عدد دارای یک بسط نامختوم است اما مشکل اینجاست که کامپیوتر دارای فضای محدود است! برای مثال در دقت معمولی تنها ۳۲ بیت فضا برای ذخیره‌ی یک عدد اعشاری در نظر گرفته می‌شود که از آن ۱ بیت برای علامت عدد است، ۸ بیت برای ذخیره‌ی مکمل دوی نما و ۲۳ بیت برای ذخیره‌ی ارقام بامعنا (جدول رنگی چند پاراگراف بالاتر را ببینید!). این وسط حتی اگر جمع در یک فضای با دقت بزرگتر انجام شود، در نهایت تعداد محدودی از آن ذخیره می‌شوند. در این بین انباشتگی بیت‌ها در هنگام جمع در آن فضای بزرگتر موجب می‌شود تا در هنگام برش، احتمالا تنی چند از بیت‌ها صورت متفاوتی نسبت به آنچه با تبدیل مستقیم بدست می‌آید را داشته باشند، این همان چیزی که شرط در تصویر اول را غلط می‌کند و آن ۴ آخر را تولید می‌کند! در نهایت با این توضیحات رفتار پایتون و جاوااسکریپت و هر زبان دیگری به نظر منطقی است.

چه کنیم؟

  • این وظیفه‌ی برنامه‌نویس است که محاسبات در دقت مناسب انجام شود. در محاسبات و با توجه به دقت موردنیاز دقت را از بین معمولی (float یا single) یا ۳۲ بیتی، مضاعف (double) یا ۶۴ بیتی و توسیع‌یافته (extended) یا ۱۲۸ بایتی انتخاب کنید.
  • این وظیفه‌ی برنامه‌نویس است که رفتار اعداد با آنچه مورد انتظار است منطبق باشد. اگر در سیستم مالی‌ای کار می‌کنید که نیاز است تا ۳ رقم اعشار اعداد دقیق باشند، در هنگام ارسال اطلاعات، عدد محاسبه شده‌ی خود را تا ۳ رقم اعشار گرد کنید.
  • با شیوه‌ی محاسبات با ممیز شناور آشنا باشید. مثلا این سند خوبی است!
  • درس محاسبات عددی دانشگاه را جدی بگیرید! باور کنید مباحث آن درس در زندگی روزمره کاربرد عملی دارد! به خدا قسم! :))).
  • خطاست دیگه! سخت نگیر! ;).
ریاضیاتمحاسبات عددیآنالیز عددیieee754
مهندسی داده، برنامه‌نویسی و ریاضی
شاید از این پست‌ها خوشتان بیاید