राशि क्रम बदलने से अलग परिणाम क्यों मिलता है?


294

राशि क्रम बदलने से अलग परिणाम क्यों मिलता है?

23.53 + 5.88 + 17.64 = 47.05

23.53 + 17.64 + 5.88 = 47.050000000000004

जावा और जावास्क्रिप्ट दोनों एक ही परिणाम देते हैं।

मैं समझता हूं कि फ्लोटिंग पॉइंट नंबरों को द्विआधारी में जिस तरह से दर्शाया गया है, उसके कारण कुछ तर्कसंगत संख्याएं ( जैसे 1/3 - 0.333333 ... ) का सटीक प्रतिनिधित्व नहीं किया जा सकता है।

तत्वों के क्रम को बदलने से परिणाम प्रभावित क्यों होता है?


28
वास्तविक संख्याओं का योग सहयोगी और सराहनीय है। फ्लोटिंग-पॉइंट्स वास्तविक संख्याएँ नहीं हैं। वास्तव में आपने सिर्फ यह साबित किया है कि उनका संचालन सराहनीय नहीं है। यह दिखाना बहुत आसान है कि वे सहयोगी भी नहीं हैं (जैसे (2.0^53 + 1) - 1 == 2.0^53 - 1 != 2^53 == 2^53 + (1 - 1))। इसलिए, हाँ: सावधान रहें जब रकम और अन्य कार्यों के आदेश का चयन करें। कुछ भाषाएं "उच्च-सटीक" sums (उदाहरण python's math.fsum) करने के लिए बिल्ट-इन प्रदान करती हैं , इसलिए आप भोले योग एल्गोरिथ्म के बजाय इन कार्यों का उपयोग करने पर विचार कर सकते हैं।
बाकुरिु ६'१३

1
@Rerteig अंकगणितीय अभिव्यक्तियों के लिए भाषा के संचालन के क्रम की जांच करके निर्धारित किया जा सकता है और, जब तक कि मेमोरी में फ़्लोटिंग पॉइंट संख्याओं का उनका प्रतिनिधित्व अलग नहीं होता, परिणाम वही होंगे यदि उनके ऑपरेटर पूर्ववर्ती नियम समान हैं। नोट का एक और बिंदु: मुझे आश्चर्य है कि यह पता लगाने में बैंकिंग अनुप्रयोग विकसित करने वाले देवों को कितना समय लगा? उन अतिरिक्त 0000000000004 सेंट वास्तव में जोड़ते हैं!
क्रिस सरीफाइस

3
@ChrisCirefice: यदि आपके पास 0.00000004 सेंट हैं , तो आप इसे गलत कर रहे हैं। आपको वित्तीय गणनाओं के लिए कभी भी बाइनरी फ्लोटिंग पॉइंट प्रकार का उपयोग नहीं करना चाहिए ।
डैनियल प्रीडेन

2
@DanielPryden आह अफसोस, यह एक मजाक था ... सिर्फ इस विचार के चारों ओर फेंकने वाले कि इस प्रकार की समस्या को हल करने की आवश्यकता वाले लोगों के पास सबसे महत्वपूर्ण नौकरियों में से एक था जिसे आप जानते हैं, लोगों की मौद्रिक स्थिति और वह सब । मैं बहुत व्यंग्यात्मक था ...
क्रिस Cirefice

जवाबों:


276

हो सकता है कि यह सवाल बेवकूफी भरा हो, लेकिन तत्वों के क्रम को बदलने से परिणाम पर क्या असर पड़ता है?

यह उन बिंदुओं को बदल देगा जिन पर मूल्यों को गोल किया गया है, उनकी परिमाण के आधार पर। एक उदाहरण के रूप तरह बात यह है कि हम देख, चलो बहाना है कि द्विआधारी चल बिन्दु के बजाय, हम एक दशमलव चल 4 महत्वपूर्ण अंक है, जहां प्रत्येक इसके अलावा करने के लिए "अनंत" परिशुद्धता में किया जाता है और फिर गोल के साथ बिंदु प्रकार का उपयोग कर रहे थे की निकटतम प्रतिनिधित्व योग्य संख्या। यहाँ दो रकम हैं:

1/3 + 2/3 + 2/3 = (0.3333 + 0.6667) + 0.6667
                = 1.000 + 0.6667 (no rounding needed!)
                = 1.667 (where 1.6667 is rounded to 1.667)

2/3 + 2/3 + 1/3 = (0.6667 + 0.6667) + 0.3333
                = 1.333 + 0.3333 (where 1.3334 is rounded to 1.333)
                = 1.666 (where 1.6663 is rounded to 1.666)

समस्या न हो इसके लिए हमें गैर-पूर्णांक की भी आवश्यकता नहीं है:

10000 + 1 - 10000 = (10000 + 1) - 10000
                  = 10000 - 10000 (where 10001 is rounded to 10000)
                  = 0

10000 - 10000 + 1 = (10000 - 10000) + 1
                  = 0 + 1
                  = 1

यह संभवतः अधिक स्पष्ट रूप से दर्शाता है कि महत्वपूर्ण हिस्सा यह है कि हमारे पास सीमित संख्या में महत्वपूर्ण अंक हैं - दशमलव स्थानों की सीमित संख्या नहीं । यदि हम हमेशा दशमलव स्थानों की समान संख्या रख सकते हैं, तो कम से कम जोड़-घटाव के साथ, हम ठीक रहेंगे (इसलिए जब तक मान ओवरफ्लो नहीं करते)। समस्या यह है कि जब आप बड़ी संख्या में आते हैं, तो छोटी जानकारी खो जाती है - इस मामले में 10001 से 10000 तक गोल किया जा रहा है। (यह उस समस्या का एक उदाहरण है जिसे एरिक लिपर्ट ने अपने उत्तर में नोट किया है ।)

यह ध्यान रखना महत्वपूर्ण है कि दाहिने हाथ की ओर की पहली पंक्ति के मान सभी मामलों में समान हैं - इसलिए यद्यपि यह समझना महत्वपूर्ण है कि आपके दशमलव संख्या (23.53, 5.88, 17.64) को doubleमानों के रूप में बिल्कुल नहीं दर्शाया जाएगा , ऊपर दिखाई गई समस्याओं के कारण केवल एक समस्या।


10
May extend this later - out of time right now!इसके लिए बेसब्री से इंतज़ार कर रहे हैं @Jon
Prateek

3
जब मैं कहता हूं कि मैं एक उत्तर पर वापस जाऊंगा तो बाद में समुदाय मुझ पर थोड़ा कम दयालु है <कुछ प्रकार के प्रकाश हृदय इमोटिकॉन यहां दर्ज करें, यह दिखाने के लिए कि मैं मजाक कर रहा हूं और झटका नहीं> ... यह बाद में वापस मिलेगा।
ग्रैडी प्लेयर

2
@ZongZhengLi: हालांकि यह समझना निश्चित रूप से महत्वपूर्ण है कि, इस मामले में मूल कारण नहीं है। आप मान जो के साथ एक समान उदाहरण लिख सकता है कर रहे हैं वास्तव में बाइनरी में प्रतिनिधित्व किया, और एक ही प्रभाव देखते हैं। यहां समस्या एक ही समय में बड़े पैमाने पर सूचना और छोटे पैमाने पर जानकारी बनाए रख रही है।
जॉन स्कीट

1
@Buksy: 10000 तक का दौर - क्योंकि हम एक डेटाटाइप के साथ काम कर रहे हैं जो केवल 4 महत्वपूर्ण अंकों को संग्रहीत कर सकता है। (इसलिए x.xxx * 10 ^ n)
जॉन स्कीट

3
@meteors: नहीं, यह अतिप्रवाह का कारण नहीं बनता है - और आप गलत संख्याओं का उपयोग कर रहे हैं। यह १०००१ से १०००० तक गोल किया जा रहा है, १००० से १००० तक नहीं। यह स्पष्ट करने के लिए, ५४३ ९ ५ को ५४३२० में गोल किया जाएगा - क्योंकि इसमें केवल चार महत्वपूर्ण अंक हैं। "चार महत्वपूर्ण अंक" और "9999 का अधिकतम मूल्य" के बीच एक बड़ा अंतर है। जैसा कि मैंने पहले कहा था, आप मूल रूप से x.xxx * 10 ^ n का प्रतिनिधित्व कर रहे हैं, जहां 10000 के लिए, x.xxx 1.000 होगा, और n होगा 4. यह बिल्कुल पसंद है doubleऔर floatजहां बहुत बड़ी संख्या के लिए, लगातार प्रतिनिधित्व करने योग्य संख्या 1 से अधिक अलग हैं।
जॉन स्कीट

52

यहाँ बाइनरी में क्या हो रहा है। जैसा कि हम जानते हैं, कुछ फ्लोटिंग-पॉइंट वैल्यूज़ को बाइनरी में बिल्कुल नहीं दर्शाया जा सकता है, भले ही उन्हें दशमलव में बिल्कुल प्रतिनिधित्व किया जा सके। ये 3 नंबर उस तथ्य के उदाहरण हैं।

इस कार्यक्रम के साथ मैं प्रत्येक संख्या के हेक्साडेसिमल निरूपण और प्रत्येक जोड़ के परिणामों का उत्पादन करता हूं।

public class Main{
   public static void main(String args[]) {
      double x = 23.53;   // Inexact representation
      double y = 5.88;    // Inexact representation
      double z = 17.64;   // Inexact representation
      double s = 47.05;   // What math tells us the sum should be; still inexact

      printValueAndInHex(x);
      printValueAndInHex(y);
      printValueAndInHex(z);
      printValueAndInHex(s);

      System.out.println("--------");

      double t1 = x + y;
      printValueAndInHex(t1);
      t1 = t1 + z;
      printValueAndInHex(t1);

      System.out.println("--------");

      double t2 = x + z;
      printValueAndInHex(t2);
      t2 = t2 + y;
      printValueAndInHex(t2);
   }

   private static void printValueAndInHex(double d)
   {
      System.out.println(Long.toHexString(Double.doubleToLongBits(d)) + ": " + d);
   }
}

printValueAndInHexविधि सिर्फ एक हेक्स-प्रिंटर सहायक है।

आउटपुट इस प्रकार है:

403787ae147ae148: 23.53
4017851eb851eb85: 5.88
4031a3d70a3d70a4: 17.64
4047866666666666: 47.05
--------
403d68f5c28f5c29: 29.41
4047866666666666: 47.05
--------
404495c28f5c28f6: 41.17
4047866666666667: 47.050000000000004

पहले 4 नंबर दिए गए हैं x, y, z, और sके हेक्साडेसिमल अभ्यावेदन। IEEE फ़्लोटिंग पॉइंट प्रतिनिधित्व में, बिट्स 2-12 बाइनरी एक्सपोनेंट का प्रतिनिधित्व करता है , अर्थात, संख्या का पैमाना। (पहला बिट सांकेतिक बिट है, और मंटिसा के लिए शेष बिट्स ।) प्रस्तुत किया गया घातांक वास्तव में द्विआधारी संख्या मीन 1023 है।

पहले 4 नंबरों के एक्सपोजर निकाले जाते हैं:

    sign|exponent
403 => 0|100 0000 0011| => 1027 - 1023 = 4
401 => 0|100 0000 0001| => 1025 - 1023 = 2
403 => 0|100 0000 0011| => 1027 - 1023 = 4
404 => 0|100 0000 0100| => 1028 - 1023 = 5

परिवर्धन का पहला सेट

दूसरी संख्या ( y) छोटी परिमाण की है। प्राप्त करने के लिए इन दोनों संख्याओं को जोड़ते समय x + y, दूसरी संख्या के अंतिम 2 बिट्स ( 01) सीमा से बाहर स्थानांतरित कर दिए जाते हैं और गणना में शामिल नहीं होते हैं।

दूसरा इसके अलावा कहते हैं x + yऔर zऔर एक ही पैमाने के दो नंबर कहते हैं।

परिवर्धन का दूसरा सेट

यहाँ, x + zपहले होता है। वे समान पैमाने के होते हैं, लेकिन वे एक संख्या प्राप्त करते हैं जो बड़े पैमाने पर होती है:

404 => 0|100 0000 0100| => 1028 - 1023 = 5

दूसरा जोड़ जोड़ता है x + zऔर y, और अब संख्याओं को जोड़ने के लिए 3 बिट्स गिराए yगए हैं ( 101)। यहां, एक गोल ऊपर की ओर होना चाहिए, क्योंकि परिणाम अगला फ़्लोटिंग पॉइंट नंबर अप है: 4047866666666666परिवर्धन के पहले सेट के 4047866666666667लिए बनाम जोड़ के दूसरे सेट के लिए। यह त्रुटि कुल के प्रिंटआउट में दिखाने के लिए पर्याप्त महत्वपूर्ण है।

निष्कर्ष में, IEEE नंबर पर गणितीय कार्य करते समय सावधान रहें। कुछ निरूपण निरर्थक होते हैं, और तराजू अलग होने पर वे और भी अधिक निष्प्राण हो जाते हैं। यदि आप कर सकते हैं तो समान पैमाने की संख्याएँ जोड़ें और घटाएँ।


तराजू अलग होना महत्वपूर्ण हिस्सा है। आप (दशमलव में) उन सटीक मानों को लिख सकते हैं जिन्हें इनपुट के रूप में बाइनरी में दर्शाया जा रहा है, और अभी भी वही समस्या है।
जॉन स्कीट

@rgettman एक प्रोग्रामर के रूप में, मुझे =)आपके हेक्स-प्रिंटर सहायक के लिए आपका उत्तर बेहतर +1 पसंद है ... यह वास्तव में बहुत साफ है!
ADTC

