المستوى: مبتدئ. هذا المقال موجّه لمن بدأ البرمجة حديثًا، ولا يفترض أي معرفة سابقة بطريقة تخزين الأرقام داخل الكمبيوتر. هتفهم بعده ليه رقم ممكن "يلف" ويبقى سالب فجأة، وإزاي تتجنّب ده.
ليه عدّاد مشاهدات يوتيوب وقف عند 2 مليار وانقلب سالب؟
في ديسمبر 2014 وصل فيديو "Gangnam Style" إلى 2,147,483,647 مشاهدة، وفجأة العدّاد اتلخبط. السبب مش سيرفرات يوتيوب، ولا اختراق. السبب نوع الرقم اللي كانوا بيعدّوا بيه. لو فهمت السطر الجاي، هتعرف ليه أي عدّاد في أي لغة ممكن يقع نفس الوقعة.
المشكلة باختصار
الكمبيوتر مش بيخزّن الرقم في مساحة لا نهائية. بيحجزله صندوق بحجم ثابت. الصندوق الشائع اسمه 32-bit signed integer، وبيسع أرقام من -2,147,483,648 لحد 2,147,483,647 وبس. لو وصلت للحد الأقصى وزوّدت واحد، الرقم مبيكبرش، لأ ده بيلف ويرجع لأصغر رقم سالب. ده اللي اسمه Integer Overflow.
اتخيلها زي عدّاد الكيلومترات في العربية
تخيّل عدّاد قديم في عربية بـ 6 خانات. أقصى رقم ممكن يوصله هو 999999. لو مشيت كيلومتر واحد كمان، الخانات بتقلب كلها لـ 000000 وتبدأ من الأول. مفيش خانة سابعة تستوعب الزيادة.
الـ integer زي العدّاد ده بالظبط، بفرق واحد: عشان بيخزّن أرقام سالبة كمان، أول ما يعدّي أكبر رقم موجب بيلف ناحية أكبر رقم سالب، مش ناحية الصفر. عشان كده 2,147,483,647 زائد واحد بيدّيك -2,147,483,648.
الشرح العلمي: ليه 32 بت بالظبط؟
الـ "بِت" (bit) خانة بتحمل 0 أو 1. صندوق فيه 32 بت بيقدر يمثّل 2^32 أي 4,294,967,296 احتمال مختلف. لكن لو عايزين نخزّن سالب وموجب، بنخصّص أول بت كـ إشارة (sign bit): 0 معناها موجب، و1 معناها سالب. الطريقة دي اسمها Two's Complement، وهي المعيار في كل المعالجات الحديثة.
النتيجة: نص المدى للموجب (لحد 2,147,483,647) ونص للسالب (لحد -2,147,483,648). لمّا تزوّد واحد على أكبر موجب، البتّات بتتحوّل من 0111...1 إلى 1000...0، وبما إن أول بت بقى 1، الكمبيوتر بيقراها على إنها أصغر رقم سالب. ده مش غلط في الكود، ده سلوك الـ hardware نفسه.
جرّبها بنفسك بالكود
الكود ده بلغة Go بيوريك الانفجار بأرقام حقيقية:
package main
import "fmt"
func main() {
var x int32 = 2147483647 // أكبر قيمة ممكنة في int32
fmt.Println(x) // 2147483647
x = x + 1 // زوّدنا واحد بس
fmt.Println(x) // -2147483648 ← لفّ وبقى سالب
}
في JavaScript الأرقام العادية بتطلع صح لأنها أصلًا 64-bit، لكن أول ما تفرض حساب 32 بت (بـ | 0) بترجع نفس المشكلة: