== operator, arrays, structs, and floats

1,919 views
Skip to first unread message

Mark Summerfield

unread,
Feb 11, 2011, 3:08:58 AM2/11/11
to golang-nuts
Hi,

(1) According to the docs == and != are not supported for arrays or
structs. Could someone tell me the official rationale for this
please?

(2) In general comparing floats using == or != isn't considered to be
reliable, so the only real use is to check a denominator for
equality with 0.0 before doing division to avoid division by zero.

There are of course reasonable algorithms for doing float
comparisons. So maybe Go should either:
(a) disallow == and != for floats but provide a global equal()
function or math.EqualFloat64() function, or
(b) keep == and != and make them use an algorithm that does a
smart but fuzzy comparison?

Thanks!

PS OT Quite a while ago "jessta" suggested:
m[key] = _, false
and Rob said
m[key] = _
would be possible but the discussion faded out.

I really hope one of these is taken up; it just seems so
counter-intuitive to supply an actual value simply to have it thrown
away.

--
Mark Summerfield, Qtrac Ltd, www.qtrac.eu
C++, Python, Qt, PyQt - training and consultancy
"Programming in Python 3" - ISBN 0321680561
http://www.qtrac.eu/py3book.html

roger peppe

unread,
Feb 11, 2011, 3:49:33 AM2/11/11
to Mark Summerfield, golang-nuts
On 11 February 2011 08:08, Mark Summerfield <li...@qtrac.plus.com> wrote:
> (2) In general comparing floats using == or != isn't considered to be
>    reliable, so the only real use is to check a denominator for
>    equality with 0.0 before doing division to avoid division by zero.

there are other uses too. for instance, you might use an out-of-range
sentinel value and compare it for equality. also, if you've converted a
float64 directly from an int32, it's non-lossy, so equality
comparisons will work.
this could be useful, for instance, if you're writing an interpreter
and using a single float64 representation for both floats and ints
(quite a few languages do this).

Jan Mercl

unread,
Feb 11, 2011, 4:47:49 AM2/11/11
to golan...@googlegroups.com
On Friday, February 11, 2011 9:08:58 AM UTC+1, Mark wrote:

(1) According to the docs == and != are not supported for arrays or
    structs. Could someone tell me the official rationale for this
    please?

1) Let me put it the other way around. How do you propose to define equality of two struct instances? Please consider also pointer fields and cyclic graphs made of them.
2) As an array item type can be also a struct, the question of array equality can't be resolved before problem 1)

chris dollin

unread,
Feb 11, 2011, 5:01:41 AM2/11/11
to golan...@googlegroups.com
On 11 February 2011 09:47, Jan Mercl <jan....@nic.cz> wrote:
> On Friday, February 11, 2011 9:08:58 AM UTC+1, Mark wrote:
>>
>> (1) According to the docs == and != are not supported for arrays or
>>     structs. Could someone tell me the official rationale for this
>>     please?
>
> 1) Let me put it the other way around. How do you propose to define equality
> of two struct instances?

Same struct type, field-by-field equality.

> Please consider also pointer fields and cyclic
> graphs made of them.

Pointer equality is already defined & does not dereference,
so cyclicity via pointers doesn't matter.

> 2) As an array item type can be also a struct, the question of array
> equality can't be resolved before problem 1)

Two slices are equal if their lengths are equal and the
corresponding available elements are equal.

It may not be perfect, but it's a definition. (Recursion through
interfaces might need thinking about.)

Chris

--
Chris "allusive" Dollin

Mark Summerfield

unread,
Feb 11, 2011, 5:15:31 AM2/11/11
to roger peppe, golang-nuts
Hi Roger,

OK, clearly there are reasonable use cases, so I withdraw my suggestion.

Thanks!

--
Mark Summerfield, Qtrac Ltd, www.qtrac.eu
C++, Python, Qt, PyQt - training and consultancy

"C++ GUI Programming with Qt 4" - ISBN 0132354160

Jessta

