مستوى المقال: متوسط — مكتوب للمطوّر اللي بيكتب كود بيلمس أرقام أو فلوس.
ليه 0.1 + 0.2 مش بيساوي 0.3 في كل لغات البرمجة
لو جمعت 0.1 + 0.2 في بايثون أو جافاسكريبت ولقيت الناتج 0.30000000000000004، ده مش bug في لغتك ولا عطل في معالجك. ده سلوك متعمّد ومضبوط 100%. في آخر المقال هتعرف ليه بيحصل، وتقدر تمنعه يكسر حساب فلوس بسطر أو اتنين.
المشكلة باختصار
تخيّل عربة شراء فيها 3 منتجات سعر كل واحد 0.10 دولار. المفروض المجموع 0.30. لكن الكود بيقولك المجموع 0.30000000000000004، وبوابة الدفع بترفض العملية لأن الرقم مش مطابق. المشكلة دي بتظهر فجأة في الفلوس، والـ unit test اللي مكتوب فيه assert total == 0.3 بيفشل وانت مش فاهم ليه.
>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.2 == 0.3
False
الحكاية ببساطة الأول
قبل أي كلام علمي، خد المثال ده. جرّب تكتب ثلث (1 ÷ 3) في النظام العشري اللي بنعدّ بيه: 0.3333 وهكذا. كل ما تزوّد تلاتات عمرك ما توصل لثلث بالظبط، لأنه كسر متكرر لا نهائي عندنا.
الكمبيوتر بيحسب بنظام تاني خالص: النظام الثنائي، أرقامه 0 و1 بس. وفيه كسور سهلة جدًا عندنا — زي 0.1 — بتطلع كسور متكررة لا نهائية عنده، بالظبط زي ما 1/3 لا نهائي عندنا. فالكمبيوتر بيخزّن أقرب رقم يقدر عليه، والفرق الصغير ده هو اللي بيطلع أول ما تجمع.
اللي بيحصل فعلاً جوّه IEEE 754
دلوقتي بالتفاصيل. أغلب اللغات بتخزّن الكسور العشرية في نوع اسمه double بمعيار IEEE 754، وحجمه 64 بت متقسّمين كالتالي: بت واحد للإشارة، 11 بت للأس (exponent)، و52 بت للجزء الكسري (mantissa).
الرقم 0.1 في النظام الثنائي بيساوي 0.0001100110011 وهكذا، والكتلة «0011» بتتكرر للأبد. بما إن عندك 52 بت بس تخزّن فيهم الكسر، المعالج بيقرّب لأقرب قيمة ممكنة. القيمة المخزّنة لـ 0.1 أكبر شوية من 0.1 الحقيقي، ولـ 0.2 كمان، فلما تجمعهم بيتراكم خطأ في حدود 5.5×10⁻¹⁷ ويظهر في آخر الأرقام.
الافتراض هنا إنك بتستخدم double، وهو الافتراضي في JavaScript و Python وأغلب الـ JSON. الـ double بيديك دقة 15 إلى 17 رقم عشري معنوي تقريبًا — كفاية لأغلب الحسابات، لكنه مش مضبوط بالمللي للكسور العشرية زي الفلوس.
الحل العملي: 3 طرق وكل واحدة بتمنها
ركّز على مبدأين: متقارنش أرقام عشرية بـ ==، ومتخزّنش فلوس في float أو double.
- احسب بالأعداد الصحيحة (أصغر وحدة). خزّن الفلوس بالقرش/السنت كأرقام صحيحة، والحساب بيطلع مضبوط 100%.
- استخدم Decimal. النوع ده بيحسب بنظام عشري حقيقي، فـ 0.1 + 0.2 بتطلع 0.3 بالظبط — بشرط تبعت قيمك كنص مش float.