44

जॉन का जवाब बेशक सही है। आपके मामले में त्रुटि उस त्रुटि से बड़ी नहीं है जिसे आप किसी भी सरल फ़्लोटिंग पॉइंट ऑपरेशन करने में संचित करेंगे। आपको एक परिदृश्य मिला है जहां एक मामले में आपको शून्य त्रुटि मिलती है और दूसरे में आपको एक छोटी सी त्रुटि मिलती है; यह वास्तव में एक दिलचस्प परिदृश्य नहीं है। एक अच्छा सवाल है: क्या ऐसे परिदृश्य हैं जहां गणना के क्रम को बदलने में एक छोटी त्रुटि से एक (अपेक्षाकृत) भारी त्रुटि हो जाती है? जवाब स्पष्ट रूप से हाँ है।

उदाहरण के लिए विचार करें:

x1 = (a - b) + (c - d) + (e - f) + (g - h);

बनाम

x2 = (a + c + e + g) - (b + d + f + h);

बनाम

x3 = a - b + c - d + e - f + g - h;

स्पष्ट रूप से सटीक अंकगणित में वे समान होंगे। यह ए, बी, सी, डी, ई, एफ, जी, एच जैसे मानों को खोजने की कोशिश करने के लिए मनोरंजक है कि एक्स 1 और एक्स 2 और एक्स 3 के मूल्य एक बड़ी मात्रा से भिन्न होते हैं। देखें कि क्या आप ऐसा कर सकते हैं!


