Equality comparison in 1.3

338 views
Skip to first unread message

Chris Perkins

unread,
Oct 1, 2011, 5:34:40 PM10/1/11
to clo...@googlegroups.com
I am trying to upgrade some code to 1.3, and I'm not sure how to do the equivalent of a 1.2-style equality comparison.

user> (= {:foo 23} {:foo 23.0})
false

This used to be true.  I see that = is now documented to compare same-type numbers only, but == can be used for 1.2-compatible comparisons. However:

user> (== {:foo 23} {:foo 23.0})
; Evaluation aborted.

== only seems to work on numbers.

How can I upgrade code that expects 1.2-compatible equality comparisons? Or do I need to change my expectations of what is considered equal in clojure?


- Chris


Daniel

unread,
Oct 2, 2011, 12:52:19 AM10/2/11
to Clojure
user=> (= 23.0 23)
false
user=> (= 23 23)
true


"compares numbers and collections in a type-independent manner"

This kind of breakage was probably to be expected with the numerics
changes in 1.3. I am also interested in this.

Sean Corfield

unread,
Oct 2, 2011, 2:08:15 AM10/2/11
to clo...@googlegroups.com
On Sat, Oct 1, 2011 at 9:52 PM, Daniel <double...@gmail.com> wrote:
> user=> (= 23.0 23)
> false

With every language I've ever worked in, I've always been told that
comparing floating point numbers for equality is a bad idea so I'm
actually glad to see that the above comparison is false...
--
Sean A Corfield -- (904) 302-SEAN
An Architect's View -- http://corfield.org/
World Singles, LLC. -- http://worldsingles.com/
Railo Technologies, Inc. -- http://www.getrailo.com/

"Perfection is the enemy of the good."
-- Gustave Flaubert, French realist novelist (1821-1880)

Stuart Halloway

unread,
Oct 2, 2011, 8:58:31 AM10/2/11
to clo...@googlegroups.com
user=> (= 23.0 23)
false
user=> (= 23 23)
true

This is the correct behavior. If the doc string is confusing, please propose alternate language.

Stu

Stuart Halloway
Clojure/core
http://clojure.com

Chris Perkins

unread,
Oct 2, 2011, 9:08:35 AM10/2/11
to clo...@googlegroups.com
Stuart,

The documentation is clear (to me) about comparing numbers directly - that part is fine. My question was about whether there is an equivalent of the new == comparison that works on containers with floating-point numbers inside.

I had guessed that == would recursively compare the contents with "loosely typed" comparison, so that, for example (== [23] [23.0]) would be true, but it does not.

Is there an equality comparison that does this? I couldn't find any mention of one in the numerics docs, but maybe missed it.


thanks,

- Chris

Stuart Halloway

unread,
Oct 2, 2011, 9:23:47 AM10/2/11
to clo...@googlegroups.com
Gotcha. No, there isn't. I don't think doing this kind of comparison is a good idea, but I have been wrong before.  If you need it, either a proposal on the wiki [1] or an incubator patch [2] would be fine.

In this case, [2] is probably better. Getting things in incubator is *much* easier than getting things in Clojure. This should be where we start to feel the benefit of modular contrib.

Chris Perkins

unread,
Oct 2, 2011, 3:29:01 PM10/2/11
to clo...@googlegroups.com
Follow-up question: Can someone explain the rationale behind the change to = semantics between integers and floating-point numbers?  I have read the design page (http://dev.clojure.org/display/doc/Documentation+for+1.3+Numerics), but all it seems to have is this somewhat cryptic description:

"It compares values in a type-independent manner, but not between floating points and integer types. This allows numbers to be used as map keys with correct semantics."

I have been playing with floating-point map-keys in 1.2, trying to find some behavior that seems like incorrect semantics, but I haven't found anything odd. What sort of incorrect semantics is this change trying to correct?

- Chris

Luc Prefontaine

unread,
Oct 2, 2011, 4:07:03 PM10/2/11
to clo...@googlegroups.com
user=> (def m { 3.0 :a 3 :b})
#'user/a
user=> a
{3.0 :a, 3 :b}
user=> (get m 3.0 )
:a
user=> (get m 3 )
:b
user=>

if 3.0 and 3 were to be considered equals, you could not represent the above map,
the second entry would squash the first.

Now if we want to allow such maps, then we cannot consider 3.0 and 3 to be equal else where,
it's a matter of consistency.

Luc P.

--
Luc P.

================
The rabid Muppet

Daniel

unread,
Oct 2, 2011, 4:20:14 PM10/2/11
to Clojure
Right. I didn't see this earlier (http://dev.clojure.org/display/doc/
Documentation+for+1.3+Numerics). We're all on the same page now. (=

