Confused about == and equals

258 views
Skip to first unread message

Iftikhar Khan

unread,
Apr 18, 2014, 11:35:33 AM4/18/14
to scala-l...@googlegroups.com
This was a post on Stack Exchange which really confused me today. I'm a Scala newbie as well and I am just going by what I have read so far from Programming Scala, 2nd edition.

There have been some answers to it but I'm not entirely convinced by them.

The question is:

Why does this comparison evaluate to `true`?

    scala> Double.NaN equals java.lang.Double.NaN
    res5: Boolean = true

But this one evaluates to `false`?

    scala> Double.NaN == java.lang.Double.NaN
    res6: Boolean = false


Here is my thinking so far:

I know that the Scala Double.NaN is really just java.lang.Double.NaN and that the Scala Double.NaN is a value type. Because it is a value type, the '==' operator will work intuitively and do a value comparison as opposed to a reference comparison. However, as can be seen above the outcome is 'false' (first point of confusion). I printed out the bit value for Double.NaN and java.lang.Double.NaN and the value is the same so I was expecting it to evaluate to 'true'.

According to the API, the equals method is used for reference types. Where the example uses 'equals', I was expecting it the outcome to 'false' because they are two different objects being compared.

I would appreciate it if someone could clarify this and put me on the right track.

Thanks
Iftikhar

Tom Switzer

unread,
Apr 18, 2014, 11:42:26 AM4/18/14
to scala-l...@googlegroups.com
So, there are a few things at work. equals is directly calling Java's Object#equals, which will evaluate equality as defined for the object's class. For Java's Double, this says NaN is equal to NaN.

The IEEE spec disagrees and says that anything compared with NaN is false, including itself. This makes some sense, because NaN isn't a thing, it is a place holder for a large number of "invalid" values. There is no way of knowing whether 2 NaN's are equal, because they could be represented 2 different "invalid" values, if you will. So, in Java, 2 real NaNs (ie of type double - lower case) compare as false, because Java's double (lower case) are correct.

Now, Scala's == does NOT just call down to Java's. It special cases comparisons with primitives, because Scala doesn't differentiate between boxed/unboxed primitives. It has to choose 1 behaviour or the other. It can't choose both and no matter which it choses, it's going to be wrong in the other case. Choosing the behaviour specified by the IEEE spec is absolutely the right thing to do. Java's boxed NaN's should've done the same, honestly.


--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Tom Switzer

unread,
Apr 18, 2014, 11:44:36 AM4/18/14
to scala-l...@googlegroups.com
Also important to remember that there is no "equals" on a real (Java/JVM) primitive double. If you call x equals y, then it implies you are working with a boxed version of Double on the left hand side, because equals doesn't exist on the primitive.

Andrew Phillips

unread,
Apr 18, 2014, 12:11:37 PM4/18/14
to scala-l...@googlegroups.com
On a related note:


and the thread that give rise to that:


Scala's behaviour in relation to Double comparisons changed between 2.9 and 2.10, by the way - see https://issues.scala-lang.org/browse/SI-5104

ap

Iftikhar Khan

unread,
Apr 18, 2014, 1:08:40 PM4/18/14
to scala-l...@googlegroups.com
Thanks Tom. That's cleared all the confusion. Appreciated.

Scott Carey

unread,
Apr 19, 2014, 10:26:52 PM4/19/14
to scala-l...@googlegroups.com


On Friday, April 18, 2014 8:42:26 AM UTC-7, Tom Switzer wrote:

Now, Scala's == does NOT just call down to Java's. It special cases comparisons with primitives, because Scala doesn't differentiate between boxed/unboxed primitives. It has to choose 1 behaviour or the other. It can't choose both and no matter which it choses, it's going to be wrong in the other case. Choosing the behaviour specified by the IEEE spec is absolutely the right thing to do. Java's boxed NaN's should've done the same, honestly.



I disagree.  Java does the right thing.  There is only one correct choice for its equals() method:  it must be an equivalence relation so that it can be consistent with hashCode and compare.  Choosing the IEEE equality would be unsound.

