هذا المقال يتطلب مستوى: مبتدئ
ليه 0.1 + 0.2 مش بيساوي 0.3؟
لو فتحت أي لغة برمجة وكتبت 0.1 + 0.2 وطلعلك 0.30000000000000004، اطمن: ده مش باج في اللغة ولا في جهازك. بعد ما تخلص القراءة هتعرف بالظبط ليه بيحصل، وأهم من كده هتعرف تكتب كود مايقعش في الفخ ده وانت بتتعامل مع فلوس أو مقارنات.
المشكلة باختصار
الكمبيوتر مش بيخزّن الأرقام العشرية زي ما انت كاتبها بالظبط. بيخزّنها بصيغة تقريبية. التقريب ده صغير جدًا، لكنه بيظهر لما تجمع أو تقارن، فتلاقي نتيجة غريبة زي 0.30000000000000004. المشكلة دي موجودة في بايثون وجافاسكريبت وC++ وجافا وأي لغة بتستخدم نظام الأرقام العشرية المعياري.
مثال بسيط قبل ما ندخل في التقنية
خلّينا نبعد عن الكمبيوتر شوية. جرّب تقسم 1 على 3 بإيدك في النظام العشري اللي احنا بنستخدمه. هتطلع 0.3333333... والتلاتات مش بتخلص. لو حبيت تكتبها على ورقة لازم توقف عند حد معيّن، يعني هتكتب رقم تقريبي مش دقيق 100%.
الكمبيوتر بيعمل نفس الحاجة بالظبط، لكن هو بيشتغل بنظام تاني اسمه النظام الثنائي (بس صفر وواحد). في النظام الثنائي، رقم بسيط زينا زي 0.1 بيتحول لكسر بيتكرر لما لا نهاية، زي ما 1÷3 اتكرر عندنا. وبما إن الكمبيوتر مايقدرش يخزّن أرقام لا نهائية، فبيقطعها عند حد، والقطع ده هو مصدر الخطأ.
اللي بيحصل فعلاً جوه الكمبيوتر
دلوقتي بالتفاصيل والدقة العلمية. أغلب اللغات بتخزّن الأرقام العشرية بمعيار عالمي اسمه IEEE 754، وتحديدًا النوع double اللي بياخد 64 بت. من الـ 64 بت دي، في 52 بت بس مخصصة للأرقام المعنوية (الجزء اللي بيحدد الدقة).
الرقم 0.1 في الثنائي = 0.0001100110011... والمجموعة 1001 بتفضل تتكرر للأبد. الكمبيوتر بياخد أول 52 بت بس ويرمي الباقي. فالقيمة اللي اتخزنت فعلًا مش 0.1 بالظبط، دي حاجة زي 0.1000000000000000055511151231257827. نفس الكلام بيحصل مع 0.2. لما تجمع القيمتين التقريبيتين، الخطأ الصغير بيتراكم ويظهر في النتيجة على شكل 0.30000000000000004.
الافتراض هنا إن اللغة بتستخدم double بدقة 64 بت، وده الوضع الافتراضي في بايثون وجافاسكريبت. الدقة الفعلية بتوصل لحوالي 15 إلى 17 رقم عشري معنوي، والخطأ في حدود 1e-16 تقريبًا.
جرّب الكود ده بنفسك في بايثون وشوف بعينك:
>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.2 == 0.3
False
# والخطأ بيتراكم لو جمعت كتير
total = 0.0
for _ in range(10):
total += 0.1
print(total) # 0.9999999999999999
print(total == 1.0) # False