সি # তে স্বাক্ষরিত ভাসা দিয়ে কেউ কি এই অদ্ভুত আচরণটি ব্যাখ্যা করতে পারেন?


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)মূল্যায়ন করার জন্যtruec.f.Equals(d.f)
জাস্টিন নিসনার

2
.কোয়ালের মতো নির্ভুল তুলনার সাথে ভাসমানগুলির তুলনা করবেন না। এটি কেবল একটি খারাপ ধারণা।
Thorsten79

6
@ থারস্টেন 79৯: এখানে এটি কীভাবে প্রাসঙ্গিক?
বেন এম

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

CanCompareBitsএসএসসিএলআইয়ের উত্স এখানে :

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

158
সিস্টেম.ওয়ালিউটাইপ এ পদক্ষেপ? বেশ সুন্দর ভাই।
পিয়ারটেন

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.
স্ল্যাक्स

1
.NET এখন ওপেন সোর্স সফ্টওয়্যার হিসাবে, এখানে ভ্যালুটাইপহেল্পার :: সিএনপিয়ারবিটস এর কোর সিএলআর বাস্তবায়নের একটি লিঙ্ক রয়েছে । আপনি পোস্ট করেছেন এমন রেফারেন্স উত্স থেকে বাস্তবায়নটি সামান্য পরিবর্তিত হওয়ায় আপনার উত্তর আপডেট করতে চাননি।
IInspectable

59

আমি উত্তরটি http://blogs.msdn.com/xiangfan/archive/2008/09/01/magic-behind-valuetype-equals.aspx এ পেয়েছি ।

মূল অংশটি হল উত্সের মন্তব্য CanCompareBits, যা স্টাইল তুলনাটি ValueType.Equalsব্যবহার করতে হবে তা নির্ধারণ করতে ব্যবহার করে memcmp:

ক্যানকম্পারেবিটসের মন্তব্য বলেছে "যদি ভ্যালুয়েটাইপটিতে পয়েন্টার না থাকে এবং শক্তভাবে প্যাক করা থাকে তবে সত্য প্রত্যাবর্তন করুন"। এবং ফাস্টএকুয়ালস চেক তুলনা দ্রুত করার জন্য "মেমক্যাম্প" ব্যবহার করে।

ওপি দ্বারা বর্ণিত সমস্যাটি লেখক ঠিক বলেছেন:

কল্পনা করুন আপনার এমন কাঠামো রয়েছে যাতে কেবল একটি ভাসা থাকে। একটিতে +0.0 থাকে এবং অন্যটিতে -0.0 থাকে তবে কী হবে? সেগুলি একই হওয়া উচিত তবে অন্তর্নিহিত বাইনারি উপস্থাপনা আলাদা। যদি আপনি সমান পদ্ধতিটিকে ওভাররাইড করে এমন অন্যান্য কাঠামো নীড় করে থাকেন তবে সেই অপ্টিমাইজেশনটি ব্যর্থ হবে।


আমি ভাবছি আচরণ যদি Equals(Object)জন্য double, floatএবং Decimal.net প্রথম ড্রাফ্ট সময় পরিবর্তিত; আমি মনে করি হবে এটি ভার্চুয়াল আছে বেশি গুরুত্বপূর্ণ X.Equals((Object)Y)বিনিময়ে কেবলমাত্র trueযখন Xএবং Yআলাদা করে চেনা যায়, চেয়ে যে পদ্ধতি অন্যান্য overloads (বিশেষ করে দেওয়া আচরণ মেলে যে, অন্তর্নিহিত জাভাস্ক্রিপ্টের এই মাতব্বরী কারণে ওভারলোড আছে Equalsপদ্ধতি এমনকি একটি সমানতা সম্পর্ক সংজ্ঞায়িত করেন না ! উদাহরণস্বরূপ 1.0f.Equals(1.0)ফলন মিথ্যা, তবে 1.0.Equals(1.0f)ফলন সত্য!) প্রকৃত সমস্যা আইএমএইচও কাঠামোগুলির সাথে যেভাবে তুলনা করা হয় তা নয় ...
সুপারক্যাট