The problem is the conflation of two different types of equals.    For numeric value comparison "do these two values represent the same number?"  NaN should never equal NaN as IEEE describes.   But that is not an equivalence relation.
There is another type of equals, where that must return true.   For example, if a NaN is used as a key in a map.

The general confusion in this area is that there are two distinct types of 'equals' for floating point numbers, and two related orderings -- a Total Ordering that corresponds with the equivalence relation, and a partial ordering that corresponds with the value equality.  Each has its place, but these are often confused -- and I think Scala has conflated these two in worse ways than Java has.

Java's equals method is required to be an equivalence relation.  It must be:

reflexive:   for all a  :  a equals a
symmetric:  for all a, b:   if  a equals b   then   b equals a
transitive:  for all a, b, c:   if a equals b and b equals c, then a equals c

Critically, the equivalence relation has a corresponding total ordering, where NaN has a place amongst other floating point values such as +Infinity +0, -0, and -Infinity, and where a equals b is when neither a < b nor a > b.  Given any total ordering, you can construct an equivalence relation with the above properties.

A type with a total order and a corresponding equivalence relation can be used as a key in maps that use either equality (e.g. hash based) or order (trees) to do their business.  As that is the primary use case for equivalence and ordering features in most programming languages, it makes the most sense to me that the case where NaN != NaN is the special, more limited one.   It really boils down to what you are doing with the value -- mathematical calculations?  Or set operations?