unread,
Feb 11, 2011, 5:24:33 AM2/11/11
to Mark Summerfield, golang-nuts
On Fri, Feb 11, 2011 at 7:08 PM, Mark Summerfield <li...@qtrac.plus.com> wrote:
> Hi,
>
> (1) According to the docs == and != are not supported for arrays or
>    structs. Could someone tell me the official rationale for this
>    please?

Aside from the problem of deciding the best definition for what make
two structs equal, the other problem is that this hides what might be
an intensive operation(If the struct or array is quite large) inside a
very small bit of code.

It's rare that someone would actually want to compare two structs to
see if they contain the same values(same with arrays).
It's much more likely that the programmer actually wants to check if
these two structs are the same struct(pointer) and made a mistake.


> PS OT Quite a while ago "jessta" suggested:
>    m[key] = _, false

I don't believe I was the first to suggest this. But still do like it.

> and Rob said
>    m[key] = _
> would be possible but the discussion faded out.

the only issue is, what does:
a:= _ mean?

> I really hope one of these is taken up; it just seems so
> counter-intuitive to supply an actual value simply to have it thrown
> away.

it's rare that people put actual values in to a map, it's more common
to put a pointer. In which case
m[key] = nil, false works fine.
Discouraging people from putting values in maps is probably a good thing.

- jessta
--
=====================
http://jessta.id.au

Jan Mercl

unread,
Feb 11, 2011, 5:25:56 AM2/11/11
to golan...@googlegroups.com
On Friday, February 11, 2011 11:01:41 AM UTC+1, chris dollin wrote:
On 11 February 2011 09:47, Jan Mercl <jan....@nic.cz> wrote:
> On Friday, February 11, 2011 9:08:58 AM UTC+1, Mark wrote:
>>
>> (1) According to the docs == and != are not supported for arrays or
>>     structs. Could someone tell me the official rationale for this
>>     please?
>
> 1) Let me put it the other way around. How do you propose to define equality
> of two struct instances?

Same struct type, field-by-field equality.

> Please consider also pointer fields and cyclic
> graphs made of them.

Pointer equality is already defined & does not dereference,
so cyclicity via pointers doesn't matter.

This is a good/handy proposal for some use cases. In the same time - it is not usable in some others ones (pointer vs pointee equality). Altogether, there's probably no general/universal solution how to define struct equality and that's IMO the reason why it is left out of the language/specs. And not only in Go but in other languages too, presumably for the same reason.

chris dollin

unread,
Feb 11, 2011, 5:32:31 AM2/11/11
to Jessta, Mark Summerfield, golang-nuts
On 11 February 2011 10:24, Jessta <jes...@jessta.id.au> wrote:
> On Fri, Feb 11, 2011 at 7:08 PM, Mark Summerfield <li...@qtrac.plus.com> wrote:
>> Hi,
>>
>> (1) According to the docs == and != are not supported for arrays or
>>    structs. Could someone tell me the official rationale for this
>>    please?
>
> Aside from the problem of deciding the best definition for what make
> two structs equal, the other problem is that this hides what might be
> an intensive operation(If the struct or array is quite large) inside a
> very small bit of code.

a.equals(b) isn't much bigger than a == b.

> It's rare that someone would actually want to compare two structs to
> see if they contain the same values(same with arrays).
> It's much more likely that the programmer actually wants to check if
> these two structs are the same struct(pointer) and made a mistake.

I disagree, because all the times when I've written an
quality test between two structures, that's what I meant.
(Not to mention the cases I didn't write but were written for
me, eg key comparisons in some kind of map.)

> it's rare that people put actual values in to a map, it's more common
> to put a pointer.

I quite often put "actual values" in the non-key part of a map.
Integers and strings and booleans come immediately to mind.
If one wants a non-primitive type in there, I don't see why one
shouldn't be allowed to. Wisdom is not for the compiler.

chris dollin

unread,
Feb 11, 2011, 5:34:59 AM2/11/11
to golan...@googlegroups.com
On 11 February 2011 10:25, Jan Mercl <jan....@nic.cz> wrote:

