المستوى: مبتدئ. المقال ده ليك لو لسه بتتعلّم البرمجة وصدمك إن رقم كبير لما تزود عليه 1 بيطلع سالب. هتفهم السبب من غير أي خلفية في الرياضيات المتقدمة.
ليه 2147483647 + 1 بيطلّع رقم سالب؟ شرح Integer Overflow للمبتدئ
لو كتبت سطر بيزود 1 على أكبر رقم صحيح، ولقيت الناتج رقم سالب ضخم زي 2147483648-، اطمئن: ده مش غلطة منك ولا bug في اللغة. ده سلوك متوقّع اسمه Integer Overflow (فيضان الأعداد الصحيحة). في آخر المقال هتعرف بالظبط ليه بيحصل، وإزاي تمنعه قبل ما يوقّعلك حسابات الفلوس أو عدّاد المشاهدات.
المشكلة باختصار
الكمبيوتر مش بيخزّن الأرقام في مساحة لا نهائية. كل رقم صحيح بياخد عدد ثابت من الخانات (bits). لما الرقم يكبر أكتر من المساحة دي، مفيش مكان يكمّل فيه، فبيلفّ من الأول — بالظبط زي عدّاد السيارة. النتيجة: رقم كبير جدًا فجأة بيبقى أصغر رقم سالب.
الفكرة بمثال: عدّاد السيارة
تخيّل عدّاد كيلومترات قديم فيه 6 خانات. أقصى رقم يقدر يوصله هو 999999. لو السيارة مشيت كيلومتر واحد زيادة، العدّاد مش هيكتب 1000000، لأن مفيش خانة سابعة. هيلفّ ويرجع 000000.
الكمبيوتر بيعمل نفس الحاجة بالظبط، بس بخانات ثنائية (0 و 1) مش عشرية. والفرق الوحيد إنه بيلفّ لرقم سالب مش للصفر، وهنشوف السبب دلوقتي.
المفهوم علميًا: البت، والثنائي، والمتمم الثنائي
الكمبيوتر بيخزّن كل حاجة كخانات ثنائية. النوع الأشهر للأعداد الصحيحة اسمه int32، وهو 32 خانة. واحدة من الـ 32 محجوزة لإشارة الرقم (موجب ولا سالب)، فبيتبقّى 31 خانة للقيمة نفسها.
عدد القيم اللي تقدر تتمثّل في 32 خانة هو 2 أس 32 = حوالي 4.29 مليار قيمة. بنقسمها نص للموجب ونص للسالب، فالمدى بيبقى من 2147483648- لحد 2147483647 (وهو 2 أس 31 ناقص 1).
طب ليه بيطلع سالب بالذات؟
الكمبيوتر بيمثّل الأرقام السالبة بطريقة اسمها المتمم الثنائي (Two's Complement). الخانة الأولى من الشمال بتشتغل كـ "إشارة": لو 0 الرقم موجب، لو 1 الرقم سالب.
أكبر رقم موجب 2147483647 كل خاناته 1 ما عدا خانة الإشارة (0). لما تزود عليه 1، الجمع بيكبّس على خانة الإشارة ويخليها 1، وكل باقي الخانات ترجع 0. القيمة دي في المتمم الثنائي معناها بالظبط 2147483648-. عشان كده الرقم مش بيرجع صفر زي العدّاد، بيقفز لأصغر رقم سالب.
كود شغّال يوريك الظاهرة
الكود ده Go وبيشتغل زي ما هو. جرّبه على Go Playground:
package main
import "fmt"
func main() {
var x int32 = 2147483647 // أقصى قيمة لـ int32
fmt.Println(x) // 2147483647
x = x + 1
fmt.Println(x) // -2147483648 ← فاض ولفّ للسالب
}
خد بالك من فرق مهم بين اللغات، عشان ميحصلش لخبطة:
- Go و Java و C و Rust: أنواع زي
int32/intليها حجم ثابت، فالفيضان بيحصل وبيلفّ بصمت (في C/Go) أو بيقطعها (Java). - Python: الأعداد الصحيحة فيه بلا حدود (arbitrary precision)، فـ
2147483647 + 1بترجّع 2147483648 عادي من غير فيضان. - JavaScript: الأرقام كلها 64-bit float، فمفيش فيضان لكن فيه فقدان دقة بعد 9007199254740991 (وهي
Number.MAX_SAFE_INTEGER).
مش كلام نظري: 3 حوادث حقيقية حصلت فعلًا
- Gangnam Style و YouTube (2014): عدّاد المشاهدات كان
int32. لما أغنية PSY عدّت 2,147,483,647 مشاهدة، العدّاد وصل آخره. يوتيوب اضطر يرقّيه لـ 64-bit (سقف جديد حوالي 9.2 كوينتيليون). المصدر: TechCrunch. - بوينج 787 (2015): عدّاد داخلي 32-bit في وحدات التحكم بالمولّدات بيزيد كل 10 مللي ثانية. بعد 248 يوم تشغيل متواصل بيفيض، وممكن يطفّي كل مولّدات الطاقة في الجو. هيئة الطيران الأمريكية (FAA) ألزمت بإعادة تشغيل الطائرات دوريًا لحين الإصلاح. المصدر: The Register والأمر الرسمي من FAA.
- لعبة Pac-Man (شاشة الموت في المرحلة 256): رقم المرحلة متخزّن في 8 خانات (أقصاه 255). عند المرحلة 256 العدّاد بيفيض، فنص الشاشة بيتحوّل لرموز عشوائية وتبقى اللعبة مستحيلة. المصدر: Wikipedia.
ازاي تتجنبها — 4 حلول وكل واحد بتمنه
- استخدم 64-bit (int64/bigint): بيرفع السقف لـ 9.2 كوينتيليون تقريبًا. التكلفة: كل رقم بياخد 8 بايت بدل 4. في جدول فيه مليار صف، ده فرق ~4 جيجا تخزين — غالبًا مقبول.
- دقة لا نهائية (BigInteger / Python int / JS BigInt): مفيش فيضان أبدًا. التكلفة: العمليات الحسابية أبطأ بكتير (ممكن 10 لـ 50 ضعف) واستهلاك ذاكرة أعلى.
- افحص قبل الجمع: في Java استخدم
Math.addExactاللي بيرمي استثناء بدل ما يلفّ بصمت. التكلفة: سطر زيادة وتعامل مع الخطأ، لكنه أأمن بكتير. - استخدم unsigned لو مش محتاج سالب أبدًا:
uint32بيوصل لـ 4.29 مليار بدل 2.1 مليار. التكلفة: لسه بيلفّ عند آخره، فهو بيأجّل المشكلة مش بيحلها.
الافتراض هنا إنك بتتعامل مع لغة بأنواع ثابتة الحجم (Go/Java/C/Rust). لو شغّال Python خالص، النقطة 1 و2 مش لازمين أصلًا.
متى متشغّلش بالك بالموضوع
الفيضان مش مشكلة في كل الحالات. متضيّعش وقتك لو:
- الرقم بطبيعته صغير ومش بيكبر مع الوقت (سن إنسان، عدد عناصر قائمة صغيرة).
- شغّال Python أو Ruby، لأن أعدادهم الصحيحة بلا حدود.
- القيمة في JavaScript وأقل من 9 كوادريليون (تحت
MAX_SAFE_INTEGER).
المشكلة بتبقى خطيرة بس مع عدّادات بتكبر بلا توقف: مشاهدات، معرّفات (IDs) بتتسلسل، أو طوابع زمنية بالمللي ثانية.
الخطوة التالية
افتح الكود أو سكيمة قاعدة البيانات بتاعتك ودوّر على أي عمود من نوع int/int32 بيعدّ حاجة بتكبر مع الوقت (views، IDs، أي عدّاد). لو لقيت واحد ممكن يعدّي 2.1 مليار خلال عمر المشروع، حوّله لـ bigint دلوقتي قبل ما يفيض في الإنتاج. غيّر عمود واحد النهاردة وجرّب الـ migration على نسخة تجريبية الأول.
المصادر
- TechCrunch — Gangnam Style Broke YouTube's Code (2014)
- Exploring Binary — Gangnam Style Overflows YouTube Counter
- Federal Register — FAA Airworthiness Directive 2015-10066 (Boeing 787)
- The Register — Boeing 787 GCU 248-day overflow
- Wikipedia — Two's Complement (المتمم الثنائي)
- Wikipedia — Integer Overflow
- MDN — Number.MAX_SAFE_INTEGER