किसी सरणी के मध्य की गणना करते समय प्रारंभ (अंत - प्रारंभ) / 2 ओवर (प्रारंभ + अंत) / 2 क्यों पसंद करते हैं?


160

मैंने देखा है प्रोग्रामर सूत्र का उपयोग करते हैं

mid = start + (end - start) / 2

सरल सूत्र का उपयोग करने के बजाय

mid = (start + end) / 2

सरणी या सूची में मध्य तत्व खोजने के लिए।

वे पूर्व का उपयोग क्यों करते हैं?


51
जंगली अनुमान: (start + end)अतिप्रवाह हो सकता है, जबकि (end - start)नहीं।
कैडिलुक जूल

30
क्योंकि बाद काम नहीं करता है startऔर endसूचक हैं।
जूल


20
start + (end - start) / 2अर्थ अर्थ को भी वहन करता है: (end - start)लंबाई है, इसलिए यह कहता है start + half the length:।
njzk2

2
@ LưuV LnhPhúc: इस सवाल का सबसे अच्छा जवाब और सबसे अधिक वोट नहीं है? यदि हां, तो दूसरे प्रश्नों को शायद इस एक के रूप में बंद कर दिया जाना चाहिए। पदों की आयु अप्रासंगिक है।
निंग एन्स्ट्रॉसम

जवाबों:


218

इसके तीन कारण हैं।

सबसे पहले, start + (end - start) / 2काम करता है , भले ही आप पॉइंटर्स का उपयोग कर रहे हों, जब तक कि end - startअतिप्रवाह 1 नहीं होता है ।

int *start = ..., *end = ...;
int *mid = start + (end - start) / 2; // works as expected
int *mid = (start + end) / 2;         // type error, won't compile

सभी में से एक, start + (end - start) / 2अतिप्रवाह नहीं होगा startऔर endबड़ी सकारात्मक संख्याएं हैं। हस्ताक्षरित ऑपरेंड के साथ, अतिप्रवाह अपरिभाषित है:

int start = 0x7ffffffe, end = 0x7fffffff;
int mid = start + (end - start) / 2; // works as expected
int mid = (start + end) / 2;         // overflow... undefined

(ध्यान दें कि end - startअतिप्रवाह हो सकता है, लेकिन केवल अगर start < 0या end < 0।)

या अहस्ताक्षरित अंकगणित के साथ, अतिप्रवाह परिभाषित किया गया है लेकिन आपको गलत उत्तर देता है। हालांकि, अहस्ताक्षरित ऑपरेंड के लिए, start + (end - start) / 2कभी भी अतिप्रवाह नहीं होगा end >= start

unsigned start = 0xfffffffeu, end = 0xffffffffu;
unsigned mid = start + (end - start) / 2; // works as expected
unsigned mid = (start + end) / 2;         // mid = 0x7ffffffe

अंत में, आप अक्सर startतत्व की ओर चक्कर लगाना चाहते हैं ।

int start = -3, end = 0;
int mid = start + (end - start) / 2; // -2, closer to start
int mid = (start + end) / 2;         // -1, surprise!

फुटनोट

1 सी मानक के अनुसार, यदि सूचक घटाव का परिणाम एक के रूप में प्रतिनिधित्व करने योग्य नहीं है ptrdiff_t, तो व्यवहार अपरिभाषित है। हालांकि, व्यवहार में, इसके लिए charकम से कम आधे पूरे पते की जगह का उपयोग करके एक सरणी आवंटित करने की आवश्यकता होती है।


के परिणाम (end - start)में signed intमामले अपरिभाषित है जब यह overflows।
20

क्या आप साबित कर सकते हैं कि end-startअतिप्रवाह नहीं होगा? AFAIK यदि आप एक नकारात्मक लेते हैं startतो इसे अतिप्रवाह करना संभव होना चाहिए। यकीन है, जब आप औसत जानते हैं कि मानों की गणना कर रहे हैं >= 0...
बाकुरू

12
@ बकुरीउ: कुछ ऐसा साबित करना असंभव है जो सच न हो।
डिट्रीच ईप जूल

