Coder Perfect

Can somebody explain why signed floats in C# behave so strangely?

Problem

Here’s an example with annotations:

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

So, what are your thoughts on this?

Asked by Alexander Efimov

Solution #1

The bug can be found in the following two lines of the System file. ValueType: (I entered the source of information)

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

(Both methods are [MethodImpl(MethodImplOptions.InternalCall.MethodImplOptions.InternalCall.MethodImplOptions.InternalCall.MethodImplOpti

CanCompareBits incorrectly returns true when all of the fields are 8 bytes wide, resulting in a bitwise comparison of two separate, but semantically identical, values.

CanCompareBits returns false when at least one field is less than 8 bytes wide, and the code then uses reflection to cycle through the fields and call Equals for each value, appropriately treating -0.0 as equal to 0.0.

SSCLI’s source for CanCompareBits is as follows:

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

Answered by SLaks

Solution #2

http://blogs.msdn.com/xiangfan/archive/2008/09/01-magic-behind-valuetype-equals.aspx was where I found the answer. .

The source comment on CanCompareBits, whose ValueType, is the important component. To determine whether to employ memcmp-style comparison, Equals uses the following criteria:

The author then goes on to state the identical issue raised by the OP:

Answered by Ben M

Solution #3

Vilx’s hypothesis is correct. “CanCompareBits” checks to determine if the value type being compared is “tightly packed” in memory. A tightly packed structure is compared by comparing the binary bits that make up the structure; a loosely packed structure is compared by calling Equals on all members.

This explains SLaks’ observation that it repros with all-double structs, which are always tightly packed.

Unfortunately, as we’ve seen, this generates a semantic distinction, because bitwise and Equals comparisons of doubles provide different outcomes.

Answered by Eric Lippert

Solution #4

Half an answer:

ValueType.Equals(), according to Reflector, does something like this:

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

Unfortunately, both CanCompareBits() and FastEquals() (both static methods) are extern ([MethodImplOptions.InternalCall)]) and do not have a source.

Back to speculating on why one situation may be compared by bits but not the other (alignment concerns, perhaps?)

Answered by Vilx-

Solution #5

With Mono’s gmcs 2.4.2.3, it gives true for me.

Answered by Matthew Flaschen

Post is based on https://stackoverflow.com/questions/2508945/can-anyone-explain-this-strange-behavior-with-signed-floats-in-c