आप एक बड़ी मात्रा को कैसे परिभाषित करते हैं? क्या हम 1000 वें क्रम पर बात कर रहे हैं? 100ths? 1 के ???
क्रंचर

3
@ क्रंचर: सटीक गणितीय परिणाम और एक्स 1 और एक्स 2 मूल्यों की गणना करें। सही और गणना किए गए परिणामों के बीच सटीक गणितीय अंतर को कॉल करें e1 और e2। त्रुटि आकार के बारे में सोचने के कई तरीके हैं। पहला है: क्या आप ऐसा परिदृश्य देख सकते हैं जिसमें या तो | e1 / e2 | या | e2 / e1 | बड़े हैं? जैसे, क्या आप एक दस गुना की गलती दूसरे की गलती कर सकते हैं? अधिक दिलचस्प एक है अगर आप सही उत्तर के आकार के एक महत्वपूर्ण अंश की त्रुटि बना सकते हैं।
एरिक लिपर्ट

1
मुझे लगता है कि वह रनटाइम के बारे में बात कर रहा है, लेकिन मुझे आश्चर्य है: यदि अभिव्यक्ति एक संकलन-समय (जैसे, कॉन्स्ट्रेप) अभिव्यक्ति थी, तो क्या कंपाइलर्स स्मार्ट को त्रुटि को कम करने के लिए पर्याप्त हैं?
केविन ह्सु