1
... তবে সেই মানের ধরণগুলি Equalsসমতা ছাড়া অন্য কিছু বোঝাতে ওভাররাইড করে। ধরুন, উদাহরণস্বরূপ, কেউ এমন একটি পদ্ধতি লিখতে চান যা একটি অপরিবর্তনীয় অবজেক্ট গ্রহণ করে এবং, যদি এটি এখনও ক্যাশে না করা হয়, তবে এটি সম্পাদন ToStringকরে ফলাফলটিকে ক্যাশে করে; যদি এটি ক্যাশে করা থাকে তবে কেবল ক্যাশে স্ট্রিংটি ফিরিয়ে দিন। করণীয় অযৌক্তিক জিনিস নয়, তবে এটির সাথে খারাপভাবে ব্যর্থ হবে Decimalযেহেতু দুটি মান সমান তুলনা করতে পারে তবে বিভিন্ন স্ট্রিং দেয়।
সুপারক্যাট

52

ভিলক্সের অনুমানটি সঠিক। "CanCompareBits" যা করে তা হ'ল পরীক্ষার মানের ধরণটি মেমরিতে "দৃly়ভাবে প্যাক করা" আছে কিনা তা যাচাই করে। শক্তভাবে প্যাকযুক্ত কাঠামোর সাথে তুলনা করা হয় কেবল কাঠামোগুলি বাইনারি বিটগুলির সাথে তুলনা করে যা কাঠামোটি তৈরি করে; সমস্ত সদস্যের কাছে সমান কল করে একটি আলগাভাবে প্যাক করা কাঠামোর তুলনা করা হয়।

এটি স্লাকসের পর্যবেক্ষণকে ব্যাখ্যা করে যে এটি স্ট্রাক্টগুলি যে সমস্ত দ্বিগুণ হয় তা পুনরায় প্রতিস্থাপন করে; যেমন স্ট্রাক্ট সবসময় শক্তভাবে প্যাক করা হয়।

দুর্ভাগ্যক্রমে যেমন আমরা এখানে দেখেছি, এটি একটি অর্থগত পার্থক্যের পরিচয় দেয় কারণ ডাবলসের বিটওয়াইয়া তুলনা এবং ডাবলের সমান তুলনা বিভিন্ন ফলাফল দেয়।


3
তাহলে কেন এটি বাগ নয়? যদিও এমএস সর্বদা মান ধরণের ক্ষেত্রে সমানকে ওভাররাইড করার পরামর্শ দেয়।
আলেকজান্ডার এফিমভ

14
আমার থেকে হ্যাককে বিট করে। আমি সিএলআরের অভ্যন্তরীণ বিষয়ে বিশেষজ্ঞ নই।
এরিক লিপার্ট

4
... তুমি না? অবশ্যই আপনার # সি ইন্টার্নাল সম্পর্কিত জ্ঞান সিএলআর কীভাবে কাজ করে তা সম্পর্কে যথেষ্ট জ্ঞানের দিকে পরিচালিত করবে।
ক্যাপ্টেনকেসি

37
@ ক্যাপটেনসি: আমি সি # সংকলকের অভ্যন্তরীণ অধ্যয়ন করতে পাঁচ বছর সময় কাটিয়েছি এবং সম্ভবত সিএলআরের অভ্যন্তরীণ অধ্যয়ন করতে কয়েক ঘন্টা ব্যয় করেছি। মনে রাখবেন, আমি সিএলআরের ভোক্তা ; আমি এর সর্বজনীন পৃষ্ঠের অঞ্চলটি যুক্তিসঙ্গতভাবে ভালভাবে বুঝতে পারি, তবে এর অভ্যন্তরগুলি আমার কাছে একটি কালো বাক্স।
এরিক লিপার্ট

1
আমার ভুল, আমি ভেবেছিলাম সিএলআর এবং ভিবি / সি # সংকলক আরও
দৃly়তার সাথে

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
আকর্ষণীয়, আমরা সবাই মনোতে যাব?
উইনিডঅ্যান্সবার্স

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 টি বাইটের একাধিক যুক্ত করে।


