Can anyone explain this strange behavior with signed floats in C#?

.net, c#, floating-point

Solution

The bug is in the following two lines of `System.ValueType`: (I stepped into the reference source)

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

(Both methods are `[MethodImpl(MethodImplOptions.InternalCall)]`)

When all of the fields are 8 bytes wide, `CanCompareBits` mistakenly returns true, resulting in a bitwise comparison of two different, but semantically identical, values.

When at least one field is not 8 bytes wide, `CanCompareBits` returns false, and the code proceeds to use reflection to loop over the fields and call `Equals` for each value, which correctly treats `-0.0` as equal to `0.0`.

Here is the source for `CanCompareBits` from SSCLI:

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

Problem

Here is the example with comments: ``` 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 do you think about this?

Original source