> Altogether,
> there's probably no general/universal solution how to define struct equality
> and that's IMO the reason why it is left out of the language/specs. And not
> only in Go but in other languages too, presumably for the same reason.

Some of those other languages instead provide ways for the
programmer to attach an appropriate definition of "equality"
to the == operator, or to the corresponding system function if
that's different. This has always struck me as the appropriate
solution.

Jan Mercl

unread,
Feb 11, 2011, 5:43:43 AM2/11/11
to golan...@googlegroups.com
On Friday, February 11, 2011 11:34:59 AM UTC+1, chris dollin wrote:

Some of those other languages instead provide ways for the
programmer to attach an appropriate definition of "equality"
to the == operator, or to the corresponding system function if
that's different. This has always struck me as the appropriate
solution.

func (a mytype) Equal(b mytype) bool { /* ... */ }

Plus  the possibility to have some kind of type Eqaulizer interface { Eqaul(b sometype) bool }

These are explicit and keep the operator overloading out of the way.

Ibrahim M. Ghazal

unread,
Feb 11, 2011, 6:22:42 AM2/11/11
to golang-nuts
On Feb 11, 1:01 pm, chris dollin <ehog.he...@googlemail.com> wrote:
> > 1) Let me put it the other way around. How do you propose to define equality
> > of two struct instances?
>
> Same struct type, field-by-field equality.
>

Sounds reasonable, but what about unnamed structs? Should "struct{a
int}{1} ==struct{a int}{1}" be true or false? And comparison between
named and unnamed structs, for example:
type t struct{a int}
...
t{1} == struct{a int}{1} // true or false?

> > Please consider also pointer fields and cyclic
> > graphs made of them.
>
> Pointer equality is already defined & does not dereference,
> so cyclicity via pointers doesn't matter.
>

I agree, and if deep equality is needed, there is always
reflect.DeepEqual [1]

[1] http://golang.org/pkg/reflect/#DeepEqual

JONNALAGADDA Srinivas

unread,
Feb 11, 2011, 8:44:00 AM2/11/11
to golan...@googlegroups.com
On Friday, February 11, 2011 4:52:42 PM UTC+5:30, Ibrahim M. wrote:

Sounds reasonable, but what about unnamed structs? Should "struct{a
int}{1} ==struct{a int}{1}" be true or false? And comparison between
named and unnamed structs, for example:
type t struct{a int}
...
t{1} == struct{a int}{1} // true or false?

Go employs nominal typing, not structural.  So, I think that both the above scenarios resolve to false.

                                                            Greetings,
                                                                    JS

JONNALAGADDA Srinivas

unread,
Feb 11, 2011, 9:04:32 AM2/11/11
to golan...@googlegroups.com, Mark Summerfield
On Friday, February 11, 2011 3:54:33 PM UTC+5:30, Jessta wrote:

It's rare that someone would actually want to compare two structs to
see if they contain the same values(same with arrays).
It's much more likely that the programmer actually wants to check if
these two structs are the same struct(pointer) and made a mistake.

        I have a current use case to the contrary.