Unfortunately, Scala specifies only == and eq
but three are needed:
  reference equality , and a corresponding hash  (these aren't needed for true value types)
  equivalence relation, and a corresponding hash and optional total order   [ NaN == NaN ;  (NaN > 0 xor NaN < 0) ]
  value equality, and an optional partial order relation    [ NaN != NaN;  !(NaN < 0); !(NaN > 0) ]


 

Alec Zorab

unread,
Apr 22, 2014, 6:38:49 AM4/22/14
to scala-l...@googlegroups.com
I'm not so sure about java equality being an equivalence relationship. Consider the following pseudocode

class A(a:Int){ /*insert equals impl here*/}

clearly we can check the values of "a" to compare equality.

Now consider

class B(as:Int, b:Int) extends A(as) { /* insert equals here*/ }

val a = new A(1)
val b = new B(1,2)

println(a == b) 

what should the above print?

println(b == a)

what about that?

We could make b == a if we checked the type of the argument to equals and then deferred to the parent type if that was appropriate which gives us symmetry, but then that leaves us in a rather thorny situation when we do this....
val b2 = new B(1, 2)
val b3 = new B(1, 3)

println(a == b2) //true
println(a == b3) //true
println(b2 == b3) //false



I'm somewhat unconvinced you can write equals methods that provide equivalence relationships in the presence of subtyping, other than referential equality.


Oliver Ruebenacker

unread,
Apr 22, 2014, 6:45:39 AM4/22/14
to scala-l...@googlegroups.com

     Hello,

  What you have shown is that equality can not always be equality of all fields if sub-classes add new fields. That is probably one of the reasons why a case class can not extend a case class.

  Once a class implements equals other than reference equality, all sub-classes should be consistent with that.

     Best,
     Oliver

Oliver Ruebenacker
Be always grateful, but never satisfied.

Alec Zorab

unread,
Apr 22, 2014, 6:51:38 AM4/22/14
to scala-l...@googlegroups.com
Sure. so subtypes can't have a more specific definition of equality than their super type. Which rather defeats the point of subtyping.

Jason Zaugg

unread,
Apr 22, 2014, 7:03:59 AM4/22/14
to scala-l...@googlegroups.com
On Tue, Apr 22, 2014 at 12:51 PM, Alec Zorab <alec...@gmail.com> wrote:
Sure. so subtypes can't have a more specific definition of equality than their super type. Which rather defeats the point of subtyping.

You can recover this with a virtual `canEqual` method.


-jason

Oliver Ruebenacker

unread,
Apr 22, 2014, 10:24:18 AM4/22/14
to scala-l...@googlegroups.com

     Hello,

On Tue, Apr 22, 2014 at 6:51 AM, Alec Zorab <alec...@gmail.com> wrote:
Sure. so subtypes can't have a more specific definition of equality than their super type. Which rather defeats the point of subtyping.

  If by "more specific definition of equality" you mean something like "we first thought these two objects are equal, but on closer look it turns out they are not", that would violate substitution principle. That's not merely a Java/Scala thing. The one special thing that Java and Scala do is to force you to choose one type of equality and embed it into the object definition. But you can always have external implementations of alternative equalities, and you can wrap objects.

     Best,
     Oliver

Oliver Ruebenacker

unread,
Apr 22, 2014, 10:39:48 AM4/22/14
to scala-l...@googlegroups.com

     Hello,

  I would question whether it ever makes sense to test floating points for equality, given their approximate nature. Can't think of a use-case.

  While we're at it, I can't think of a use-case for boxing floating points, either.

     Best,
     Oliver

Paul Hudson

unread,
Apr 22, 2014, 10:46:25 AM4/22/14
to scala-l...@googlegroups.com

On 22 April 2014 15:39, Oliver Ruebenacker <cur...@gmail.com> wrote:
I would question whether it ever makes sense to test floating points for equality, given their approximate nature. Can't think of a use-case.

Certainly, one needs to be aware of the possibilities of inexact results, but  it's quite possible to use floating point in a way that is not approximate. (2.0 + 2.0 = 4.0, even for floating point). There are many use-cases.

Oliver Ruebenacker

unread,
Apr 22, 2014, 11:10:02 AM4/22/14
to scala-l...@googlegroups.com

     Hello,

  I don't see the use case of adding 2.0 and 2.0 and comparing it to 4.0.

  Also, how do you know it will compare as true? Consider:

scala> 1.9 - 1.8 == 1.8 - 1.7
res40: Boolean = false

     Best,
     Oliver

--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Seth Tisue

unread,
Apr 22, 2014, 11:17:37 AM4/22/14
to scala-l...@googlegroups.com
On Tue, Apr 22, 2014 at 11:10 AM, Oliver Ruebenacker <cur...@gmail.com> wrote:
> I don't see the use case of adding 2.0 and 2.0 and comparing it to 4.0.
> Also, how do you know it will compare as true?

IEEE 754 guarantees it.

Oliver Ruebenacker

unread,
Apr 22, 2014, 11:29:49 AM4/22/14
to scala-l...@googlegroups.com

     Hello,

  I agree for this special case, since 2.0 and 4.0 have exact representations. But most floating point literals don't have exact IEEE floating point representations, so there is no guarantee they compare equal although the literals look like they should (e.g. 1.9-1.8 is not equal to 1.8-1.7).

  And even if you have a set of double values that compare as expected, I could still not imagine what the use case could be.

Paul Hudson

unread,
Apr 22, 2014, 11:37:19 AM4/22/14
to scala-l...@googlegroups.com

On 22 April 2014 16:29, Oliver Ruebenacker <cur...@gmail.com> wrote:
And even if you have a set of double values that compare as expected, I could still not imagine what the use case could be.

I might be writing a Lua interpreter, say (where the only numeric type is float, and people - correctly - assume they can compare integer-valued values all the time)


Seth Tisue

unread,
Apr 22, 2014, 12:07:30 PM4/22/14
to scala-l...@googlegroups.com
On Tue, Apr 22, 2014 at 11:37 AM, Paul Hudson <phu...@pobox.com> wrote:
> I might be writing a Lua interpreter, say (where the only numeric type is
> float, and people - correctly - assume they can compare integer-valued
> values all the time)

JavaScript, same. NetLogo (the language I work on), same. (That's
actually how I happen to know that IEEE 754 guarantees 2.0 + 2.0 = 4.0
— we wouldn't have been able to make this implementation choice
otherwise.)

Languages that work this way tend to print their integers without the
trailing ".0":

V8 version 3.21.17 [sample shell]
> print(3.0)
3

Scott Carey

unread,
Apr 22, 2014, 1:32:14 PM4/22/14
to scala-l...@googlegroups.com
Have you ever tried to sort them?

What about organizing multidimensional coordinates in a tree?

If you have > and <, you'll soon want == , <= and =>.

When you want "is this value 1.23456789?"  you'll need an approximation of equals to deal with the floating point approximation.
When you want "where in this tree would this value be located" you'll need a total ordering and equality relation.

The problem is that these are often conflated, and in both Java and Scala the object model asks you to choose one and only one for ".equals()" and "==", respectively.

Oliver Ruebenacker

unread,
Apr 22, 2014, 3:15:02 PM4/22/14
to scala-l...@googlegroups.com

     Hello,

  I did not know you could reliably do integer arithmetic using floating points. Of course, it is more expensive, so you only would do it if you have to. Plus, integers have more digits than floats of the same size.

  I'm not convinced you want an equals for sorting - lesser and greater would be sufficient (admittedly, if it wasn't for NaN, that would imply an equals).

  Questions of the kind "is this value 1.23456789" usually do not call for an equals, precisely due to the approximate nature - you would rather ask whether it is close enough for your purpose, and that's usually not a transitive relationship.

  Ok, there are some use cases, if not that many.

     Best,
     Oliver

