सी # फ्लोट अभिव्यक्ति: परिणाम को कास्टिंग करते समय अजीब व्यवहार int


128

मेरे पास निम्नलिखित सरल कोड हैं:

int speed1 = (int)(6.2f * 10);
float tmp = 6.2f * 10;
int speed2 = (int)tmp;

speed1और speed2एक ही मूल्य होना चाहिए, लेकिन वास्तव में, मेरे पास है:

speed1 = 61
speed2 = 62

मुझे पता है कि मुझे शायद कास्टिंग के बजाय Math.Round का उपयोग करना चाहिए, लेकिन मैं यह समझना चाहूंगा कि मान अलग-अलग क्यों हैं।

मैंने जेनरेट किए गए बायटेकोड को देखा, लेकिन एक स्टोर और लोड को छोड़कर, ऑपकोड समान हैं।

मैंने जावा में भी समान कोड की कोशिश की, और मैं सही ढंग से 62 और 62 प्राप्त करता हूं।

क्या कोई इसे समझा सकता है?

संपादित करें: वास्तविक कोड में, यह सीधे 6.2f * 10 नहीं है, लेकिन एक फ़ंक्शन कॉल * एक स्थिर है। मेरे पास निम्नलिखित बायोटेक है:

के लिए speed1:

IL_01b3:  ldloc.s    V_8
IL_01b5:  callvirt   instance float32 myPackage.MyClass::getSpeed()
IL_01ba:  ldc.r4     10.
IL_01bf:  mul
IL_01c0:  conv.i4
IL_01c1:  stloc.s    V_9

के लिए speed2:

IL_01c3:  ldloc.s    V_8
IL_01c5:  callvirt   instance float32 myPackage.MyClass::getSpeed()
IL_01ca:  ldc.r4     10.
IL_01cf:  mul
IL_01d0:  stloc.s    V_10
IL_01d2:  ldloc.s    V_10
IL_01d4:  conv.i4
IL_01d5:  stloc.s    V_11

हम देख सकते हैं कि ऑपरेंड फ्लोट हैं और केवल यही अंतर है stloc/ldloc

वर्चुअल मशीन के लिए, मैंने मोनो / विन 7, मोनो / मैकओएस और .NET / विंडोज के साथ एक ही परिणाम की कोशिश की।


9
मेरा अनुमान है कि एक ऑपरेशन एकल-परिशुद्धता में किया गया, जबकि दूसरा डबल-परिशुद्धता में किया गया। उनमें से एक 62 से थोड़ा कम मान लौटाता है, इसलिए एक पूर्णांक को काटते समय 61 की उपज।
गाबे

2
ये विशिष्ट फ्लोट बिंदु सटीक मुद्दे हैं।
TJHeuvel

3
इसे .Net / WinXP, .Net / Win7, मोनो / उबंटू और मोनो / OSX पर आज़माकर, दोनों Windows संस्करणों के लिए आपके परिणाम देता है, लेकिन दोनों मोनो संस्करणों में speed1 और speed2 के लिए 62 है। थैंक्स @BoltClock
Eugen Rieck

6
मिस्टर लिपर्ट ... आप आसपास ??
vc 74

6
कंपाइलर की निरंतर अभिव्यक्ति मूल्यांकनकर्ता यहां कोई पुरस्कार नहीं जीत रहा है। स्पष्ट रूप से यह पहली अभिव्यक्ति में 6.2f को छोटा कर रहा है, इसका आधार 2 में सटीक प्रतिनिधित्व नहीं है इसलिए यह 6.199999 तक समाप्त होता है। लेकिन दूसरी अभिव्यक्ति में ऐसा नहीं होता है, शायद इसे किसी भी तरह डबल परिशुद्धता में रखने का प्रबंधन करके। यह अन्यथा पाठ्यक्रम के लिए बराबर है, फ्लोटिंग पॉइंट की संगति कभी समस्या नहीं है। यह तय नहीं होने जा रहा है, आप वर्कअराउंड जानते हैं।
हंस पसंत

जवाबों:


168

सबसे पहले, मैं मानता हूं कि आप जानते हैं कि 6.2f * 10फ्लोटिंग पॉइंट राउंडिंग के कारण 62 बिल्कुल नहीं है (यह वास्तव में मूल्य 61.99999809265137 है double) और कहा जाता है कि आपका सवाल केवल यही है कि दो समान रूप से समान संगणना के परिणामस्वरूप गलत मान क्यों होता है।

उत्तर यह है कि के मामले में (int)(6.2f * 10), आप doubleमूल्य 61.99999809265137 ले रहे हैं और इसे एक पूर्णांक में विभाजित कर रहे हैं , जो 61 पैदावार देता है।