type HalfBond struct {
        otherId int
        order int
}

        This struct represents one half of a bond between two atoms, as viewed by one of them.  Effectively, it represents a neighbor of the current atom.  Several hundred times, I have to compare two such neighbors to see if they are the same.  I *cannot* compare pointers, since the actual instances may have been instantiated in different places, quite independent of one another.  I need to compare the `content'.
        It is certainly very handy to have well-defined semantics for `==' as applying to structures.  Currently, I have a custom `Equals()' defined for each such type.  They work, but add no value -- they simply do a blind field-for-field comparison.  There are more complex cases where a custom comparison is needed (e.g. a ring in a molecule: the order of atoms may not be the same in two instances).  It would be great to have `==' call a standard `Equals()'-kind-of method, of course :-)

        I have a suggestion.  Could people list all the concerns that they have with defining a general `==': be it semantic consistency or performance or whatever else?  We could discuss them, and try to converge on a meaningful and consistent definition.  Should it be possible, then the Go team could consider it, perhaps.

                                                            Greetings,
                                                                    JS

Jessta

unread,
Feb 11, 2011, 9:05:32 AM2/11/11
to chris dollin, Mark Summerfield, golang-nuts
On Fri, Feb 11, 2011 at 9:32 PM, chris dollin <ehog....@googlemail.com> wrote:
>> Aside from the problem of deciding the best definition for what make
>> two structs equal, the other problem is that this hides what might be
>> an intensive operation(If the struct or array is quite large) inside a
>> very small bit of code.
>
> a.equals(b) isn't much bigger than a == b.
>

a.equals(b) is much bigger.
In this case equals() is a method that can't have any assumptions made
about it until it is inspected.
a==b is a simple comparison, it's behaviour is simple and it's
performance characteristics are predictable.
for a==b {//something} would be unexpectedly slow if a and b were
large arrays that were being compared.

loops should look like loops because computability depends on it.

Russ Cox

unread,
Feb 11, 2011, 9:23:56 AM2/11/11
to Mark Summerfield, golang-nuts
> (1) According to the docs == and != are not supported for arrays or
>    structs. Could someone tell me the official rationale for this
>    please?

They're missing because we are uneasy about the
performance implications, but not because we are
necessarily against them. The conservative thing
to do is omit them until we're sure, because it is
easier to add than remove.

Arrays and structs are straightforward, if expensive.
The real question is what == on slices would mean.
I think a lot of programmers would expect it to
be "same length, same contents", but an equal number
might expect it to be "same pointer+len+cap".

> (2) In general comparing floats using == or != isn't considered to be
>    reliable, so the only real use is to check a denominator for
>    equality with 0.0 before doing division to avoid division by zero.

This is a popular meme but not true.
== on floats is entirely deterministic, as are the other operations.
It's true that if you are not doing a precise calculation you should
not be testing for precise equality, but that's dependent on the
context.

People who write true numerical algorithms care deeply about
having == when they want it and know exactly what comparison
to use when they don't. There's no "smart but fuzzy comparison"
that would magically work for all use cases. In any event, this
situation is not unique to Go. It is really about floating point math.
If == were really worthless IEEE 754 would not have defined it,
and it wouldn't be present in other languages either.

> m[k] = _

I updated the title on issue 561.
http://code.google.com/p/go/issues/detail?id=561

Russ

chris dollin

unread,
Feb 11, 2011, 9:49:48 AM2/11/11
to Jessta, Mark Summerfield, golang-nuts
On 11 February 2011 14:05, Jessta <jes...@jessta.id.au> wrote:
> On Fri, Feb 11, 2011 at 9:32 PM, chris dollin <ehog....@googlemail.com> wrote:
>>> Aside from the problem of deciding the best definition for what make
>>> two structs equal, the other problem is that this hides what might be
>>> an intensive operation(If the struct or array is quite large) inside a
>>> very small bit of code.
>>
>> a.equals(b) isn't much bigger than a == b.
>>
>
> a.equals(b) is much bigger.
> In this case equals() is a method that can't have any assumptions made
> about it until it is inspected.

Yes, once.

> a==b is a simple comparison, it's behaviour is simple and it's
> performance characteristics are predictable.

> for a==b {//something} would be unexpectedly slow if a and b were
> large arrays that were being compared.

No, it wouldn't: it would be /expectedly/ slow. If you want to
compare big things for equality, it can take a long time.

> loops should look like loops because computability depends on it.

Um ... what?

I think your argument boils down to "operators should be cheap",
and mine boils down to "operators should be expressive".

Steven

unread,
Feb 11, 2011, 9:49:57 AM2/11/11
to golan...@googlegroups.com

Go employs structural typing for unnamed types.
http://golang.org/doc/go_spec.html#Type_identity

This is the criteria for being able to compare two values:
The operands must be comparable; that is, the first operand must be
assignable to the type of the second operand, or vice versa.

Thus both comparisons would work, in the first case because the types
are identical, and thus assignable. In the second case, because one
type is unnamed and the underlying types are identical.

David Roundy

unread,
Feb 11, 2011, 10:28:09 AM2/11/11
to roger peppe, Mark Summerfield, golang-nuts
On Fri, Feb 11, 2011 at 12:49 AM, roger peppe <rogp...@gmail.com> wrote:
> On 11 February 2011 08:08, Mark Summerfield <li...@qtrac.plus.com> wrote:
>> (2) In general comparing floats using == or != isn't considered to be
>>    reliable, so the only real use is to check a denominator for
>>    equality with 0.0 before doing division to avoid division by zero.
>
> there are other uses too. for instance, you might use an out-of-range
> sentinel value and compare it for equality. also, if you've converted a
> float64 directly from an int32, it's non-lossy, so equality
> comparisons will work.

I'd generalize that floating point arithmetic is exact for any small
integers times reasonable powers of two. This isn't such an uncommon
scenario (think integrating from zero to one with a power of two
number of steps.

Also, equality checking is useful when you have a stable algorithm
that you want to converge to machine precision, e.g. if you were
forced to implement sqrt yourself.

> this could be useful, for instance, if you're writing an interpreter
> and using a single float64 representation for both floats and ints
> (quite a few languages do this).
>

--
David Roundy

Sonia Keys

unread,
Feb 11, 2011, 11:11:55 AM2/11/11
to golan...@googlegroups.com
On Friday, February 11, 2011 5:24:33 AM UTC-5, Jessta wrote:

the only issue is, what does:
a:= _  mean?

= _ would have a special meaning for map indexes no matter what, but if you wanted it to mean something in other cases, I think it would be great to have it assign the zero value for the type.  This would represent convenient new functionality in the case of structs and arrays.  (Of course this leaves _ = _ to consider...)

Russ Cox

unread,
Feb 11, 2011, 11:27:31 AM2/11/11
to golan...@googlegroups.com
>> the only issue is, what does:
>> a:= _  mean?
>
> = _ would have a special meaning for map indexes no matter what, but if you
> wanted it to mean something in other cases, I think it would be great to
> have it assign the zero value for the type.  This would represent convenient
> new functionality in the case of structs and arrays.  (Of course this leaves
> _ = _ to consider...)

Doing both of these would be a mistake:

var x []int
var m map[int]int

// identical
x[0] = _
x[0] = 0

// subtly different
m[0] = _
m[0] = 0

Russ

JONNALAGADDA Srinivas

unread,
Feb 11, 2011, 11:44:35 AM2/11/11
to golan...@googlegroups.com
On Friday, February 11, 2011 8:19:57 PM UTC+5:30, Steven wrote:

Go employs structural typing for unnamed types.
http://golang.org/doc/go_spec.html#Type_identity


        Touché!

                                                            Greetings,
                                                                    JS

Mark Summerfield

unread,
Feb 12, 2011, 2:37:31 AM2/12/11
to r...@golang.org, golang-nuts
On Fri, 11 Feb 2011 09:23:56 -0500
Russ Cox <r...@golang.org> wrote:
> > (1) According to the docs == and != are not supported for arrays or
> >    structs. Could someone tell me the official rationale for this
> >    please?
>
> They're missing because we are uneasy about the
> performance implications, but not because we are
> necessarily against them. The conservative thing
> to do is omit them until we're sure, because it is
> easier to add than remove.
>
> Arrays and structs are straightforward, if expensive.
> The real question is what == on slices would mean.
> I think a lot of programmers would expect it to
> be "same length, same contents", but an equal number
> might expect it to be "same pointer+len+cap".

I just wanted to know the reason, and now I do:-)

Personally, I'd rather have the convenience of having the operator---so
long as the documentation explains the performance implications I then
have the choice whether to use it. But with no operator I have no
choice!



> > (2) In general comparing floats using == or != isn't considered to
> > be reliable, so the only real use is to check a denominator for
> >    equality with 0.0 before doing division to avoid division by
> > zero.
>
> This is a popular meme but not true.
> == on floats is entirely deterministic, as are the other operations.
> It's true that if you are not doing a precise calculation you should
> not be testing for precise equality, but that's dependent on the
> context.
>
> People who write true numerical algorithms care deeply about
> having == when they want it and know exactly what comparison
> to use when they don't. There's no "smart but fuzzy comparison"
> that would magically work for all use cases. In any event, this
> situation is not unique to Go. It is really about floating point
> math. If == were really worthless IEEE 754 would not have defined it,
> and it wouldn't be present in other languages either.

[snip]

It is a popular meme because it is true---at least for those programmers
who don't know how IEEE-754 works and who aren't necessarily aware that,
e.g., .1 and .2 can't be represented exactly as floating-point numbers.
(I have a comp. sci background where this is year 1 stuff, but I have
met a *lot* of programmers who come from other fields.) I know it isn't
Go-specific and I agree that the current == is fine for floats---but it
isn't always intuitive:

package main
import "fmt"
func main() {
a := 0.0
b := 0.0
for i := 0; i < 10; i++ {
a += 0.1
if i % 2 == 0 {
b += 0.2
} else {
fmt.Printf("%-5t %g %g\n", a == b, a, b)
}
}
}

Produces:

true 0.2 0.2
true 0.4 0.4
false 0.6 0.6000000000000001
false 0.7999999999999999 0.8
false 0.9999999999999999 1

On my 64-bit Linux box.

Thanks!


--
Mark Summerfield, Qtrac Ltd, www.qtrac.eu
C++, Python, Qt, PyQt - training and consultancy

"Rapid GUI Programming with Python and Qt" - ISBN 0132354187
http://www.qtrac.eu/pyqtbook.html

bflm

unread,
Feb 12, 2011, 4:35:33 AM2/12/11
to golang-nuts
On Feb 12, 8:37 am, Mark Summerfield <l...@qtrac.plus.com> wrote:
> > > (2) In general comparing floats using == or != isn't considered to
> > > be reliable, so the only real use is to check a denominator for
> > >    equality with 0.0 before doing division to avoid division by
> > > zero.
>
> > This is a popular meme but not true.
> > == on floats is entirely deterministic, as are the other operations.
> > It's true that if you are not doing a precise calculation you should
> > not be testing for precise equality, but that's dependent on the
> > context.
>
Actually the output shows the opposite of your claim (the meme is
true). The == operator in your example *reliably* shows the last three
cases a and b are not equal (as can be seen in their text form). This
supports what Russ said before on the topic.

So the operator is perfectly OK. What's not OK is to expect that
without (exact) constant folding e.g. 'x + 0.1 + 0.1' == 'x + 0.2'.
There lies to problem, not in the equality operator. In this case the
unreliable part is - as you correctly pointed out - the limits in
representation of certain floating point numbers.

Russ Cox

unread,
Feb 12, 2011, 8:57:07 AM2/12/11
to Mark Summerfield, golang-nuts
It sounds like we mostly agree about the technical part of ==.

> package main
> import "fmt"
> func main() {
>    a := 0.0
>    b := 0.0
>    for i := 0; i < 10; i++ {
>        a += 0.1
>        if i % 2 == 0 {
>            b += 0.2
>        } else {
>            fmt.Printf("%-5t %g %g\n", a == b, a, b)
>        }
>    }
> }
>
> Produces:
>
> true  0.2 0.2
> true  0.4 0.4
> false 0.6 0.6000000000000001
> false 0.7999999999999999 0.8
> false 0.9999999999999999 1

One nice thing demonstrated by your program is that
we changed the default precision for printing a floating
point number in Go. Printing using fmt or strconv will
default to giving exactly enough digits to uniquely identify
the float64 or float32 argument.

That means, if two numbers print the same using
fmt.Print or fmt.Printf with %g or %v, then they are ==.
Contrapositively, if they are !=, they print differently.

This is a tiny thing but a huge win for understanding
the operations. In C your prints would have said

false 0.6 0.6
false 0.8 0.8
false 1 1

Russ

Reply all
Reply to author
Forward
0 new messages