David Starner

unread,
Apr 22, 2014, 3:25:43 PM4/22/14
to scala-l...@googlegroups.com
On Tue, Apr 22, 2014 at 7:39 AM, Oliver Ruebenacker <cur...@gmail.com> wrote:
> I would question whether it ever makes sense to test floating points for
> equality, given their approximate nature. Can't think of a use-case.

Handling division by zero. Or reading in a file that's a list of
floating point numbers with 0.0 as a terminator.

--
Kie ekzistas vivo, ekzistas espero.

Oliver Ruebenacker

unread,
Apr 22, 2014, 3:38:44 PM4/22/14
to scala-l...@googlegroups.com

     Hello,

On Tue, Apr 22, 2014 at 3:25 PM, David Starner <prosf...@gmail.com> wrote:
On Tue, Apr 22, 2014 at 7:39 AM, Oliver Ruebenacker <cur...@gmail.com> wrote:
>   I would question whether it ever makes sense to test floating points for
> equality, given their approximate nature. Can't think of a use-case.

Handling division by zero.

  In my experience, cases where floats are divided by zero usually fall into one of these categories:

  (1) No problem, just continue with Double.Inf.
  (2) We need to check for values close to zero, too, anyway.
  (3) Fix the bug.
 
Or reading in a file that's a list of
floating point numbers with 0.0 as a terminator.

  That is an interesting terminator.

     Best,
     Oliver

--
Kie ekzistas vivo, ekzistas espero.
--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

David Starner

unread,
Apr 22, 2014, 3:52:40 PM4/22/14
to scala-l...@googlegroups.com
On Tue, Apr 22, 2014 at 12:38 PM, Oliver Ruebenacker <cur...@gmail.com> wrote:
>
>> Or reading in a file that's a list of
>> floating point numbers with 0.0 as a terminator.
>
>
> That is an interesting terminator.

It's not necessarily a good idea, but I had that exact example in
class, and I suspect there's real-life data files out there like that.
It's simpler in many languages to read in a float then to read in a
string and convert it to a float; in fact, early versions of Fortran
it was much harder, if not impossible.

Oliver Ruebenacker

unread,
Apr 22, 2014, 4:06:09 PM4/22/14
to scala-l...@googlegroups.com

     Hello,

  Yes, I think I vaguely remember having Fortran problems in college just like that. Also, Turbo Pascal had a file type consisting entirely of objects of one type.

     Best,
     Oliver
 

--
Kie ekzistas vivo, ekzistas espero.

--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Naftoli Gugenheim

unread,
May 21, 2014, 9:24:44 PM5/21/14
to scala-l...@googlegroups.com

So when is the scala standard library getting a typeclass-based equality? ;)

Reply all
Reply to author
Forward
0 new messages