6

I try following code use == and Equals on number comparison:

Console.WriteLine( (int)2 == (double)2.0 );     
Console.WriteLine( ( (int)2 ).Equals( (double)2.0) );

Console.WriteLine((float)2.0 == (double)2.0);
Console.WriteLine( ( (float)2.0 ).Equals( (double)2.0 )   );

The result:

true 
false
true 
false

int, double, float are all ValueType, after reading posts Here1 and Here2, I still cannot understand why == and Equals turns out different result,

What is the working detail behind == and Equals in these 4 cases about number?

(if this question is duplicate please tell me)



EDIT: 4 more interesting cases:

double, float <-> int

Console.WriteLine((double)2.0 == (int)2);              //True
Console.WriteLine(((double)2.0).Equals((int)2));       //True

Console.WriteLine((float)2.0 == (int)2.0);             //True
Console.WriteLine(((float)2.0).Equals((int)2.0));      //True

double, int <-> float

Console.WriteLine((double)2.0 == (float)2.0);          //True
Console.WriteLine(((double)2.0).Equals((float)2.0));   //True

Console.WriteLine((int)2 == (float)2.0);               //True
Console.WriteLine(((int)2).Equals((float)2.0));        //False
yu yang Jian
  • 6,680
  • 7
  • 55
  • 80
  • 1
    You can see this question: https://stackoverflow.com/q/46829332/5311735. Not sure if it's duplicate or not but it should explain situation in your question too. – Evk Oct 23 '17 at 08:12
  • Another similar question: https://stackoverflow.com/questions/814878/c-sharp-difference-between-and-equals. This is related to behavior of floating-point numbers against integers (floating point inaccuracy exists in `float` & `double` but not `decimal`). Also `Equals` is a virtual method which overridable to fit with certain data type. – Tetsuya Yamamoto Oct 23 '17 at 08:14
  • @PatrickHofman Removed – Ipsit Gaur Oct 23 '17 at 08:15
  • 2
    As for the edit - you can again look at the answer I linked in first comment, it's also explained there (the reason is `int` can be implicitly converted to `double` and `float` so double.Equals(double\float)` overload of `Equals` is called. – Evk Oct 23 '17 at 08:47

6 Answers6

5

From MSDN:

ValueType.Equals indicates whether this instance and a specified object are equal.

and

Return value:

Type: System.Boolean

true if obj and this instance are the same type and represent the same value; otherwise, false.*

If you do this:

        int a = 1;
        double b = a;
        bool check = a.Equals(b);

You are calling this implementation of Equals:

[__DynamicallyInvokable]
public override bool Equals(object obj)
{
  if (!(obj is int))
    return false;
  return this == (int) obj;
}

If you do this:

        int a = 1;
        int b = a;
        bool check = a.Equals(b);

You are calling this other:

[NonVersionable]
[__DynamicallyInvokable]
public bool Equals(int obj)
{
  return this == obj;
}
Giancarlo Melis
  • 697
  • 4
  • 17
3

(int)2 == (double)2.0 and (float)2.0 == (double)2.0 are compared at compile time. Actually it doesn't compare the backing data types but the values as seen by the compiler (hence 2==2). And even then, the == on float/int does an implicit type conversion.

The Equals method though is ran on runtime, where types are different and hence the method returns false.

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • 2
    That's not true (about "floating point imprecision kicks in") - it fails because `float` and `double` are different types (and `int` and `double`). – Evk Oct 23 '17 at 08:15
  • We should take note that `Equals(object obj)` can be overridden which may cause different behavior. – jegtugado Oct 23 '17 at 08:16
  • @Evk You are right. Checked and referenced the code. – Patrick Hofman Oct 23 '17 at 08:17
  • Eh? Given a `bool AreEqual(int i, double d) { return i == d; }`, `AreEqual(2, 2.0)` returns true even though the comparison is performed at run time. I don't see why compile vs run time matters here. –  Oct 23 '17 at 09:21
  • @hvd, yeah. Added the implicit type conversion, which is a point here too. – Patrick Hofman Oct 23 '17 at 09:25
3
(int)2 == (double)2.0            - True because the compiler promotes int to double when comparing via ==.
((int)2).Equals( (double)2.0)    - False because this is calling int.Equals(object) and the types are different.
(float)2.0 == (double)2.0        - True because the compiler promotes float to double when comparing via ==.
((float)2.0).Equals((double)2.0) - False becaue this is calling float.Equals(object) and the types are different.
(double)2.0 == (int)2            - True because the compiler promotes int to double when comparing via ==.
((double)2.0).Equals((int)2)     - True because there exists double.Equals(double) and the compiler
                                   promotes the integer parameter 2 to double to call double.Equals(2.0).
(float)2.0 == (int)2.0           - True because the compiler promotes int to float when comparing via ==.
((float)2.0).Equals((int)2.0)    - True because there exists float.Equals(float) and the compiler
                                   promotes the integer parameter 2 to float to call float.Equals(2.0f).