4
यह सी में विशेष रुचि है, क्योंकि सूचक घटाव (प्रति मानक) डिजाइन द्वारा टूट गया है। कार्यान्वयन को इतना बड़ा बनाने के लिए अनुमति दी जाती है जो कि end - startअपरिभाषित है, क्योंकि ऑब्जेक्ट आकार अहस्ताक्षरित होते हैं जबकि सूचक अंतर पर हस्ताक्षर किए जाते हैं। तो end - start"पॉइंटर्स का उपयोग करते हुए भी काम करता है", बशर्ते आप किसी तरह नीचे के सरणी का आकार भी रखें PTRDIFF_MAX। मानक के लिए उचित होने के लिए, यह अधिकांश आर्किटेक्चर पर बाधा नहीं है क्योंकि यह मेमोरी मैप का आधा आकार है।
स्टीव जेसोप

3
@ बकुरीउ: वैसे, पोस्ट पर एक "एडिट" बटन है जिसका उपयोग आप बदलावों का सुझाव देने के लिए कर सकते हैं (या उन्हें खुद कर सकते हैं) अगर आपको लगता है कि मैंने कुछ याद किया है, या कुछ अस्पष्ट है। मैं केवल मानव हूं, और इस पोस्ट को दो हजार से अधिक जोड़े नेत्रगोलक द्वारा देखा गया है। इस तरह की टिप्पणी, "आपको स्पष्ट करना चाहिए ..." वास्तव में मुझे गलत तरीके से परेशान करता है।
डिट्रिच एप्प

18

हम इस तथ्य को प्रदर्शित करने के लिए एक सरल उदाहरण ले सकते हैं। एक निश्चित बड़े सरणी में मान लीजिए , हम रेंज के मध्य बिंदु को खोजने की कोशिश कर रहे हैं [1000, INT_MAX]। अब, डेटा प्रकार स्टोर कर सकते हैं INT_MAXसबसे बड़ा मूल्य है int। यदि 1इसमें जोड़ दिया जाए तो भी अंतिम मान ऋणात्मक हो जाएगा।

इसके अलावा, start = 1000और end = INT_MAX

सूत्र का उपयोग: (start + end)/2,

मध्य बिंदु होगा

(1000 + INT_MAX)/2= -(INT_MAX+999)/2, जो ऋणात्मक है और यदि हम इस मान का उपयोग करके अनुक्रमित करने का प्रयास करते हैं तो विभाजन दोष दे सकते हैं

लेकिन, सूत्र का उपयोग करते हुए (start + (end-start)/2), हमें मिलता है:

(1000 + (INT_MAX-1000)/2)= (1000 + INT_MAX/2 - 500)= (INT_MAX/2 + 500) जो अति नहीं करेगा


1
यदि आप 1 को जोड़ते हैं INT_MAX, तो परिणाम नकारात्मक नहीं होगा, लेकिन अपरिभाषित होगा।
celtschk

@celtschk सैद्धांतिक रूप से, हाँ। व्यावहारिक रूप से यह रैप-अराउंड से जा रहा INT_MAXहोगा -INT_MAX। हालांकि उस पर भरोसा करना एक बुरी आदत है।
मस्त

17

दूसरों ने पहले ही जो कहा है, उसे जोड़ने के लिए, पहले वाला कम गणितीय रूप से दिमाग लगाने वालों को इसका अर्थ स्पष्ट करता है:

mid = start + (end - start) / 2

के रूप में पढ़ता है:

मध्य बराबर लंबाई के साथ आधा शुरू होता है।

जहाँ तक:

mid = (start + end) / 2

के रूप में पढ़ता है:

मिड स्टार्ट स्टार्ट एंड के आधे हिस्से के बराबर है

जो पहले की तरह स्पष्ट नहीं लगता, कम से कम जब उस तरह से व्यक्त किया जाता है।

जैसा कि कोस ने बताया कि यह भी पढ़ सकते हैं:

मध्य आरंभ और अंत के औसत के बराबर होता है

जो स्पष्ट है, लेकिन अभी भी नहीं है, कम से कम मेरी राय में, पहले की तरह स्पष्ट है।


3
मैं आपकी बात देख रहा हूं, लेकिन यह वास्तव में एक खिंचाव है। यदि आप "ई - एस" देखते हैं और "लंबाई" सोचते हैं, तो आप लगभग निश्चित रूप से "(एस + ई) / 2" और "औसत" या "मध्य" देखते हैं।
djechlin

2
@djechlin प्रोग्रामर गणित में खराब हैं। वे अपना काम करने में व्यस्त हैं। उनके पास गणित की कक्षाओं में भाग लेने का कोई समय नहीं है।
थोड़ा विदेशी

1

प्रारंभ + (अंत-प्रारंभ) / 2 संभव अतिप्रवाह से बच सकते हैं, उदाहरण के लिए प्रारंभ = 2 ^ 20 और अंत = 2 ^ 30

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.