> If the doc string is confusing, please propose alternate language.

Updating the '=' docstring to match that page sounds appropriate ie
adding "...,but not between floating points and integer types."


This kind of change in equality semantics seems prone to cause really
sinister bugs in old 1.2 programs not well unit tested, so I've got a
general concern that there's no easy upgrade (like an =' (prime)) for
old programs that may have relied on this documented functionality.

Chris Perkins

unread,
Oct 2, 2011, 4:31:40 PM10/2/11
to clo...@googlegroups.com
Luc,

I think you are mistaken.

user=> (clojure-version)
"1.2.1"
user=> (def m {3.0 :a 3 :b})
#'user/m
user=> (get m 3.0)
:a
user=> (get m 3)
:b
user=> (= 3 3.0)
true
user=> 

Do you have another example?

- Chris

Daniel

unread,
Oct 2, 2011, 4:41:03 PM10/2/11
to Clojure
I think he's saying that it boiled down to consistency ie it's
inconsistent to allow maps where 3 != 3.0, but make 3 = 3.0 fit
clojure equality semantics. I don't know if that's satisfying, but
it's fair.

Luc Prefontaine

unread,
Oct 2, 2011, 4:46:57 PM10/2/11
to clo...@googlegroups.com
What I meant is that (= 3 3.0) is the erroneous behavior.

Either it's equal every where (including as keys in maps) or not.

You cannot have both semantics coexists. It's all or nothing.

Look at this the other way around (1.2):

user=> {3 :a 3 :b}
java.lang.IllegalArgumentException: Duplicate key: 3
user=> (= 3.0 3)
true
user=> {3.0 :a 3 :b}
{3.0 :a, 3 :b}

This is non sense, if 3.0 and 3 are to be equal, then the last map should also fail
with a duplicate entry.

It's an inconsistent behavior.

--

Chris Perkins

unread,
Oct 2, 2011, 5:03:47 PM10/2/11
to clo...@googlegroups.com
Ok, I follow you now.  That makes sense.  Sort-of :)

On the other hand, it's only inconsistent if you consider clojure's = to map to java's .equals method, but it does not:

user=> (clojure-version)
"1.2.1"
user=> (= 3 3.0)
true
user=> (.equals 3 3.0)
false

So it doesn't really violate the contract for java's Map, which tells us that if a.equals(b), then they must have the same hash, and so a and b cannot both be keys. If you think of clojure's = as a looser sort of equivalence, then it's not a problem. I think.

In any case, the 1.3 behavior does feel weird in sometimes:

user> (def i 3)
#'user/i
user> (def j 3.0)
#'user/j
user> (< i j)
false
user> (> i j)
false
user> (= i j)
false

OK, that kind-of freaks me out...  :)

- Chris


Luc Prefontaine

unread,
Oct 2, 2011, 5:51:07 PM10/2/11
to clo...@googlegroups.com
In practice, in most languages, it's uncertain to test for equality between floating point values.
This is mainly a side effect of internal representations.

http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm

It's probably safer to keep things that way. Ints (or longs) are not floating point values.
The same issues could arise if we allow cross type equality tests, especially with computed values,
giving a false impression of a defined behavior.

If you have nearly equal values between an int (or long) and a floating point value, where do round up ?
Is it fine to round up the decimals or dropping them because they are "not significant" ?
For whom ? How often does a floating point value end up with no decimals following a computation ?

The above is a can of worms especially in heavy computation algorithms.

If Clojure stays on the most accepted side of the fence, then all the semantic issues that do not respect
this must change.

I would rather see something like this in the code:

(= 3 (long 3.8))

or even better

(= 3 (trunc 3.8))

Which clearly states what we want to test.

Luc P.

--

Chris Perkins

unread,
Oct 3, 2011, 9:08:09 AM10/3/11
to clo...@googlegroups.com
core.clj currently contains two definitions of =, one of which is commented out. The active one has this docstring:

  "Equality. Returns true if x equals y, false if not. Same as
  Java x.equals(y) except it also works for nil, and compares
  numbers and collections in a type-independent manner.  Clojure's immutable data
  structures define equals() (and thus =) as a value, not an identity,
  comparison."

and the commented-out one has this:

  "Equality. Returns true if x equals y, false if not. Same as Java
  x.equals(y) except it also works for nil. Boxed numbers must have
  same type. Clojure's immutable data structures define equals() (and
  thus =) as a value, not an identity, comparison."

Sounds to me like the second one was intended to be the new docstring.

- Chris

Reply all
Reply to author
Forward
0 new messages