مستوى المقال: مبتدئ. لو لسه بادئ في البرمجة ولقيت الكمبيوتر بيطلّع نتيجة غريبة في جمع أرقام عشرية بسيطة، المقال ده ليك. هتفهم السبب من الجذر، وتعرف تتصرف صح في حسابات الفلوس.
ليه 0.1 + 0.2 لا تساوي 0.3 في كل لغات البرمجة؟
افتح الـ console في المتصفح دلوقتي واكتب 0.1 + 0.2. هتطلعلك 0.30000000000000004، مش 0.3. نفس النتيجة في Python و Java و C و Go و Rust. ده مش عيب في لغة معينة، وده مش bug في كودك. ده سلوك مقصود في طريقة تخزين الأرقام العشرية في الكمبيوتر.
المشكلة باختصار
الكمبيوتر بيخزّن الأرقام في نظام ثنائي (binary)، يعني أصفار وآحاد بس. ساعات الكسر العشري اللي إنت كتبته بالنظام العشري ما لهوش تمثيل دقيق في النظام الثنائي. فالكمبيوتر بيقرّبه لأقرب رقم يقدر يخزّنه، والفرق الصغير ده بيظهر لما تجمع رقمين.
النتيجة العملية: لو بتحسب فلوس أو فواتير أو قياسات وبتقارن أرقام عشرية بـ ==، ممكن الكود يطلّع نتيجة غلط في حالات ما تتوقعهاش، والعميل يشوف فاتورة فيها قرش زيادة أو ناقص.
المثال اللي بيقرّب الفكرة
تخيل إني طلبت منك تكتب ثلث (⅓) في النظام العشري على ورقة. هتكتب 0.3333... والتلاتات مش بتخلص. مهما كتبت أرقام، فضل في فرق صغير عن الثلث الحقيقي. مفيش عدد محدود من الخانات يمثّل الثلث بالظبط في النظام العشري.
الكمبيوتر بيقابل نفس المشكلة، بس مع الرقم 0.1. في النظام الثنائي، الرقم 0.1 بيتكتب 0.000110011001100110011... والنمط ده بيكرّر لما لا نهاية. الكمبيوتر عنده مساحة محدودة (64 بت)، فبيقصّ الرقم عند خانة معينة ويقرّبه. وقت ما تجمع نسختين مقرّبتين من 0.1 و 0.2، الفرق الصغير بيتراكم ويظهر في آخر النتيجة.
التفسير العلمي: معيار IEEE 754
أغلب اللغات بتخزّن الأرقام العشرية بصيغة اسمها IEEE 754 double precision (64 بت). من دول 52 بت بس مخصصة للجزء الكسري (mantissa)، يعني دقة حوالي 15 إلى 17 رقم عشري معنوي.
الرقم 0.1 لمّا بيتخزّن، بيتقرّب فعليًا لـ 0.1000000000000000055511151231257827.... والرقم 0.2 بيتقرّب لقيمة قريبة بنفس الشكل. تجمعهم، فيطلع 0.30000000000000004. الفرق هنا في حدود 1.1 × 10⁻¹⁶، رقم صغير جدًا، بس كفاية إنه يخلّي 0.1 + 0.2 == 0.3 ترجع false.
الحل العملي: 3 طرق مرتبة بالأولوية
- للفلوس: خزّن بالقروش كأعداد صحيحة (integers). بدل ما تخزّن 19.99 جنيه، خزّن 1999 قرش. الأعداد الصحيحة دقيقة 100% في الجمع والطرح. ده الحل الأهم وأعلى ROI لأي تطبيق فيه مدفوعات.
- للمقارنة: قارن بهامش خطأ (epsilon) بدل المساواة المباشرة.
- لو محتاج كسور عشرية دقيقة: استخدم نوع Decimal. زي في Python أو مكتبة في JavaScript.