क्या कोई C # में हस्ताक्षरित फ़्लोट्स के साथ इस अजीब व्यवहार की व्याख्या कर सकता है?


247

यहाँ टिप्पणियों के साथ उदाहरण दिया गया है:

class Program
{
    // first version of structure
    public struct D1
    {
        public double d;
        public int f;
    }

    // during some changes in code then we got D2 from D1
    // Field f type became double while it was int before
    public struct D2 
    {
        public double d;
        public double f;
    }

    static void Main(string[] args)
    {
        // Scenario with the first version
        D1 a = new D1();
        D1 b = new D1();
        a.f = b.f = 1;
        a.d = 0.0;
        b.d = -0.0;
        bool r1 = a.Equals(b); // gives true, all is ok

        // The same scenario with the new one
        D2 c = new D2();
        D2 d = new D2();
        c.f = d.f = 1;
        c.d = 0.0;
        d.d = -0.0;
        bool r2 = c.Equals(d); // false! this is not the expected result        
    }
}

तो आप इस बारे मे क्या सोचते हैं?


2
चीजों को अजनबी c.d.Equals(d.d)के trueरूप में मूल्यांकन करने के लिएc.f.Equals(d.f)
जस्टिन निस्नेर

2
फ्लोट की तुलना सटीक तुलना के साथ न करें। यह केवल एक बुरा विचार है।
Thorsten79

6
@ Thorsten79: यह यहाँ कैसे प्रासंगिक है?
बेन एम

2
यह सबसे अजीब है। च के लिए एक डबल के बजाय एक लंबे समय का उपयोग करना समान व्यवहार का परिचय देता है। और एक और छोटे क्षेत्र को जोड़ने से यह फिर से सही हो जाता है ...
जेन्स

1
अजीब - यह केवल तब होता है जब दोनों एक ही प्रकार के होते हैं (फ्लोट या डबल)। फ्लोट (या दशमलव) के लिए एक बदलें और डी 2 डी 1 के समान काम करता है।
tvanfosson

जवाबों:


387

बग निम्नलिखित दो पंक्तियों में है System.ValueType: (मैंने संदर्भ स्रोत में कदम रखा)

if (CanCompareBits(this)) 
    return FastEqualsCheck(thisObj, obj);

(दोनों हैं [MethodImpl(MethodImplOptions.InternalCall)])

जब सभी फ़ील्ड 8 बाइट्स चौड़ी होती हैं, तो CanCompareBitsगलती से सही हो जाता है, जिसके परिणामस्वरूप दो अलग-अलग, लेकिन शब्दार्थ समान, मानों की एक बिटवाइज़ तुलना होती है।

जब कम से कम एक फ़ील्ड 8 बाइट्स चौड़ी नहीं होती है, तो CanCompareBitsवह झूठी हो जाती है, और कोड फ़ील्ड पर लूप के लिए प्रतिबिंब का उपयोग करने के लिए आगे बढ़ता है और Equalsप्रत्येक मूल्य के लिए कॉल करता है, जो सही तरीके से -0.0बराबर मानता है 0.0

यहाँ CanCompareBitsSSCLI के लिए स्रोत है :

FCIMPL1(FC_BOOL_RET, ValueTypeHelper::CanCompareBits, Object* obj)
{
    WRAPPER_CONTRACT;
    STATIC_CONTRACT_SO_TOLERANT;

    _ASSERTE(obj != NULL);
    MethodTable* mt = obj->GetMethodTable();
    FC_RETURN_BOOL(!mt->ContainsPointers() && !mt->IsNotTightlyPacked());
}
FCIMPLEND

159
System.ValueType में कदम रखना? यह बहुत कट्टर भाई है।
पियरटेन

2
आप यह नहीं समझाते कि "8 बाइट्स वाइड" का महत्व क्या है। क्या सभी 4-बाइट फ़ील्ड के साथ एक ही परिणाम नहीं होगा? मुझे लगता है कि एक एकल 4-बाइट फ़ील्ड और 8-बाइट फ़ील्ड्स केवल ट्रिगर करता है IsNotTightlyPacked
गाबे

1
@ गैबे मैंने पहले लिखा था किThe bug also happens with floats, but only happens if the fields in the struct add up to a multiple of 8 bytes.
SLaks

1
.NET अब ओपन सोर्स सॉफ्टवेयर होने के साथ, यहां ValueTypeHelper :: CanCompareBits के कोर सीएलआर कार्यान्वयन का एक लिंक है । लागू होने के बाद से आप अपने उत्तर को अपडेट नहीं करना चाहते हैं, जो आपके द्वारा पोस्ट किए गए संदर्भ स्रोत से थोड़ा बदला गया है।
IInspectable

59

मुझे इसका जवाब http://blogs.msdn.com/xiangfan/archive/2008/09/01/magic-behind-valuetype-equals.aspx पर मिला ।

