هذا المقال يتطلب مستوى: مبتدئ. لو لسه بادئ في البرمجة وصدمك إن الكمبيوتر "بيغلط" في جمع كسرين بسيطين، المقال ده ليك.
افتح أي محرر بايثون دلوقتي واكتب 0.1 + 0.2. الناتج اللي هيطلعلك مش 0.3، ده 0.30000000000000004. نفس الحاجة بتحصل في جافاسكريبت و C و Java وكل لغة تقريبًا. ده مش باج في اللغة، وده مش عطل في جهازك. ده سلوك مقصود اسمه الفاصلة العائمة (Floating Point). في آخر المقال هتعرف ليه بيحصل، وإزاي تتعامل معاه في كودك من غير ما يفسدلك حسابات الفلوس.
الفاصلة العائمة: ليه الكمبيوتر بيقرّب الكسور
المشكلة باختصار
الكمبيوتر بيخزّن الأرقام بنظام ثنائي (أصفار وواحدات بس). بعض الكسور البسيطة عندنا في النظام العشري مالهاش تمثيل دقيق ومنتهي في النظام الثنائي. فالجهاز بيقرّبها لأقرب قيمة يقدر يخزنها. التقريب الصغير ده هو اللي بيظهر في صورة ذيل غريب زي ...00004 لما تجمع رقمين.
الفكرة بمثال قبل الكلام العلمي
تخيّل إنك بتقسم رغيف على 3 أشخاص بالتساوي. كل واحد ياخد ثلث. لو حبيت تكتب الثلث بالأرقام العشرية هتكتب 0.3333... والتلاتات دي مش بتخلص أبدًا. لو وقفت عند 4 خانات (0.3333) يبقى انت قرّبت، والناتج "قريب جدًا" من الثلث بس مش بالظبط هو.
الكمبيوتر بيواجه نفس المشكلة بالظبط، بس مع الرقم 0.1. الرقم ده سهل علينا في النظام العشري، لكن في النظام الثنائي بيتكتب 0.000110011001... وبيفضل بيكرّر بلا نهاية، زي التلاتات بتاعتنا. فالجهاز بيقف عند حد معين ويقرّب. لما تجمع تقريبين مع بعض، الناتج بيبان فيه فرق بسيط جدًا.
الشرح العلمي الدقيق
الأرقام الكسرية في معظم اللغات بتتخزن حسب معيار اسمه IEEE 754 بصيغة "double" بطول 64 بت. الـ 64 بت دول متقسمين على 3 أجزاء: بت واحد للإشارة (موجب/سالب)، 11 بت للأس (يحدد مكان الفاصلة)، و52 بت بس لتخزين أرقام الرقم نفسه (المانتيسا).
الـ 52 بت دول بيدوك دقة حوالي 15 إلى 17 خانة عشرية معنوية. أي رقم محتاج أطول من كده بيتقرّب. عشان كده 0.1 المخزّن فعليًا مش 0.1 بالظبط، هو رقم أقرب ليه بفرق ضئيل جدًا في حدود 5.5e-17. لما تجمع 0.1 المقرّب مع 0.2 المقرّب، الفرقين بيتجمعوا ويظهروا في الناتج.