هذا المقال يتطلب مستوى مبتدئ.
فتحت Console المتصفح وكتبت 0.1 + 0.2، فجالك 0.30000000000000004. مش غلط في المتصفح، ولا غلط في JavaScript. لو جربت نفس المعادلة في Python أو Java أو Go أو Swift، النتيجة هتكون نفسها بالظبط. السبب في الكمبيوتر نفسه، مش في اللغة.
Floating Point: السبب الحقيقي وراء الأرقام الغريبة
المقال ده بيشرحلك ليه الكمبيوتر بيعمل كده، وإمتى المشكلة دي ممكن تكسر تطبيقك (خصوصًا لو بيتعامل مع نقود)، وإيه الحلول العملية اللي تقدر تطبقها في 5 دقائق.
مثال بسيط: المسطرة الناقصة
قبل ما ندخل في التعريف العلمي، خد المثال ده:
عندك مسطرة طولها 10 سنتيمتر، ومعلّم عليها فقط كل ربع سنتيمتر (0.25, 0.5, 0.75, 1.0…). طلبت منك ترسم خط طوله ثلث سنتيمتر بالظبط (0.333…). هتحاول، هتلاقي إن العلامة دي مش موجودة على المسطرة. اضطريت تختار أقرب علامة متاحة، يعني 0.25 أو 0.5. النتيجة قريبة من ثلث، لكن مش ثلث بالظبط.
الكمبيوتر بيعمل نفس الكلام، لكن بـ نظام ثنائي (binary). علامات مسطرته كلها قوى الرقم 2: نص (1/2)، ربع (1/4)، ثُمُن (1/8)، ربع الـ ثُمُن (1/16)، وهكذا. الرقم 0.1 ما يقعش بالظبط على أي علامة من دول. فالكمبيوتر بيخزّن أقرب رقم ممكن، اللي بيكون 0.1000000000000000055511151231257827021181583404541015625. الفرق صغير جدًا، لكنه موجود.
لما تجمع تقريبين (0.1 + 0.2)، الفروق الصغيرة دي بتتراكم، فبتطلع نتيجة 0.30000000000000004 بدل 0.3 المضبوط.
التعريف العلمي: IEEE 754
IEEE 754 معيار عالمي صدر سنة 1985 بيحدّد إزاي الكمبيوتر يخزّن ويحسب الأرقام العشرية. كل لغة برمجة بتستخدمه تقريبًا، عشان كده النتيجة واحدة في كل اللغات.
المعيار بيقسّم أي رقم عشري لـ 3 أجزاء داخل الذاكرة:
- sign bit — bit واحد بيحدد لو الرقم موجب ولا سالب
- exponent — مجموعة bits بتحدد المدى (الأس)
- mantissa — مجموعة bits بتخزن الأرقام المعنوية
في النوع الأكثر شيوعًا (double precision أو float64) اللي JavaScript بتستخدمه افتراضيًا لكل الأرقام، الرقم بياخد 64 bit مقسومين كده: 1 sign + 11 exponent + 52 mantissa.
المشكلة إن 0.1 في النظام الثنائي بيتحوّل لكسر دوري لانهائي (زي 0.333… في النظام العشري). لما الكمبيوتر بيقصّه عند 52 bit، بيحصل rounding، والنتيجة بتفرق عن 0.1 الحقيقي بفرق صغير جدًا (تقريبًا 5.55 × 10⁻¹⁸).