के मामले में float f = 6.2f * 10, आप डबल मूल्य 61.99999809265137 ले रहे हैं और कर रहे हैं गोलाई निकटतम करने के लिए floatहै, जो है 62. तब आप उस काटना floatएक पूर्णांक के लिए, और परिणाम 62 है।

व्यायाम: संचालन के निम्नलिखित अनुक्रम के परिणामों की व्याख्या करें।

double d = 6.2f * 10;
int tmp2 = (int)d;
// evaluate tmp2

अद्यतन: जैसा कि टिप्पणियों में उल्लेख किया गया है, अभिव्यक्ति 6.2f * 10औपचारिक रूप से है floatक्योंकि दूसरे पैरामीटर में एक अंतर्निहित रूपांतरण है, floatजो अंतर्निहित रूपांतरण से बेहतर है double

वास्तविक मुद्दा यह है कि संकलक को एक मध्यवर्ती का उपयोग करने की अनुमति है (लेकिन आवश्यक नहीं) जो औपचारिक प्रकार (धारा 11.2.2) की तुलना में अधिक सटीक है । इसलिए आप अलग-अलग प्रणालियों पर अलग-अलग व्यवहार देखते हैं: अभिव्यक्ति में (int)(6.2f * 10), संकलक को 6.2f * 10परिवर्तित करने से पहले मूल्य को उच्च परिशुद्धता मध्यवर्ती रूप में रखने का विकल्प होता है int। यदि यह करता है, तो परिणाम 61 है। यदि यह नहीं होता है, तो परिणाम 62 है।

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


6
मुझे यकीन नहीं है कि यह वास्तव में सवाल का जवाब देता है। मान क्यों लिया जा (int)(6.2f * 10)रहा है double, जैसा fकि यह निर्दिष्ट करता है float? मुझे लगता है कि मुख्य बिंदु (अभी भी अनुत्तरित) यहाँ है।
ken2k

1
मुझे लगता है कि यह संकलक है जो ऐसा कर रहा है, क्योंकि यह फ्लोट शाब्दिक * इंट शाब्दिक है संकलक ने फैसला किया है कि यह सबसे अच्छा संख्यात्मक प्रकार का उपयोग करने के लिए स्वतंत्र है, और परिशुद्धता को बचाने के लिए यह डबल (शायद) के लिए चला गया है। (आईएल के समान होने पर भी समझाता है)
जॉर्ज डकेट

5
अच्छी बात। प्रकार 6.2f * 10वास्तव में है float, नहीं double। मुझे लगता है कि संकलक मध्यवर्ती को अनुकूलित कर रहा है, जैसा कि 11.1.6 के अंतिम पैराग्राफ द्वारा अनुमत है ।
रेमंड चेन

3
इसका मान समान है (मान 61.99999809265137 है)। अंतर वह पथ है जो मूल्य पूर्णांक बनने के रास्ते पर ले जाता है। एक मामले में, यह सीधे एक पूर्णांक में जाता है, और दूसरे में यह floatपहले रूपांतरण के माध्यम से जाता है ।
रेमंड चेन

38
रेमंड का जवाब यहाँ पूरी तरह से सही है। मैं ध्यान देता हूं कि C # संकलक और jit संकलक दोनों को किसी भी समय अधिक परिशुद्धता का उपयोग करने की अनुमति दी जाती है , और ऐसा असंगत रूप से करने के लिए । और वास्तव में, वे ऐसा ही करते हैं। यह सवाल स्टैकऑवरफ्लो पर दर्जनों बार आया है; हाल के उदाहरण के लिए stackoverflow.com/questions/8795550/… देखें ।
एरिक लिपिपर्ट

11

विवरण

फ्लोटिंग नंबर शायद ही कभी सटीक होते हैं। 6.2fकी तरह कुछ है 6.1999998...। यदि आप इसे इंट में डालते हैं तो यह इसे छोटा कर देगा और 61 में यह 10 परिणाम देगा।

जॉन स्कीट्स DoubleConverterक्लास देखें। इस वर्ग के साथ आप वास्तव में स्ट्रिंग के रूप में एक अस्थायी संख्या के मूल्य की कल्पना कर सकते हैं। Doubleऔर floatदोनों अस्थायी संख्याएँ हैं , दशमलव नहीं है (यह एक निश्चित बिंदु संख्या है)।

नमूना

DoubleConverter.ToExactString((6.2f * 10))
// output 61.9999980926513671875

अधिक जानकारी


5

आईएल देखें:

