2010-06-21 12 views
7

मैंने सी # कंपाइलर द्वारा फ्लोट राउंडिंग/ट्रंकेशन के साथ एक दिलचस्प व्यवहार देखा है। अर्थात्, जब एक फ्लोट शाब्दिक गारंटीकृत प्रतिनिधित्व योग्य सीमा (7 दशमलव अंक) से परे है, तो ए) स्पष्ट रूप से फ्लोट (एक अर्थात् अनावश्यक ऑपरेशन) के लिए एक फ्लोट परिणाम कास्टिंग कर रहा है और बी) स्थानीय चर में इंटरमीडिएट गणना परिणामों को संग्रहीत करने से आउटपुट बदल जाता है। एक उदाहरण:फ्लोट वैरिएबल बनाम फ्लोट चर के साथ अजीब कंपाइलर व्यवहार

using System; 

class Program 
{ 
    static void Main() 
    { 
     float f = 2.0499999f; 
     var a = f * 100f; 
     var b = (int) (f * 100f); 
     var c = (int) (float) (f * 100f); 
     var d = (int) a; 
     var e = (int) (float) a; 
     Console.WriteLine(a); 
     Console.WriteLine(b); 
     Console.WriteLine(c); 
     Console.WriteLine(d); 
     Console.WriteLine(e); 
    } 
} 

उत्पादन होता है:

205 
204 
205 
205 
205 

अपने कंप्यूटर पर JITted डीबग बिल्ड में, ख गणना निम्न प्रकार है:

  var b = (int) (f * 100f); 
0000005a fld   dword ptr [ebp-3Ch] 
0000005d fmul  dword ptr ds:[035E1648h] 
00000063 fstp  qword ptr [ebp-5Ch] 
00000066 movsd  xmm0,mmword ptr [ebp-5Ch] 
0000006b cvttsd2si eax,xmm0 
0000006f mov   dword ptr [ebp-44h],eax 

जबकि घ

के रूप में गणना की जाती है
  var d = (int) a; 
00000096 fld   dword ptr [ebp-40h] 
00000099 fstp  qword ptr [ebp-5Ch] 
0000009c movsd  xmm0,mmword ptr [ebp-5Ch] 
000000a1 cvttsd2si eax,xmm0 
000000a5 mov   dword ptr [ebp-4Ch],eax 

अंत में, मेरी तलाश चालू: आउटपुट की दूसरी पंक्ति चौथी से अलग क्यों है? क्या वह अतिरिक्त एफएमयूएल इतना अंतर बनाता है? यह भी ध्यान रखें कि अगर फ्लोट एफ से अंतिम (पहले से ही प्रस्तुत नहीं किया जा सकता) अंक हटा दिया जाता है या यहां तक ​​कि कम हो जाता है, तो सब कुछ "जगह में पड़ता है"।

+0

मैं यहाँ इस सवाल का जवाब देने के देखा था, लेकिन इसे – Andrey

उत्तर

5

आपका प्रश्न पूछ क्यों इन दो परिणाम भिन्न हैं, करने के लिए सरल किया जा सकता:

float f = 2.0499999f; 
var a = f * 100f; 
var b = (int)(f * 100f); 
var d = (int)a; 
Console.WriteLine(b); 
Console.WriteLine(d); 

आप नेट परावर्तक में कोड को देखें, तो आप देख सकते हैं कि इसके बाद के संस्करण कोड वास्तव में संकलित किया गया है के रूप में अगर यह थे निम्नलिखित कोड:

float f = 2.05f; 
float a = f * 100f; 
int b = (int) (f * 100f); 
int d = (int) a; 
Console.WriteLine(b); 
Console.WriteLine(d); 

चल बिन्दु गणना हमेशा वास्तव में नहीं बनाया जा सकता। 2.05 * 100f का परिणाम 205 के बराबर नहीं है, लेकिन गोल त्रुटियों के कारण बस थोड़ा सा है। जब यह मध्यवर्ती परिणाम एक पूर्णांक में परिवर्तित हो जाता है तो छोटा कर दिया जाता है। जब एक फ्लोट के रूप में संग्रहीत किया जाता है तो यह निकटतम प्रतिनिधित्व करने योग्य रूप में गोल होता है। गोल करने के ये दो तरीके अलग-अलग परिणाम देते हैं।

Console.WriteLine((int) (2.0499999f * 100f)); 
Console.WriteLine((int)(float)(2.0499999f * 100f)); 

गणना संकलक में पूरी तरह से किया जाता है:


