1. ホーム
  2. c#

[解決済み】C#の符号付き浮動小数点のこの奇妙な動作を説明できる人はいますか?

2022-04-01 01:55:25

質問

以下はコメント付きの例です。

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行の System.ValueType : (参照元を踏み込みました)

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

(どちらのメソッドも [MethodImpl(MethodImplOptions.InternalCall)] )

全てのフィールドが8バイト幅の場合。 CanCompareBits が誤って真を返してしまい、結果として2つの異なる、しかし意味的には同一の値をビット単位で比較することになります。

少なくとも1つのフィールドが8バイト幅でない場合。 CanCompareBits はfalseを返し、コードはリフレクションを使ってフィールドをループして Equals をそれぞれの値に対して正しく処理します。 -0.0 と同じです。 0.0 .

のソースはこちらです。 CanCompareBits を 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