任何人都可以用C#中的签名浮点数解释这种奇怪的行为吗?

时间:2010-03-24 15:27:17

标签: c# .net floating-point

这是带注释的示例:

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

那么,你怎么看待这个?

11 个答案:

答案 0 :(得分:383)

该错误出现在以下两行System.ValueType中:(我进入了参考源)

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

(两种方法都是[MethodImpl(MethodImplOptions.InternalCall)]

当所有字段都是8字节宽时,CanCompareBits错误地返回true,从而对两个不同但语义相同的值进行逐位比较。

当至少一个字段不是8字节宽时,CanCompareBits返回false,代码继续使用反射循环遍历字段并为每个值调用Equals,这正确地处理{{ 1}}等于-0.0

以下是SSCLI 0.0的来源:

CanCompareBits

答案 1 :(得分:59)

我在http://blogs.msdn.com/xiangfan/archive/2008/09/01/magic-behind-valuetype-equals.aspx找到答案。

核心部分是CanCompareBits上的来源评论,ValueType.Equals用于确定是否使用memcmp - 样式比较:

  

CanCompareBits的评论说   “如果值类型没有,则返回true   包含指针并且紧密   打包“。和FastEqualsCheck一起使用   “memcmp”加快比较。

作者继续说明OP所描述的问题:

  

想象一下你有一个结构   只包含一个浮点数。会发生什么   如果一个包含+0.0,另一个包含+0.0   包含-0.0?他们应该是   相同,但底层二进制   代表性不同。如果你   嵌套覆盖其他结构   Equals方法,即优化   也会失败。

答案 2 :(得分:52)

Vilx的猜想是正确的。 “CanCompareBits”所做的是检查所讨论的值类型是否在内存中“紧密包装”。通过简单地比较构成结构的二进制位来比较紧密压缩的结构;通过在所有成员上调用Equals来比较松散堆积的结构。

这解释了SLaks的观察,即它的结构都是双重的结构;这些结构总是紧密包装。

不幸的是,正如我们在这里看到的那样,这引入了语义差异,因为双精度的按位比较和双精度的等于比较给出了不同的结果。

答案 3 :(得分:22)

半个回答:

Reflector告诉我们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()(两种静态方法)都是extern([MethodImpl(MethodImplOptions.InternalCall)])并且没有可用的源。

回到猜测为什么一个案例可以按位进行比较,而另一个案例不能(对齐问题可能?)

答案 4 :(得分:17)

使用Mono的gmcs 2.4.2.3, 对我来说是真实的。

答案 5 :(得分: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个字节的倍数时才会发生。

答案 6 :(得分:10)

它必须与逐位比较相关,因为0.0应仅与信号位-0.0不同。

答案 7 :(得分:5)

  

......你怎么看待这个?

始终在值类型上重写Equals和GetHashCode。这将是快速和正确的。

答案 8 :(得分:4)

这个10年前的错误只是一个更新:它可能在.NET中发布的has been fixed免责声明:我是此PR的作者)核心2.1.0。

blog post解释了这个错误以及我如何解决它。

答案 9 :(得分:2)

如果你像这样制作D2

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

这是真的。

如果你这样做

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

它仍然是假的。

如果结构只保存双打,那么

t似乎是假的。

答案 10 :(得分:1)

它必须与零相关,因为更改行

  

d.d = -0.0

为:

  

d.d = 0.0

导致比较成立......