IL_0000:  ldc.i4.s    3D              // speed1 = 61
IL_0002:  stloc.0
IL_0003:  ldc.r4      00 00 78 42     // tmp = 62.0f
IL_0008:  stloc.1
IL_0009:  ldloc.1
IL_000A:  conv.i4
IL_000B:  stloc.2

कंपाइलर कंपाइल-टाइम कंटीन्यूअस एक्सप्रेशन्स को उनकी निरंतर वैल्यू तक कम कर देता है, और मुझे लगता है कि यह किसी बिंदु पर गलत सन्निकटन कर देता है जब यह कंटीन्यू को कंवर्ट करता है int। के मामले में speed2, यह रूपांतरण संकलक द्वारा नहीं, बल्कि CLR द्वारा किया जाता है, और वे विभिन्न नियम लागू करते हैं ...


1

मेरा अनुमान है कि है 6.2fनाव परिशुद्धता के साथ वास्तविक प्रतिनिधित्व है 6.1999999, जबकि 62fशायद करने के लिए कुछ इसी तरह है 62.00000001(int)कास्टिंग हमेशा दशमलव मान को छोटा कर देती है, इसीलिए आपको वह व्यवहार मिलता है।

संपादित करें : टिप्पणियों के अनुसार मैंने intकास्टिंग के व्यवहार को बहुत अधिक सटीक परिभाषा के रूप में व्यक्त किया है।


intदशमलव मान को ट्रंक करने के लिए कास्टिंग करना , यह गोल नहीं है।
जिम

@James D'Angelo: क्षमा करें, अंग्रेजी मेरी प्राथमिक भाषा नहीं है। सटीक शब्द नहीं जानते थे इसलिए मैंने व्यवहार को "सकारात्मक संख्याओं से निपटने के दौरान गोल चक्कर" के रूप में परिभाषित किया, जो मूल रूप से समान व्यवहार का वर्णन करता है। लेकिन हां, बिंदु लिया गया, ट्रंकट इसके लिए सटीक शब्द है।
InBetween

कोई बात नहीं, यह सिर्फ सहानुभूति है, लेकिन परेशानी हो सकती है अगर कोई सोचने लगता है float-> intगोलाई शामिल है। = डी
जिम डी'एंगेलो

1

मैंने इस कोड (Win7 / .NET 4.0 पर) को संकलित और डिसाइड किया। मुझे लगता है कि संकलक फ्लोटिंग स्थिर अभिव्यक्ति का दोगुना मूल्यांकन करता है।

int speed1 = (int)(6.2f * 10);
   mov         dword ptr [rbp+8],3Dh       //result is precalculated (61)

float tmp = 6.2f * 10;
   movss       xmm0,dword ptr [000004E8h]  //precalculated (float format, xmm0=0x42780000 (62.0))
   movss       dword ptr [rbp+0Ch],xmm0 

int speed2 = (int)tmp;
   cvttss2si   eax,dword ptr [rbp+0Ch]     //instrunction converts float to Int32 (eax=62)
   mov         dword ptr [rbp+10h],eax 

0

Singleकेवल 7 अंक और जब यह Int32संकलक के लिए कास्टिंग mantains सभी अस्थायी बिंदु अंक काटते हैं। रूपांतरण के दौरान एक या अधिक महत्वपूर्ण अंक खो सकते हैं।

Int32 speed0 = (Int32)(6.2f * 100000000); 

619999980 का परिणाम देता है (Int32) (6.2f * 10) 61 देता है।

जब दो सिंगल को गुणा किया जाता है, तो यह अलग होता है, उस स्थिति में कोई छोटा ऑपरेशन नहीं होता है, बल्कि केवल सन्निकटन होता है।

Http://msdn.microsoft.com/en-us/library/system.single.aspx देखें


-4

क्या कोई कारण है जिसे आप intपार्स करने के बजाय टाइप कर रहे हैं ?

int speed1 = (int)(6.2f * 10)

फिर पढ़ेंगे

int speed1 = Int.Parse((6.2f * 10).ToString()); 

अंतर शायद गोलाई के साथ करना है: यदि आप को कास्ट करते हैं doubleतो आपको संभवतः 61.78426 जैसा कुछ मिलेगा।

कृपया निम्नलिखित आउटपुट पर ध्यान दें

int speed1 = (int)(6.2f * 10);//61
double speed2 = (6.2f * 10);//61.9999980926514

यही कारण है कि आपको अलग-अलग मूल्य मिल रहे हैं!


1
Int.Parseपैरामीटर के रूप में एक स्ट्रिंग लेता है।
ken2k

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