The other answers are correct, so I won't repeat them here.
What I want to add is another potential surprise about considering == as an equivalence relation, which normally imply associativity:
(a == b) and (b == c) => (a == c)
But if you try this snippet for example in https://repl.it :
class Main {
public static void main(String[] args) {
int i=16777217;
float f=16777216.0f;
double d=16777216.0;
if(i == f) {
System.out.println("i == f");
}
if(d == f) {
System.out.println("d == f");
}
if(i == d) {
System.out.println("i == d");
}
}
}
The surprise is that i==f
and f==d
but not(i==d)
...
This is because when writing i==f
, an implicit conversion float(i)==f
happens, and this conversion potentially loose precision because an int can require up to 31 bits of precision, while a float offers at most 24.
This could have been different, if you look how Lisp, Scheme or recent Squeak/Pharo Smalltalk handles the comparisons, you'll see that they care for exactness...
Extract from http://www.lispworks.com/documentation/lcl50/aug/aug-170.html
In general, when an operation involves both a rational and a floating-point argument, the rational number is first converted to floating-point format, and then the operation is performed.
This conversion process is called floating-point contagion. However, for numerical equality comparisons, the arguments are compared using rational arithmetic to ensure transitivity of the equality (or inequality) relation.