2010-07-28 31 views
19

संभव डुप्लिकेट:
problem in comparing double values in C#यह लूप कभी खत्म क्यों नहीं होता?

मैं इसे कहीं पढ़ा है, लेकिन वास्तव में इस सवाल का जवाब मत भूलना इसलिए मैं यहाँ फिर से पूछो। इस पाश परवाह किए बिना अंत कभी नहीं लगता है कि आप किसी भी भाषा में यह कोड (मैं इसे सी # में परीक्षण, सी ++, जावा ...):

double d = 2.0; 
while(d != 0.0){ 
    d = d - 0.2; 
} 
+2

कभी भी फ़्लोटिंग मानों के साथ '==' का उपयोग न करें। शायद 'f> epsilon' जैसे कुछ का उपयोग करें। – pascal

+20

[यह कुछ दिन हो गया था, मुझे लगता है कि हम देय थे।] (Http://docs.sun.com/source/806-3568/ncg_goldberg.html) – GManNickG

+0

जावा में, मुझे लगता है कि "Strictfp" संशोधक है ऐसी परिस्थितियों के लिए –

उत्तर

32

चल बिन्दु गणना पूरी तरह से सटीक नहीं हैं। आपको एक प्रतिनिधित्व त्रुटि मिलेगी क्योंकि 0.2 में बाइनरी फ़्लोटिंग पॉइंट नंबर के रूप में सटीक प्रतिनिधित्व नहीं है, इसलिए मान शून्य 0 के बराबर नहीं बनता है। समस्या को देखने के लिए एक डिबग बयान जोड़ने का प्रयास करें: यह प्रकार decimal उपयोग करने के लिए है हल करने के लिए

double d = 2.0; 
while (d != 0.0) 
{ 
    Console.WriteLine(d); 
    d = d - 0.2; 
} 
 
2 
1,8 
1,6 
1,4 
1,2 
1 
0,8 
0,6 
0,4 
0,2 
2,77555756156289E-16 // Not exactly zero!! 
-0,2 
-0,4 

एक तरह से।

+1

और गोल त्रुटियां क्यों हैं? बाइनरी अंकों को 'डबल' स्टोर टाइप करें। चूंकि 0,2 लिखित बाइनरी आवधिक अंश है, इसलिए हमें एन बिट्स पर संख्या लिखने के लिए कुछ काटना होगा। – adf88

27

(एक बात के लिए आप भर एक ही चर का उपयोग नहीं कर रहे हैं, लेकिन मुझे लगता है कि यह मान लेंगे कि लिखने में कोई त्रुटि :)

0.2 वास्तव में 0.2 नहीं है। यह निकटतम double मान 0.2 है। जब आपने 2.0 से 10 बार घटाया है, तो आप बिल्कुल 0.0 के साथ समाप्त नहीं होंगे।

सी # में आप के बजाय decimal प्रकार का उपयोग करने को बदल सकते हैं, जो काम करेगा:

// Works 
decimal d = 2.0m; 
while (d != 0.0m) { 
    d = d - 0.2m; 
} 

यह काम करता है क्योंकि दशमलव प्रकार (0.2 ठीक तरह दशमलव मूल्यों का प्रतिनिधित्व सीमा के भीतर करता है, यह एक 128- है बिट प्रकार)। शामिल हर मूल्य सटीक रूप से प्रतिनिधित्व योग्य है, इसलिए यह काम करता है। क्या काम इस नहीं होगा होगा:

decimal d = 2.0m; 
while (d != 0.0m) { 
    d = d - 1m/3m; 
} 

यहाँ, "एक तिहाई" बिल्कुल प्रदर्शनीय है तो हम पहले की तरह ही समस्या के साथ खत्म।

सामान्य रूप से, फ़्लोटिंग पॉइंट नंबरों के बीच सटीक समानता तुलना करने का एक बुरा विचार है - आमतौर पर आप उन्हें एक निश्चित सहिष्णुता के साथ तुलना करते हैं।

मेरे पास floating binary point और floating decimal point पर एक सी #/.NET संदर्भ से आलेख हैं, जो चीजों को और विस्तार से समझाते हैं।

1

च uninitialised है;)

यदि आप मतलब है:

double f = 2.0; 

इस दोहरे चर पर गैर सटीक arthimetic का एक प्रभाव हो सकता है।

3

आप का उपयोग कर बेहतर होगा

while(f > 0.0) 

* संपादित करें: नीचे पास्कल की टिप्पणी देखें। लेकिन यदि आपको लूप को एक अभिन्न, निर्धारिती संख्या को चलाने की आवश्यकता है, बल्कि एक अभिन्न डेटा प्रकार का उपयोग करें।

+0

मुझे पता है कि मुझे इसका उपयोग करना चाहिए, लेकिन क्यों f! = 0.0 काम नहीं करता है? –

+1

यदि आप अंतिम 'f' 2.0e-16 है ... – pascal

1

यह फ़्लोटिंग पॉइंट की सटीकता के कारण है। उपयोग करें (डी> 0.0), या यदि आपको अवश्य,

while (Math.abs(d-0.0) > some_small_value){ 

} 
2

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

public class Looping { 

    public static void main(String[] args) { 

     double d = 2.0; 
     while(d != 0.0 && d >= 0.0) { 
      System.out.println(d); 
      d = d - 0.2; 
     } 

    } 

} 

आपका आउटपुट होना चाहिए कुछ की तरह:

2.0 
1.8 
1.6 
1.4000000000000001 
1.2000000000000002 
1.0000000000000002 
0.8000000000000003 
0.6000000000000003 
0.4000000000000003 
0.2000000000000003 
2.7755575615628914E-16 

और अब आप को देखने के लिए क्यों हालत d == 0 कभी नहीं होता है सक्षम होना चाहिए। । (पिछले संख्या में एक संख्या है कि बहुत 0 के करीब लेकिन काफी नहीं

चल बिन्दु weirdness का एक और उदाहरण के लिए है, इस प्रयास करें:

public class Squaring{ 

    public static void main(String[] args) { 

     double d = 0.1; 
     System.out.println(d*d); 

    } 

} 

क्योंकि वहाँ वास्तव में का कोई द्विआधारी प्रतिनिधित्व है 0.1, बराबरी यह परिणाम आप उम्मीद करेंगे (0.01), लेकिन वास्तव में कुछ 0.010000000000000002 तरह का उत्पादन नहीं करता!

+1

"पूरी तरह से सटीक नहीं" इसे ओवरस्टेट कर रहा है, तो IM12। सिर्फ इसलिए कि * हर * दशमलव मान बाइनरी फ़्लोटिंग पॉइंट में बिल्कुल प्रतिनिधित्व नहीं करता है, उदाहरण के लिए बिल्कुल 0.5 सटीक सटीक सटीक रूप से प्रतिनिधित्व करने के लिए 0.25 और 0.25 का सटीक रूप से प्रतिनिधित्व नहीं करता है। –

+0

संपादित, धन्यवाद जॉन - यह थोड़ा अधिक था। – Stephen

10

मैं एक सिंक्लेयर ZX-81 खरीदने याद उत्कृष्ट बुनियादी प्रोग्रामिंग मैनुअल के माध्यम से अपने रास्ते से काम कर रहा है, और लगभग लौटने दुकान में जब मैं अपने पहले फ्लोटिंग पॉइंट राउंडिंग त्रुटि में आया था।

मैंने कभी कल्पना नहीं की होगी कि लोगों को अभी भी 27.9 99 8 9 साल बाद इन समस्याओं का सामना करना पड़ेगा।

+2

जेडएक्स-स्पेक्ट्रम मैनुअल इस मुद्दे की एक विस्तृत व्याख्या के साथ आया था। मैं इसके बारे में कड़ी मेहनत कर रहा हूं (लगभग 10 या 11 साल की उम्र में) और "ओह, यह समझ में आता है" जा रहा है। यह एक बहुत अच्छा मैनुअल था ... –

+7

27.9 99 8 9 साल के लिए +1 :-) –

0

यह रोक नहीं है क्योंकि 0.2 मैं ठीक दो के पूरक में प्रतिनिधित्व नहीं तो अपने पाश कभी नहीं 0.0==0.0 परीक्षण निष्पादित करता

+0

दो के पूरक में फ्लोटिंग पॉइंट नंबरों की गोल करने वाली त्रुटियों के साथ कोई संबंध नहीं है। – Egon

0

के रूप में अन्य लोगों ने कहा, यह सिर्फ एक बुनियादी समस्या यह है कि आप जब फ्लोटिंग प्वाइंट कर मिलता है अंकगणित कोई आधार। यह बस होता है कि बेस -2 कंप्यूटर में सबसे आम है (क्योंकि यह कुशल हार्डवेयर कार्यान्वयन को स्वीकार करता है)।

सबसे अच्छा फिक्स, यदि संभव हो, तो अपने लूपिंग के लिए संख्या के किसी प्रकार के भाग्य का प्रतिनिधित्व करने के लिए स्विच करना है, जिससे फ़्लोटिंग-पॉइंट मान उस से प्राप्त किया जा सकता है। ठीक है, वह overblown लगता है! अपने विशिष्ट मामले के लिए, मैं लिखते हैं के रूप में:

int dTimes10 = 20; 
double d; 
while(dTimes10 != 0) { 
    dTimes10 -= 2; 
    d = dTimes10/10.0; 
} 

यहाँ, हम वास्तव में अंशों [20/10, 18/10, 16/10 के साथ काम कर रहे हैं, ..., 2/10, 0/10] जहां फ़्लोरिंग-पॉइंट में कनवर्ट करने से पहले, एक निश्चित संप्रदाय के साथ संख्या में पूर्णांक (यानी, सही पाने में आसान) के साथ पुनरावृत्ति किया जाता है। यदि आप इस तरह के काम करने के लिए अपने वास्तविक पुनरावृत्तियों को फिर से लिख सकते हैं, तो आपको बहुत सफलता मिलेगी (और वे वास्तव में आप जो भी कर रहे थे उससे कहीं अधिक महंगा नहीं हैं, जो शुद्धता पाने के लिए एक महान व्यापार-बंद है)।

यदि आप ऐसा नहीं कर सकते हैं, तो आपको अपनी तुलना के रूप में बराबर-भीतर-ईपीएसलॉन का उपयोग करने की आवश्यकता है। लगभग abs(d - target) < ε के साथ, जहां ε (ईपीएसलॉन) चयन कभी-कभी अजीब हो सकता है। असल में, ε का सही मान कारकों के समूह पर निर्भर करता है, लेकिन शायद यह 0 के रूप में सबसे अच्छा चुना गया है।001 उदाहरण के पुनरावृत्ति के लिए चरण मान के पैमाने को दिया गया है (यानी, यह चरण की परिमाण का आधा प्रतिशत है, इसलिए इसके भीतर कुछ भी जानकारीपूर्ण के बजाय त्रुटि होगी)।

संबंधित मुद्दे