একটি অপ্টিমাইজার নিয়মের মতো দেখে মনে হচ্ছে: কিছুটা তুলনা করলে তার সবকটি দ্বিগুণ হয়, অন্যথায় আলাদা ডাবল করুন quএককালাল কল
হেন্ক

আমি মনে করি না যে এটি এখানে পরীক্ষার মতো একই সমস্যা হিসাবে দেখা যাচ্ছে যা Bad.f এর জন্য ডিফল্ট মান 0 নয়, অন্য ক্ষেত্রে এটি আন্তঃ বনাম ডাবল ইস্যু বলে মনে হচ্ছে।
ড্রিস জোউক

6
@Driss জন্য ডিফল্ট মান double হল 0 । আপনি ভুল.
স্ল্যাक्स

10

এটি অবশ্যই কিছুটা তুলনা করে সম্পর্কিত হতে হবে, যেহেতু কেবল সিগন্যাল বিটের দ্বারা 0.0পৃথক হওয়া উচিত -0.0


5

…আপনি এ ব্যপারে কী ভাবছেন?

সর্বদা মানের ধরণের ক্ষেত্রে সমান এবং getHashCode ওভাররাইড করুন। এটি দ্রুত এবং সঠিক হবে।


সতর্কতা ব্যতীত যে সাম্যতা প্রাসঙ্গিক তখনই এটি প্রয়োজনীয়, আমি যা ভাবছিলাম ঠিক এটি এটিই। সর্বাধিক ভোট দেওয়া উত্তরগুলির মতো ডিফল্ট মান ধরণের সাম্য আচরণের সান্নিধ্য দেখতে ততই মজাদার, CA1815 থাকার একটি কারণ রয়েছে।
জো অ্যামেন্টা

দেরী উত্তরটির জন্য দুঃখিত আমার দৃষ্টিতে (অবশ্যই আমার দৃষ্টিতে অবশ্যই) সমতাটি সর্বদা মান ধরণের ক্ষেত্রে প্রাসঙ্গিক। সাধারণ ক্ষেত্রে ডিফল্ট সমতা প্রয়োগ কার্যকর হয় না। ( ) খুব বিশেষ কেস বাদে। খুব। খুব বিশেষ। আপনি যখন ঠিক জানেন আপনি কি করছেন এবং কেন করছেন।
ভাইচাস্লাভ ইভানোভ

আমি মনে করি আমরা সম্মত হই যে মান ধরণের জন্য সমতা চেককে ওভাররাইড করা কার্যত সর্বদা সম্ভব এবং খুব কম ব্যতিক্রম সহ অর্থবহ এবং সাধারণত এটি কঠোরভাবে আরও সঠিক করে তুলবে। "প্রাসঙ্গিক" শব্দটি দিয়ে আমি যে বক্তব্যটি প্রকাশ করার চেষ্টা করছিলাম সেটি হ'ল এমন কিছু মান ধরণের রয়েছে যার উদাহরণগুলির সাথে সাম্যের জন্য অন্যান্য উদাহরণগুলির সাথে কখনও তুলনা করা হবে না, সুতরাং ওভাররাইডিংয়ের ফলে ডেড কোডটি বজায় রাখা দরকার। সেগুলি (এবং আপনি যে অদ্ভুত বিশেষ কেসগুলির প্রতিশ্রুতি দেন) কেবলমাত্র সেগুলিই আমি এড়িয়ে যাব।
জো অ্যামেন্টা

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;
}

এটি এখনও মিথ্যা।

আমি টি দেখে মনে হচ্ছে এটা মিথ্যা যদি struct হয় শুধুমাত্র ডাবলস ঝুলিতে।


1

লাইন পরিবর্তন করার পরে এটি অবশ্যই শূন্য সম্পর্কিত হতে হবে

dd = -0.0

প্রতি:

dd = 0.0

সত্যের তুলনায় ফলাফল ...


বিপরীতে NaN এর পরিবর্তনের জন্য একে অপরের সাথে সমান তুলনা করতে পারে, যখন তারা আসলে একই বিট প্যাটার্নটি ব্যবহার করে use
হারোড
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.