मुख्य अंश स्रोत पर टिप्पणी है CanCompareBits, जो ValueType.Equalsयह निर्धारित करने के लिए उपयोग करता है कि क्या memcmp-स्टाइल तुलना का उपयोग करना है :

CanCompareBits की टिप्पणी यह ​​कहती है कि "सही है अगर वैल्यूएटाइप में सूचक नहीं है और कसकर पैक किया गया है"। और FastEqualsCheck तुलना को तेज करने के लिए "memcmp" का उपयोग करता है।

लेखक ओपी द्वारा बताई गई समस्या के बारे में बताता है:

कल्पना कीजिए कि आपके पास एक संरचना है जिसमें केवल एक फ्लोट है। यदि किसी में +0.0, और दूसरे में -0.0 हो तो क्या होगा? वे समान होना चाहिए, लेकिन अंतर्निहित बाइनरी प्रतिनिधित्व अलग हैं। यदि आप समान संरचना को ओवरराइड करते हैं, तो यह अनुकूलन विफल हो जाएगा।


मुझे आश्चर्य है कि के व्यवहार करता है, तो Equals(Object)के लिए double, floatऔर Decimal.net की जल्दी ड्राफ्ट के दौरान बदल; मुझे लगता है कि होता है कि यह आभासी है करने के लिए अधिक महत्वपूर्ण है X.Equals((Object)Y)केवल वापसी trueजब Xऔर Yअप्रभेद्य हैं, की तुलना में है कि विधि अन्य भार के (विशेष रूप से दिए गए के व्यवहार से मेल खाते हैं कि, अंतर्निहित प्रकार बलात्कार की वजह से, अतिभारित है करने के लिए Equalsतरीकों यहां तक कि एक तुल्यता संबंध परिभाषित नहीं करते ; उदा, 1.0f.Equals(1.0)झूठी उपज देता है, लेकिन 1.0.Equals(1.0f)सही पैदावार देता है!) वास्तविक समस्या IMHO संरचनाओं की तुलना करने के तरीके से नहीं है ...
Supercat

1
... लेकिन इस तरह के साथ कि वे मूल्य प्रकार Equalsसमतुल्यता के अलावा कुछ और मतलब है। मान लीजिए, उदाहरण के लिए, कोई ऐसी विधि लिखना चाहता है जो एक अपरिवर्तनीय वस्तु लेती है और, यदि इसे अभी तक कैश नहीं किया गया है, तो इस ToStringपर कार्य करता है और परिणाम को कैश करता है; यदि इसे कैश किया गया है, तो कैश्ड स्ट्रिंग लौटाएं। ऐसा करने के लिए कोई अनुचित बात नहीं है, लेकिन यह बुरी तरह Decimalसे विफल हो जाएगा क्योंकि दो मूल्यों की तुलना समान हो सकती है लेकिन अलग-अलग तारों का उत्पादन कर सकती है।
सुपरैट

52

विल्क्स का अनुमान सही है। "CanCompareBits" क्या करता है यह देखने के लिए जांचता है कि क्या स्मृति में प्रश्न प्रकार "कसकर पैक" है। कसकर भरी हुई संरचना की तुलना संरचना को बनाने वाले द्विआधारी बिट्स की तुलना करके की जाती है; सभी सदस्यों पर समान कॉल करके तुलनात्मक रूप से पैक्ड संरचना की तुलना की जाती है।

यह एसएलएसी के अवलोकन की व्याख्या करता है कि यह उन संरचनाओं के साथ है जो सभी युगल हैं; ऐसी संरचनाएं हमेशा कसकर पैक की जाती हैं।

दुर्भाग्य से जैसा कि हमने यहां देखा है, यह एक अर्थगत अंतर का परिचय देता है क्योंकि युगल की तुलना में बिटवाइस और युगल की तुलना में भिन्न परिणाम मिलते हैं।


3
तो फिर यह बग क्यों नहीं है? भले ही एमएस हमेशा मूल्य प्रकारों पर बराबरी को ओवरराइड करने की सिफारिश करता है।
अलेक्जेंडर एफिमोव

14
मेरे सामने से बिल्ली को पीटता है। मैं सीएलआर के आंतरिक मामलों का विशेषज्ञ नहीं हूं।
एरिक लिपर्ट

4
... तुम नहीं हो? निश्चित रूप से सी # इंटर्न के आपके ज्ञान से सीएलआर कैसे काम करता है, इस पर काफी ज्ञान होगा।
कैप्टनसी

37
@CaptainCasey: मैंने C # कंपाइलर के इंटर्नल का अध्ययन करते हुए पांच साल बिताए हैं और संभवत: कुल कुछ घंटों में CLR के इनरल्स का अध्ययन किया है। याद रखें, मैं सीएलआर का उपभोक्ता हूं ; मैं इसके सार्वजनिक सतह क्षेत्र को यथोचित रूप से अच्छी तरह से समझता हूं, लेकिन इसके आंतरिक हिस्से मेरे लिए एक ब्लैक बॉक्स हैं।
एरिक लिपर्ट