जब आप लिखते हैं मेरा यह उत्तर अपनी टिप्पणी के बारे में। उपर्युक्त कोड इसके बराबर है:

Console.WriteLine(204); 
Console.WriteLine(205); 
+0

नहीं मिल रहा है इसलिए आप कहते हैं कि (int) छंटनी द्वारा किया जाता है और (फ्लोट) का अर्थ गोलाकार होता है। अगर ऐसी बात है, तो क्यों उत्पादन Console.WriteLine ((int) (2.0499999f * 100F)) और Console.WriteLine के लिए अलग है ((int) (नाव) (2.0499999f * 100F))? – Alan

+0

@Alan, मेरा उत्तर जांचें। कारण यह है कि फ्लोट केवल 7 अंक रख सकता है। लॉग (2^23) = 6.9 – Andrey

+0

@Alan: जब आप हार्ड-कोडित स्थिरांक का उपयोग करते हैं तो गणना पूरी तरह से कंपाइलर में होती है और कंपाइलर के नियमों का उपयोग करती है, न कि .NET रनटाइम में। –

2

मार्क कंपाइलर के बारे में सही है। अब हम संकलक मूर्ख करते हैं:

float f = (Math.Sin(0.5) < 5) ? 2.0499999f : -1; 
    var a = f * 100f; 
    var b = (int) (f * 100f); 
    var c = (int) (float) (f * 100f); 
    var d = (int) a; 
    var e = (int) (float) a; 
    Console.WriteLine(a); 
    Console.WriteLine(b); 
    Console.WriteLine(c); 
    Console.WriteLine(d); 
    Console.WriteLine(e); 

पहली अभिव्यक्ति व्यर्थ है लेकिन अनुकूलन से संकलक से बचाता है। परिणाम है:

205 
204 
205 
204 
205 

ठीक है, मैं स्पष्टीकरण मिल गया।

2.0499999f फ्लोट के रूप में संग्रहीत नहीं किया जा सकता है, क्योंकि इसमें केवल 7 10-आधारित अंक हो सकते हैं। और यह शाब्दिक 8 अंक है, इसलिए संकलक इसे गोलाकार कर सकता है क्योंकि स्टोर नहीं कर सका। (एक चेतावनी आईएमओ देना चाहिए)

यदि आप 2.049999f में बदलते हैं तो परिणाम की उम्मीद की जाएगी।

+0

धन्यवाद एंड्री, मैंने कंपाइलर बनाम रनटाइम जानकारी के आधार पर मार्क का जवाब चुना, लेकिन आपका भी प्रासंगिक है। – Alan

4

एक टिप्पणी में आप से पूछा

इन नियमों अलग अलग हैं?

हां। या, बल्कि, नियम अलग-अलग व्यवहार की अनुमति देते हैं।

और हाँ अगर, मैं, यह जानते करने वाले या तो सी # भाषा संदर्भ दस्तावेज़ या MSDN से, या संकलक और रनटाइम

यह से गर्भित है के बीच यह सिर्फ एक सामयिक विसंगति है विनिर्देश। फ़्लोटिंग पॉइंट ऑपरेशंस में सटीक न्यूनतम स्तर का सटीक होना आवश्यक है, लेकिन यदि यह फिट दिखाई देता है तो संकलक या रनटाइम को और परिशुद्धता का उपयोग करने की अनुमति है। इससे बड़े, अवलोकन योग्य परिवर्तन हो सकते हैं जब आप छोटे बदलावों को बढ़ाते हुए संचालन करते हैं। गोलाकार, उदाहरण के लिए, एक बेहद बड़े बदलाव को एक बेहद बड़े रूप में बदल सकता है।

इस तथ्य को काफी अक्सर यहां पूछे जाने वाले प्रश्नों की ओर जाता है। इस स्थिति और अन्य स्थितियों है कि इसी तरह की विसंगतियों का उत्पादन कर सकते पर कुछ पृष्ठभूमि के लिए, निम्न देखें:

Why does this floating-point calculation give different results on different machines?

C# XNA Visual Studio: Difference between "release" and "debug" modes?

CLR JIT optimizations violates causality?

https://stackoverflow.com/questions/2494724

+1

एरिक, बहुत बहुत धन्यवाद। आपका आखिरी लिंक विशेष रूप से प्रबुद्ध था। मैंने प्रश्न पोस्ट करने से पहले वास्तव में इसी तरह के परिदृश्यों की खोज की, लेकिन स्पष्ट रूप से मेरा दायरा बहुत संकीर्ण था। – Alan

+0

@Alan: आपका स्वागत है! –

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