सामान्य तौर पर @kevinhsu, संकलक इतना स्मार्ट नहीं है। बेशक कंपाइलर सटीक अंकगणित में ऑपरेशन करने का चयन कर सकता है यदि वह ऐसा चुना है, लेकिन यह आमतौर पर नहीं होता है।
एरिक लिपर्ट

8
@frozenkoi: हाँ, त्रुटि बहुत आसानी से अनंत हो सकती है। उदाहरण के लिए, सी # पर विचार करें: double d = double.MaxValue; Console.WriteLine(d + d - d - d); Console.WriteLine(d - d + d - d);- आउटपुट इन्फिनिटी है फिर 0.
जॉन स्कीट

10

यह वास्तव में सिर्फ जावा और जावास्क्रिप्ट से बहुत अधिक कवर करता है, और यह संभवत: फ़्लोट्स या डबल्स का उपयोग करके किसी भी प्रोग्रामिंग भाषा को प्रभावित करेगा।

मेमोरी में, फ्लोटिंग पॉइंट IEEE 754 की तर्ज पर एक विशेष प्रारूप का उपयोग करते हैं (कनवर्टर जितना मैं कर सकता हूं, उससे बेहतर स्पष्टीकरण प्रदान करता है)।

वैसे भी, यहाँ फ्लोट कनवर्टर है।

