जब कोई संख्या लपेटकर शुरू से चल पड़ती है

तय चौड़ाई वाले पूर्णांक के पास अपने अधिकतम से ऊपर जगह नहीं होती, इसलिए उसमें एक जोड़ने पर न्यूनतम निकलता है। बिना चिह्न वाला आठ-बिट काउंटर 255 से 0 पर चला जाता है; चिह्न वाला 127 से −128 पर। न कोई अपवाद उठता है और न डिफ़ॉल्ट रूप से कोई संकेत प्रोग्राम तक पहुँचता है, और यही ओवरफ़्लो को त्रुटि-संदेश के बजाय एक तरह का बग बनाता है: गणित चुपचाप ग़लत होता है और आगे का सब कुछ उस पर भरोसा कर लेता है।

जो कूटलेखन यह कराता है वह टू-कॉम्प्लिमेंट है, और यह उसका दोष नहीं है। लपेटना सीधे उसी गुण से निकलता है जो उसे उपयोगी बनाता है, यानी जोड़ और घटाव को एक ही परिपथ से चला लेना।

लपेटना करता क्या है?

जो बिट समा नहीं पाए उन्हें गिरा देता है। जोड़ सामान्य रूप से चलता है, परिणाम को प्रकार से एक बिट अधिक चाहिए, और वह बिट गिर जाता है — जिसका मापांक में अर्थ है कि गणित 2 की चौड़ाई-घात के मापांक में हो रहा है।

चौड़ाई बिना चिह्न चिह्न सहित
8 बिट 0 से 255 −128 से 127
16 बिट 0 से 65,535 −32,768 से 32,767
32 बिट 0 से 4,294,967,295 −2,147,483,648 से 2,147,483,647
64 बिट 0 से लगभग 1.8 × 10^19 लगभग −9.2 × 10^18 से 9.2 × 10^18

चिह्न वाली पंक्तियाँ याद रखने लायक़ हैं, क्योंकि 2,147,483,647 लगातार सामने आता है। यह 32-बिट चिह्नित पूर्णांक में समाने वाला सबसे बड़ा मान है, और आश्चर्यजनक रूप से बहुत सारे सॉफ़्टवेयर आज भी गिनतियाँ, पहचान-संख्याएँ और समय-मुहरें वहीं रखते हैं।

चिह्नित ओवरफ़्लो अलग क्यों है?

C और C++ में दोनों सिर्फ़ परिणाम में नहीं, दर्जे में भी अलग हैं। बिना चिह्न वाला ओवरफ़्लो लपेटने के रूप में परिभाषित है और प्रोग्राम उस पर भरोसा कर सकता है। चिह्नित ओवरफ़्लो अपरिभाषित व्यवहार है, यानी कंपाइलर मान सकता है कि वह कभी होता ही नहीं।

उस मान्यता के दाँत हैं। ओवरफ़्लो पकड़ने के लिए if (x + 1 < x) लिखी जाँच को ऑप्टिमाइज़र पूरी तरह हटा सकता है, इस तर्क पर कि चिह्नित ओवरफ़्लो हो ही नहीं सकता इसलिए शर्त हमेशा झूठी है। जाँच ग़ायब, बिल्ड मानक के अनुसार सही, और सुरक्षा जा चुकी।

भरोसेमंद रूप परिणाम के बजाय संकार्यों की जाँच करता है, या उन अंतर्निर्मित फलनों का उपयोग करता है जो बड़े कंपाइलर ओवरफ़्लो-जाँच के साथ देते हैं। Rust डिबग बिल्ड में जाँचता है और रिलीज़ में लपेटता है, और Python मनमानी परिशुद्धता पर जाकर सवाल ही टाल देता है।

असमान दायरा कहाँ जाल बनता है?

उस अकेले मान पर जिसका कोई धनात्मक जोड़ीदार नहीं। सबसे ऋणात्मक संख्या का ऋण लेने पर कुछ भी निरूपणीय नहीं निकल सकता, इसलिए वह ख़ुद ही निकलती है: 32 बिट में −2,147,483,648 का ऋण −2,147,483,648 ही है।

इससे यह मान्यता टूटती है कि निरपेक्ष मान कभी ऋणात्मक नहीं होता, और जाँचें ठीक इसी पर टिकी होती हैं। जो नेमका किसी अंतर का परिमाण लेकर ग़ैर-ऋणात्मक परिणाम की उम्मीद करती है, उसे एक इनपुट पर ऋणात्मक मिलता है, और फिर वह उसी से किसी सरणी में अनुक्रमणिका लगाती है।