1
मेरी गलती, मुझे लगा कि सीएलआर और वीबी / सी # कंपाइलर अधिक कसकर युग्मित थे ... इसलिए सी # / वीबी -> सीआईएल -> सीएलआर
कैप्टनसी २५'१०

22

आधा उत्तर:

रिफ्लेक्टर हमें बताता है कि ValueType.Equals()ऐसा कुछ करता है:

if (CanCompareBits(this))
    return FastEqualsCheck(this, obj);
else
    // Use reflection to step through each member and call .Equals() on each one.

दुर्भाग्य से CanCompareBits()और FastEquals()(दोनों स्थिर तरीके) बाहरी हैं ( [MethodImpl(MethodImplOptions.InternalCall)]) और कोई स्रोत उपलब्ध नहीं है।

यह अनुमान लगाने के लिए कि क्यों एक मामले की तुलना बिट्स से की जा सकती है, और दूसरा नहीं कर सकता (संरेखण मुद्दे शायद?)


17

यह है मोनो gmcs 2.4.2.3 के साथ मेरे लिए सच देते हैं,।


5
हां, मैंने इसे मोनो में भी आजमाया है, और यह मुझे सच भी देता है। ऐसा लगता है कि एमएस कुछ जादू अंदर करता है :)
अलेक्जेंडर एफिमोव

3
दिलचस्प है, हम सभी मोनो के लिए जहाज?
WeNeedAnswers

14

सरल परीक्षण मामला:

Console.WriteLine("Good: " + new Good().Equals(new Good { d = -.0 }));
Console.WriteLine("Bad: " + new Bad().Equals(new Bad { d = -.0 }));

public struct Good {
    public double d;
    public int f;
}

public struct Bad {
    public double d;
}

संपादित करें : बग भी फ़्लोट्स के साथ होता है, लेकिन केवल तब होता है जब संरचना में फ़ील्ड 8 बाइट्स के कई तक जोड़ते हैं।


एक ऑप्टिमाइज़र नियम की तरह दिखता है: यदि इसके सभी डबल्स की तुलना में थोड़ा-सा करते हैं, तो अलग से डबल करते हैं। कुछ कॉल
हेंक

मुझे नहीं लगता कि यह वही परीक्षण मामला है जैसा कि यहां प्रस्तुत मुद्दा लगता है कि Bad.f के लिए डिफ़ॉल्ट मान 0 नहीं है, जबकि अन्य मामला Int बनाम Double मुद्दा प्रतीत होता है।
द ड्रिस ज़ौक

6
@Driss: के लिए डिफ़ॉल्ट मान double है 0 । तुम गलत हो।
एसएलके 15

10

यह बिट तुलना से थोड़ा संबंधित होना चाहिए, क्योंकि केवल सिग्नल बिट 0.0से अलग होना चाहिए -0.0


5

…आप इस बारे में क्या सोचते हैं?

हमेशा वैल्यू टाइप पर इक्वल और गेटहैशकोड को ओवरराइड करें। यह तेज और सही होगा।


एक चेतावनी के अलावा कि यह केवल तभी आवश्यक है जब समानता प्रासंगिक हो, यह वही है जो मैं सोच रहा था। जितना मज़ेदार यह डिफ़ॉल्ट मान प्रकार समानता व्यवहार के quirks को देखने के लिए है जैसा कि उच्चतम मतदान जवाब करते हैं, वहाँ एक कारण है कि CA1815 मौजूद है।
जो Amenta

@JoeAmenta देर से जवाब के लिए क्षमा करें। मेरे विचार में (सिर्फ मेरे विचार में), मूल्य प्रकारों के लिए समानता हमेशा ( ) प्रासंगिक है। सामान्य मामलों में डिफ़ॉल्ट समानता कार्यान्वयन स्वीकार्य नहीं है। ( ) बहुत विशेष मामलों को छोड़कर। बहुत। बहुत खास। जब आप वास्तव में जानते हैं कि आप क्या कर रहे हैं और क्यों कर रहे हैं।
वायाचेस्लाव इवानोव

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

4

इस 10 साल पुराने बग के लिए बस एक अपडेट: यह तय किया गया है ( अस्वीकरण : मैं इस पीआर का लेखक हूं) .NET कोर में। जो शायद .NET कोर 2.1.0 में जारी किया जाएगा।

ब्लॉग पोस्ट बग के बारे में बताया और मैं इसे कैसे तय की।


2

अगर आप इस तरह से डी 2 बनाते हैं

public struct D2
{
    public double d;
    public double f;
    public string s;
}

यह सच है।

अगर आप इसे इस तरह बनाते हैं

public struct D2
{
    public double d;
    public double f;
    public double u;
}

यह अभी भी झूठा है।

मुझे लगता है कि यह गलत है अगर संरचना केवल डबल्स रखती है।


1

लाइन बदलने के बाद से यह शून्य से संबंधित होना चाहिए

dd = -0.0

सेवा:

dd = 0.0

तुलना में परिणाम सच है ...


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