(double)2.0 == (float)2.0)       - True because the compiler promotes float to double when comparing via ==.
((double)2.0).Equals((float)2.0) - True because there exists double.Equals(double) and the compiler
                                   promotes the float parameter 2.0f to double to call double.Equals(2.0).
(int)2 == (float)2.0             - True because the compiler promotes int to float when comparing via ==.
((int)2).Equals((float)2.0)      - False because this is calling int.Equals(object) and the types are different.

Note that in the cases where false is returned, it's because although int.Equals(int) exists, the compiler cannot call it because there is no automatic type conversion from floating point types to int.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
2

The == is an operator and the compiler will first apply implicit conversions to widen one of the the operands when needed.

1.0 == 1 => 1.0 == 1.0 => true

The Equals() method does not trigger implicit conversion and so it returns false. It is also more expensive, requiring a boxing operation. And one of the first things it checks is if the operands are of the same type.

(1.0).Equals(1) => Double(1.0).Equals(object(1)) => Double == Int32 => false

H H
  • 263,252
  • 30
  • 330
  • 514
  • But why `object(1.0).Equals(object(1))`? it is `float(1.0).Equals(object(1))` – Evk Oct 23 '17 at 08:45
  • @Evk - yes, I overdid that. Will edit. But `1.0` is Double, not Single. – H H Oct 23 '17 at 08:58
  • @Evk `((object)1.0).Equals(1)` returns `false`, not `true`. – Matthew Watson Oct 23 '17 at 09:01
  • @MatthewWatson and how is this relevant? @Henk your example is still not good, because `(1.0).Equals(1)` returns `true`, because `int` can be implicitly converted to `double` which it does and so `double.Equals(double)` overload is called. – Evk Oct 23 '17 at 09:05
  • @Evk Sorry, I thought you were wondering why `object(1.0).Equals(object(1))` was true (which it isn't, and also it's not valid code - I assumed you meant `((object)1.0))` etc). I'm confused now as to what you meant... – Matthew Watson Oct 23 '17 at 09:07
  • @MatthewWatson I just used the same "syntax" as Henk did in answer. So by object(1.0).Equals(object(1) he meant that `object.Equals(object)` overload is called, and I said that really `float.Equals(object)` is called. – Evk Oct 23 '17 at 09:15
  • @Evk Thanks, I understand now (and of course, you are correct). – Matthew Watson Oct 23 '17 at 09:41
0

As plain numbers, == compares the values. When using the .Equals method, they are treated as instances of object and hence the .Equals sees the types as different and the condition fails.

You would expect it to give true, but the type checking condition precedes the value comparison.

staa99
  • 191
  • 1
  • 2
  • 13
0

Thanks for everyone's answer, they are all good informations, and make me find Equals method Doc of each type.

In all cases related to == are all true, and cases related to Equals are:

Console.WriteLine( ( (int)2 ).Equals( (double)2.0) );      //False
Console.WriteLine( ( (float)2.0 ).Equals( (double)2.0 ) ); //False
Console.WriteLine(((double)2.0).Equals((int)2));           //True
Console.WriteLine(((float)2.0).Equals((int)2.0));          //True
Console.WriteLine(((double)2.0).Equals((float)2.0));       //True
Console.WriteLine(((int)2).Equals((float)2.0));            //False

and equals method MSDN link: Double, Int32, Float(Single)

In method description, Equals method are all in the format:

public bool Equals(double obj)
public bool Equals(int obj)
public bool Equals(float obj)

So I guess when using Equals, the value wait to be compared will be first converted, and key point is whether the type can be converted without being truncated or rounded off.

Here correspond to 6 cases:

// double cannot implicit convert to int -> F
Console.WriteLine( ( (int)2 ).Equals( (double)2.0) );      //False
// double cannot implicit convert to float -> F
Console.WriteLine( ( (float)2.0 ).Equals( (double)2.0 ) ); //False
// int can implicit convert to double -> T
Console.WriteLine(((double)2.0).Equals((int)2));           //True
// int can implicit convert to float -> T
Console.WriteLine(((float)2.0).Equals((int)2.0));          //True
// float can implicit convert to double -> T
Console.WriteLine(((double)2.0).Equals((float)2.0));       //True
// float cannot implicit convert to int -> F
Console.WriteLine(((int)2).Equals((float)2.0));            //False

Edit and Correct:

By Go to Definition Funciton in VS, True Case all go to Equals(double/float/int obj), and the False Case all go to Equals(object obj), and in the description of Equals(object obj):

Returns:
true if obj <is an instance of Int/Float/Double> and <equals the value> of this instance; 
otherwise, false.

So I guess if the implicit conversion fail, it goes to Equals(object obj) and get false when type checking.

yu yang Jian
  • 6,680
  • 7
  • 55
  • 80