Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When is Double's == operator invoked?

Tags:

It all started with a trick question that someone posed to me.. (It's mentioned in the book - C# in a nutshell) Here's the gist of it.

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

The above doesn't seem right. a should always be == to itself (reference equality) & both should be consistent.

Seems like Double overloads the == operator. Confirmed by reflector as follows:

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

Strange that looks recursive and no mention of the NaN specific behavior. So why does it return false?

So I add some more code to distinguish

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

Now I see

    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) 
  • for doubles, the == operator call translates to a ceq IL opcode
  • where as for strings, it translates to System.String::op_Equality(string, string).

Sure enough the documentation for ceq specifies that it is special-cased for floating point numbers and NaN. This explains the observations.

Questions:

  • Why is the op_Equality defined on Double ? (And the implementation does not factor in the NaN specific behavior)
  • When is it invoked ?
like image 500
Gishu Avatar asked Feb 19 '13 12:02

Gishu


People also ask

What is the use of == === operators?

The strict equality operator ( === ) checks whether its two operands are equal, returning a Boolean result. Unlike the equality operator, the strict equality operator always considers operands of different types to be different.

What does == mean in JavaScript?

The equality operator ( == ) checks whether its two operands are equal, returning a Boolean result. Unlike the strict equality operator, it attempts to convert and compare operands that are of different types.

Why do we use == in Python?

The == operator helps us compare the equality of objects. The is operator helps us check whether different variables point towards a similar object in the memory. We use the == operator in Python when the values of both the operands are very much equal.

What is === vs ==?

JavaScript provides three different value-comparison operations: === — strict equality (triple equals) == — loose equality (double equals)


1 Answers

Reflector's erroneous interpretation

The decompilation that you are seeing from Reflector is actually a bug in Reflector. Reflector needs to be able to decompile a function where two doubles are being compared; in those functions, you would find ceq emitted right into the code. As a result, Reflector interprets a ceq instruction as == between two doubles to help decompile a function where two doubles are being compared.

By default, value types don't come with an == implementation. (Don't user-defined structs inherit an overloaded == operator?) However, all of the built-in scalar types have an explicitly overloaded operator that the compiler translates into the appropriate CIL. The overload also contains a simple ceq based comparison, so that dynamic/late-bound/Reflection-based invokes of the == operator overload won't fail.


More details

For predefined value types, the equality operator (==) returns true if the values of its operands are equal, false otherwise. For reference types other than string, == returns true if its two operands refer to the same object. For the string type, == compares the values of the strings.

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

What you said implies that == uses reference type semantics for comparison of a double. However, since double is a value type, it uses value semantics. This is why 3 == 3 is true, even though they're different stack objects.

You can almost think of this compiler translation as how LINQ's Queryable object contains extension methods with code in them, but the compiler translates these calls into expression trees which are passed to the LINQ provider instead. In both cases, the underlying function never really gets called.


Double's comparison semantics

The documentation for Double does allude to how the ceq CIL instruction works:

If two Double.NaN values are tested for equality by calling the Equals method, the method returns true. However, if two NaN values are tested for equality by using the equality operator, the operator returns false. When you want to determine whether the value of a Double is not a number (NaN), an alternative is to call the IsNaN method.

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


Raw compiler source

If you look in the decompiled C# compiler source, you'll find the following code to handle direct translation of double comparisons into 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; } 

The above code is from Roslyn.Compilers.CSharp.CodeGen.CodeGenerator.EmitBinaryCondOperator(...), and I added the "..."'s in order to make the code more readable for this purpose.

like image 136
David Pfeffer Avatar answered Sep 29 '22 09:09

David Pfeffer