ليه 0.1 + 0.2 ما بيساويش 0.3؟ الفاصلة العائمة من الصفر
المستوى: مبتدئ. المقال ده متكتب لحد لسه بادئ في البرمجة. أي مفهوم صعب هتلاقيه متشروح بمثال بسيط الأول، وبعدين بالتعريف العلمي الدقيق.
بعد المقال ده هتعرف بالظبط ليه الكمبيوتر بيقولك 0.1 + 0.2 = 0.30000000000000004، إزاي الغلط الصغير ده ممكن يضيّع قروش في كل فاتورة، وإزاي تمنعه بسطر واحد.
المشكلة باختصار
افتح أي كونسول بايثون أو جافاسكريبت دلوقتي واكتب 0.1 + 0.2. مش هيطلعلك 0.3. هيطلعلك 0.30000000000000004. ده مش عطل في اللغة ولا في جهازك. ده سلوك متعمّد وموجود في كل لغة بتشتغل بأرقام عشرية عادية: بايثون، جافاسكريبت، Java، C.
لو ماعرفتش السبب، هتفضل تكتب كود بيقارن أرقام عشرية بـ == وبيفشل من غير ما تفهم ليه. ولو بتحسب فلوس، الانحراف ده بيتراكم.
الحكاية بمثال بسيط الأول
تخيّل إنك عايز تكتب "ثلث" بالأرقام العشرية. تقسم 1 على 3، يطلعلك 0.3333333... والتلاتات دي مش بتخلص أبدًا. الورقة اللي بتكتب عليها محدودة، فلازم توقف عند حد وتقرّب. يعني إنت أصلًا مش بتكتب "ثلث" بالظبط، إنت بتكتب رقم قريب منه.
الكمبيوتر بيعاني من نفس المشكلة بالظبط، بس مع أرقام مختلفة. هو مابيفكّرش بنظام العشرة زينا، هو بيفكر بنظام الاتنين. وفي نظام الاتنين، الرقم 0.1 بيتحوّل لكسر دوري لا ينتهي، تمامًا زي الثلث عندنا.
دلوقتي الشرح العلمي الدقيق
الكمبيوتر بيخزّن الأرقام العشرية بصيغة اسمها الفاصلة العائمة مزدوجة الدقة (double precision floating point)، ومعرّفة في معيار عالمي اسمه IEEE 754. الرقم بيتخزّن في 64 بت متقسّمة تلات أجزاء: بت واحد للإشارة، 11 بت للأس، و52 بت للأرقام نفسها (المانتيسا).
الـ 52 بت دول بيدّوك دقة حوالي 15 إلى 17 خانة عشرية بس. أي حاجة بعد كده بتتقرّب. المشكلة إن 0.1 في النظام الثنائي محتاج عدد لا نهائي من البتات عشان يتكتب بالظبط. فالكمبيوتر بيقرّبه لأقرب رقم يقدر يخزّنه. نفس الكلام مع 0.2. فلما تجمع رقمين مقرّبين، الغلط الصغير في كل واحد بيظهر في الناتج على شكل ...04 في الآخر.
جرّبها بنفسك بكود شغّال
في بايثون:
>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.2 == 0.3
False
# جمع 0.1 عشر مرات المفروض يطلع 1.0
>>> sum(0.1 for _ in range(10))
0.9999999999999999
في جافاسكريبت نفس الحكاية: