这一切都始于某人向我提出的一个技巧问题..(在书中提到 - 简而言之C#)这是它的要点。
Double a = Double.NaN;
Console.WriteLine(a == a); // => false
Console.WriteLine(a.Equals(a)); // => true
以上看似不对。 a应始终为==自身(参考平等)&两者都应该是一致的。
似乎Double重载了==运算符。反射器确认如下:
[__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.s a
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操作码果然documentation for ceq
指定它是特殊的浮点数和NaN。这解释了观察结果。
问题:
答案 0 :(得分:11)
您从Reflector看到的反编译实际上是Reflector中的错误。反射器需要能够反编译一个比较两个双精度的函数;在这些函数中,您会在代码中找到ceq
。因此,Reflector将ceq
指令解释为两个双精度之间的==,以帮助反编译一个比较两个双精度的函数。
默认情况下,值类型不带有==实现。 (Don't user-defined structs inherit an overloaded == operator?)但是,所有内置标量类型都有一个显式重载的运算符,编译器将转换为适当的CIL。重载还包含一个简单的基于ceq
的比较,因此==运算符重载的动态/后期绑定/基于反射的调用不会失败。
对于预定义的值类型,等于运算符(==)返回true,如果 其操作数的值相等,否则为false。以供参考 除了string之外的类型,==如果它的两个操作数引用则返回true 同一个对象。对于字符串类型,==比较的值 字符串。
- http://msdn.microsoft.com/en-us/library/53k8ybth.aspx
你所说的暗示==使用引用类型语义来比较double
。但是,由于double
是值类型,因此它使用值语义。这就是3 == 3
为真的原因,即使它们是不同的堆栈对象。
您几乎可以将此编译器转换视为LINQ的Queryable对象如何包含带有代码的扩展方法,但编译器会将这些调用转换为表达式树,而不是传递给LINQ提供程序。在这两种情况下,底层函数永远不会被调用。
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(...)
,我添加了“...”,以使代码更具可读性。
答案 1 :(得分:2)
在msdn中声明;
如果通过调用Equals来测试两个Double.NaN值的相等性 方法,该方法返回true。但是,如果测试了两个NaN值 为了通过使用相等运算符进行相等,运算符返回 假。当您想确定Double的值是否不是时 一个数字(NaN),另一种方法是调用IsNaN方法。
这是符合IEC 60559:1989的要求,因为它声明两个NaN值不相等,因为它们不被视为数字,因此op_Equal
定义符合此标准;
根据IEC 60559:1989,两个浮点数的值为 NaN永远不会相等。但是,根据规范 System.Object :: Equals方法,希望覆盖此方法 提供价值平等语义。由于System.ValueType提供 这个功能通过使用Reflection,描述 Object.Equals特别指出值类型应该考虑 覆盖默认的ValueType实现以获得性能 增加。事实上从查看源头 System.ValueType :: Equals(clr \ src \ BCL \ System \ ValueType.cs的第36行 在SSCLI中,甚至还有来自CLR Perf团队的评论 System.ValueType :: Equals的效果不快。
请参阅:http://blogs.msdn.com/b/shawnfa/archive/2004/07/19/187792.aspx
答案 2 :(得分:1)
来自msdn:http://msdn.microsoft.com/en-us/library/ya2zha7s.aspx
如果通过调用Equals来测试两个Double.NaN值的相等性 方法,该方法返回true。但是,如果测试了两个NaN值 为了通过使用相等运算符进行相等,运算符返回 假。当您想确定Double的值是否不是时 一个数字(NaN),另一种方法是调用IsNaN方法。