مستوى المقال: مبتدئ. لو لسه بادئ في البرمجة ولقيت الكمبيوتر بيطلّع نتيجة غريبة في جمع أرقام عشرية بسيطة، المقال ده ليك. هتفهم السبب من الجذر، وتعرف تتصرف صح في حسابات الفلوس.
ليه 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. زي
decimal.Decimalفي Python أو مكتبةbig.jsفي JavaScript.
// المشكلة
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false
// الحل 1: للفلوس، اشتغل بالقروش (أعداد صحيحة)
const price = 1999; // 19.99 جنيه = 1999 قرش
const tax = 320; // 3.20 جنيه = 320 قرش
const total = price + tax; // 2319 قرش، دقيق 100%
console.log((total / 100).toFixed(2)); // "23.19"
// الحل 2: قارن بهامش خطأ صغير (epsilon)
function almostEqual(a, b, eps = 1e-9) {
return Math.abs(a - b) < eps;
}
console.log(almostEqual(0.1 + 0.2, 0.3)); // true
# نفس المشكلة في Python
print(0.1 + 0.2) # 0.30000000000000004
# الحل: نوع Decimal للحسابات الحساسة
from decimal import Decimal
print(Decimal("0.1") + Decimal("0.2")) # 0.3 (بالظبط)
الـ trade-offs اللي لازم تعرفها
كل حل ليه ثمنه:
- التخزين بالقروش: بتكسب دقة كاملة. بتخسر إن لازم تفتكر دايمًا تقسم على 100 وقت العرض، وتتعامل مع القسمة بحذر (مثلًا تقسيم فاتورة على 3).
- مقارنة الـ epsilon: بتكسب مقارنات آمنة. بتخسر إنك لازم تختار قيمة epsilon مناسبة لمجال أرقامك، وغلط اختيارها بيرجّعك لنفس المشكلة.
- نوع Decimal: بتكسب دقة عالية وقراءة واضحة. بتخسر في السرعة: Decimal أبطأ من float بمراحل (في Python حوالي 20 إلى 100 ضعف حسب العملية)، فمش مناسب لملايين العمليات في الثانية.
متى لا تشغّل بالك بالموضوع أصلاً
لو شغلك مش حسابات مالية ولا قياسات دقيقة، الفرق الصغير ده غالبًا ملوش أي تأثير. مثلًا في رسم واجهات، أو حساب نسب تقريبية، أو فيزياء ألعاب، الفرق في حدود 10⁻¹⁶ مش هيبان لأي مستخدم. في الحالات دي استخدم float العادي وكمّل شغلك. الافتراض هنا إنك مش بتقارن النتيجة بـ == وبتعتمد على القيمة مباشرة.
الخطوة التالية
روح على أي مكان في كودك بتقارن فيه رقمين عشريين بـ == أو بتجمع فلوس بـ float. لو لقيت مقارنة مباشرة في حسابات مالية، حوّلها للتخزين بالقروش أو استبدل المقارنة بدالة almostEqual. ابدأ بأخطر مكان: شاشة الدفع أو الفاتورة.
المصادر
- IEEE 754-2019 Standard for Floating-Point Arithmetic — IEEE.
- David Goldberg, "What Every Computer Scientist Should Know About Floating-Point Arithmetic", ACM Computing Surveys, 1991.
- Python Documentation — "Floating Point Arithmetic: Issues and Limitations" (docs.python.org/3/tutorial/floatingpoint.html).
- MDN Web Docs — Number و Number.EPSILON (developer.mozilla.org).
- الموقع التوضيحي 0.30000000000000004.com لتجميع سلوك اللغات المختلفة.