何时调用Double的==运算符?

这一切都始于一个人向我提出的一个技巧问题……(在书中提到 – 简而言之C#)这是它的要点。

Double a = Double.NaN; Console.WriteLine(a == a); // => false Console.WriteLine(a.Equals(a)); // => true 

以上似乎不对。 a应该总是==对自己(引用相等)并且两者都应该是一致的。

看起来像Double重载==运算符。 reflection器确认如下:

 [__DynamicallyInvokable] public static bool operator ==(double left, double right) { return (left == right); } 

奇怪的是看起来递归并且没有提到NaN特定的行为。 那为什么它会返回错误?

所以我添加了一些代码来区分

 var x = "abc"; var y = "xyz"; Console.WriteLine(x == y); // => false 

现在我明白了

  L_0001: ldc.r8 NaN L_000a: stloc.0 L_000b: ldloc.0 L_000c: ldloc.0 L_000d: ceq L_000f: call void [mscorlib]System.Console::WriteLine(bool) L_0014: nop L_0015: ldloca.sa L_0017: ldloc.0 L_0018: call instance bool [mscorlib]System.Double::Equals(float64) L_001d: call void [mscorlib]System.Console::WriteLine(bool) L_0022: nop L_0023: ldstr "abc" L_0028: stloc.1 L_0029: ldstr "xyz" L_002e: stloc.2 L_002f: ldloc.1 L_0030: ldloc.2 L_0031: call bool [mscorlib]System.String::op_Equality(string, string) L_0036: call void [mscorlib]System.Console::WriteLine(bool) 
  • 对于双精度数,==运算符调用转换为ceq IL操作码
  • 对于字符串,它转换为System.String :: op_Equality(string,string)。

果然ceq的文档指定它是特殊的浮点数和NaN。 这解释了观察结果。

问题:

  • 为什么在Double上定义op_Equality? (并且实现不会影响NaN特定行为)
  • 什么时候被调用?

reflection器的错误解释

您从Reflector看到的反编译实际上是Reflector中的一个错误。 reflection器需要能够反编译一个比较两个双精度的函数; 在这些函数中,你会发现ceq直接发送到代码中。 因此,Reflector将ceq指令解释为两个双精度之间的==,以帮助反编译一个比较两个双精度的函数。

默认情况下,值类型不带有==实现。 ( 用户定义的结构体是否inheritance了重载的==运算符? )但是,所有内置标量类型都有一个显式重载的运算符,编译器将其转换为适当的CIL。 重载还包含一个简单的基于ceq的比较,因此==运算符重载的动态/后期绑定/基于reflection的调用不会失败。


更多细节

对于预定义的值类型,如果操作数的值相等,则相等运算符(==)返回true,否则返回false。 对于除string之外的引用类型,如果其两个操作数引用同一对象,则==返回true。 对于字符串类型,==比较字符串的值。

http://msdn.microsoft.com/en-us/library/53k8ybth.aspx

你所说的暗示==使用引用类型语义来比较double 。 但是,由于double是值类型,因此它使用值语义。 这就是3 == 3为真的原因,即使它们是不同的堆栈对象。

您几乎可以将此编译器转换视为LINQ的Queryable对象如何包含带有代码的扩展方法,但编译器会将这些调用转换为表达式树,而不是传递给LINQ提供程序。 在这两种情况下,底层函数永远不会被调用。


Double的比较语义

Double的文档确实提到了ceq CIL指令的工作原理:

如果通过调用Equals方法测试两个Double.NaN值的相等性,则该方法返回true。 但是,如果使用等于运算符测试两个NaN值的相等性,则运算符返回false。 如果要确定Double的值是否不是数字(NaN),则可以选择调用IsNaN方法。

http://msdn.microsoft.com/en-us/library/ya2zha7s.aspx


原始编译器源

如果查看反编译的C#编译器源代码,您将找到以下代码来处理双重比较直接转换为ceq

 private void EmitBinaryCondOperator(BoundBinaryOperator binOp, bool sense) { int num; ConstantValue constantValue; bool flag = sense; BinaryOperatorKind kind = binOp.OperatorKind.OperatorWithLogical(); if (kind <= BinaryOperatorKind.GreaterThanOrEqual) { switch (kind) { ... case BinaryOperatorKind.Equal: goto Label_0127; ... } } ... Label_0127: constantValue = binOp.Left.ConstantValue; if (((constantValue != null) && constantValue.IsPrimitiveZeroOrNull) && !constantValue.IsFloating) { ... return; } constantValue = binOp.Right.ConstantValue; if (((constantValue != null) && constantValue.IsPrimitiveZeroOrNull) && !constantValue.IsFloating) { ... return; } this.EmitBinaryCondOperatorHelper(ILOpCode.Ceq, binOp.Left, binOp.Right, sense); return; } 

上面的代码来自Roslyn.Compilers.CSharp.CodeGen.CodeGenerator.EmitBinaryCondOperator(...) ,我添加了“...”,以使代码更具可读性。

在msdn中声明;

如果通过调用Equals方法测试两个Double.NaN值的相等性,则该方法返回true。 但是,如果使用等于运算符测试两个NaN值的相等性,则运算符返回false。 如果要确定Double的值是否不是数字(NaN),则可以选择调用IsNaN方法。

这样做是为了符合IEC 60559:1989,因为它声明两个NaN值不相等,因为它们不被视为数字,因此op_Equal定义符合这一标准;

根据IEC 60559:1989,两个具有NaN值的浮点数永远不会相等。但是,根据System.Object :: Equals方法的规范,需要重写此方法以提供值相等语义。 由于System.ValueType通过使用Reflection提供此function,因此Object.Equals的描述明确指出值类型应考虑覆盖默认的ValueType实现以获得性能提升。 事实上,通过查看System.ValueType :: Equals的源代码(SSCLI中的clr \ src \ BCL \ System \ ValueType.cs的第36行),甚至CLR Perf团队对System.ValueType的影响发表了评论。 ::等于不快。

参考: http : //blogs.msdn.com/b/shawnfa/archive/2004/07/19/187792.aspx

来自msdn: http : //msdn.microsoft.com/en-us/library/ya2zha7s.aspx

如果通过调用Equals方法测试两个Double.NaN值的相等性,则该方法返回true。 但是,如果使用等于运算符测试两个NaN值的相等性,则运算符返回false。 如果要确定Double的值是否不是数字(NaN),则可以选择调用IsNaN方法。