भाग का किनारा भी वही है। न्यूनतम को −1 से भाग देना उसी कारण ओवरफ़्लो करता है, और कई प्रोसेसरों पर वह लपेटने के बजाय हार्डवेयर अपवाद उठाता है।

कौन-सी असली विफलताएँ यही थीं?

अच्छी तरह दर्ज मामले जानने लायक़ हैं क्योंकि सब रोकने में सस्ते और भुगतने में महँगे थे। एरियन 5 की उड़ान 501 तब नष्ट हुई जब 64-बिट फ़्लोटिंग मान को 16-बिट चिह्नित पूर्णांक में बदला गया जो उसे समा नहीं सकता था, उस कोड में जो एक धीमे रॉकेट से लिया गया था जहाँ वह मान कभी इतना बड़ा होता ही नहीं था।

बोइंग 787 के लिए एक उड़ानयोग्यता निर्देश में समय-समय पर बिजली बंद करने को कहा गया, क्योंकि 32-बिट चिह्नित पूर्णांक में सेकंड के सौवें हिस्सों का काउंटर 248 दिन बाद ओवरफ़्लो करता था और जनरेटर नियंत्रण इकाइयों को एक साथ सुरक्षित अवस्था में डाल देता था।

वर्ष 2038 की समस्या वही आकार है, बस तारीख़ सबको दिखती है: 1970 से सेकंडों की 32-बिट चिह्नित गिनती जनवरी 2038 में चुक जाती है। हल चौड़ा प्रकार है, और काम हर उस जगह को खोजना है जहाँ सँकरा रखा गया था।

भेजने से पहले पकड़ें कैसे?

मशीन से शिकायत करवाकर। GCC और Clang में बने सैनिटाइज़र चलते समय चिह्नित ओवरफ़्लो पकड़ते हैं, और परीक्षण-समुच्चय के लिए एक चालू करना आमतौर पर एक दिन का काम है और नतीजा स्थायी।

प्रकार सोच-समझकर चुनना दूसरा आधा है। जो गिनती दो अरब से ऊपर जा सकती है वह शुरू से 64-बिट प्रकार में रहनी चाहिए, और जिस पहचान-संख्या को दूसरे तंत्र सहेजेंगे और पढ़ेंगे उसके लिए भी यही।

जहाँ किसी मान को लपेटने के बजाय सीमा पर रुकना चाहिए — ऑडियो नमूने, पिक्सेल चैनल — वह व्यवहार साफ़-साफ़ लिखना पड़ता है, क्योंकि कोई भी पूर्णांक प्रकार उसे मुफ़्त नहीं देता।

लोग जो पूछते हैं

क्या प्रोसेसर को पता चलता है? हाँ। हार्डवेयर ओवरफ़्लो ध्वज लगाता है, और आपका प्रोग्राम उसे इसलिए नहीं देखता कि अधिकांश भाषाएँ हर क्रिया के बाद उसे जाँचती नहीं।

क्या लपेटना कभी चाहिए भी होता है? अक्सर। हैश, जाँच-योग और चक्रीय क्रम-संख्याएँ सब मापांक-गणित पर टिकी हैं, और इसीलिए बिना चिह्न वाला व्यवहार परिभाषित है।

क्या 64-बिट प्रकार समस्या ख़त्म कर देते हैं? काउंटरों और समय-मुहरों के लिए व्यवहार में हाँ। गुणनफलों और संचयों के लिए नहीं, क्योंकि दो बड़े मानों का गुणा 64 बिट को भी उतनी ही आसानी से पार कर जाता है।

और JavaScript? संख्याएँ फ़्लोटिंग हैं, इसलिए सामान्य गणित लपेटता नहीं; 2^53 से ऊपर वह परिशुद्धता खोता है, जो और भी चुपचाप होता है। बिट-संकार्य अपवाद हैं और पहले 32-बिट चिह्नित में बदलते हैं।

लपेटना वही गणित है जो कूटलेखन कहता है, बस उस मान पर जिसके किनारे तक पहुँचने की किसी को उम्मीद नहीं थी। टू-कॉम्प्लिमेंट कैलकुलेटर दिखाता है कि कोई मान हर चौड़ाई पर कैसा दिखता है और उसकी सीमाएँ कहाँ हैं, और संख्या-आधार कन्वर्टर यह देखने का तेज़ रास्ता है कि बिटों ने किया क्या।