http://www.h-schmidt.net/FloatConverter/

संचालन के आदेश के बारे में बात ऑपरेशन की "सुंदरता" है।

आपकी पहली पंक्ति पहले दो मानों से 29.41 निकलती है, जो हमें घातांक के रूप में 2 ^ 4 देती है।

आपकी दूसरी पंक्ति 41.17 निकलती है जो हमें घातांक के रूप में 2 ^ 5 देती है।

हम घातांक को बढ़ाकर एक महत्वपूर्ण आंकड़ा खो रहे हैं, जिसके परिणाम बदलने की संभावना है।

41.17 पर अंतिम दाईं ओर के अंतिम बिट पर टिक करने की कोशिश करें और आप देख सकते हैं कि घातांक के 1/2 ^ 23 के रूप में "निरर्थक" के रूप में कुछ इस अस्थायी बिंदु अंतर का कारण बनने के लिए पर्याप्त होगा।

संपादित करें: आपमें से जो महत्वपूर्ण आंकड़े याद करते हैं, यह उस श्रेणी में आता है। 10 ^ 4 + 4999 1 के महत्वपूर्ण आंकड़े के साथ 10 ^ 4 होने जा रहा है। इस मामले में, महत्वपूर्ण आंकड़ा बहुत छोटा है, लेकिन हम .00000000004 के साथ परिणाम देख सकते हैं।


9

फ्लोटिंग पॉइंट नंबरों को IEEE 754 प्रारूप का उपयोग करके दर्शाया गया है, जो मंटिसा (महत्व) के लिए विशिष्ट आकार प्रदान करता है। दुर्भाग्यवश यह आपको एक विशिष्ट संख्या में 'आंशिक निर्माण ब्लॉकों' के साथ खेलने के लिए देता है, और कुछ आंशिक मूल्यों का सटीक प्रतिनिधित्व नहीं किया जा सकता है।

आपके मामले में जो हो रहा है, वह यह है कि दूसरे मामले में, जोड़ के मूल्यांकन के आदेश के कारण जोड़ संभवत: कुछ सटीक मुद्दों में चल रहा है। मैंने मानों की गणना नहीं की है, लेकिन यह उदाहरण के लिए हो सकता है कि 23.53 + 17.64 का सही प्रतिनिधित्व नहीं किया जा सकता है, जबकि 23.53 + 17-16 कर सकते हैं।

दुर्भाग्य से यह एक ज्ञात समस्या है जिससे आपको बस निपटना है।


6

मेरा मानना ​​है कि इसे निकासी के आदेश के साथ करना है। जबकि योग एक गणित की दुनिया में स्वाभाविक रूप से एक ही है, बाइनरी दुनिया में ए + बी + सी = डी के बजाय, यह है

A + B = E
E + C = D(1)

तो वहाँ वह द्वितीयक कदम है जहाँ फ्लोटिंग पॉइंट नंबर बंद हो सकते हैं।

जब आप ऑर्डर बदलते हैं,

A + C = F
F + B = D(2)

4
मुझे लगता है कि यह उत्तर वास्तविक कारण से बचता है। "वहाँ वह द्वितीयक चरण है जहाँ फ़्लोटिंग पॉइंट नंबर बंद हो सकते हैं"। स्पष्ट रूप से, यह सच है, लेकिन हम जो व्याख्या करना चाहते हैं वह क्यों है
ज़ोंग
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.