1. 15
utcc.utoronto.ca
1.

2. 11

Meh using floating point as keys for maps in any language is just asking for trouble, it’s not just NaN

I don’t think there’s any real use case for it

I’d say clear() is good for clarity, and that’s it

1. 4

Oh? Technically, JavaScript and Lua arrays are maps with floats for keys. 😈 So is a database index on a float column.

I’m sure people in the scientific-programming community put floats in maps too. (Particularly ordered maps, where you can do range searches.)

There’s nothing magically evil about floats or floating-point equality. It’s all precisely specified and repeatable. The problems with equality and round off error mostly occur when you try to round-trip non-integral floats to ASCII.

1. 2

Hm I’m not convinced by those examples. I’ll give a practical rebuttal and a theoretical one …

Challenge: show me some “good, production” code that actually relies on non-integer floats as map keys, which implies equality. You can do it in Python and C++, and apparently Go, but it should be avoided for the same reason that exact float equality should be avoided.

I’ve never seen any code that uses this to good effect (doesn’t mean it doesn’t exist, hence the challenge)

https://www.boost.org/doc/libs/1_78_0/libs/math/doc/html/math_toolkit/float_comparison.html

https://bitbashing.io/comparing-floats.html

In JavaScript and Lua code, you can rely on the fact that there’s a subset of floats that behave like integers, and are closed with respect to addition / subtraction / multiplication. But there’s no reason to use that in languages that have integers.

(One contrived example I can MAYBE think of is if you wanted a log-base-2-spaced histogram with buckets `... 0.25, 0.5, 1, 2, 4 ...` but there’s no reason you couldn’t do that with integers either – the integer bucket would be the exponent)

The other answer is: ordering is useful, and different than equality. Databases make indices for range queries, but exact quality has the same problems:

https://dev.mysql.com/doc/refman/8.0/en/problems-with-float.html

Round off also occurs at the ends of the float range (numbers with absolute values that are very big and very small) – you don’t have to do any serialization to encounter that problem.

Ordered maps that support range queries are very different data structures than Go’s `map`, which is a hash table.

This post gives some good intuition, e.g. in the range 2^24 and 2^25, you can only represent EVEN integers with a `float`. (With a double that becomes 2^53)

https://blog.reverberate.org/2014/09/what-every-computer-programmer-should.html

edit: Another reason to avoid hashing floats is that there are bit representations for positive and negative zero. Maybe Go and Python take care of that for you, but it’s also probably slower than using an integer. I think with C++ that problem is yours.

https://stackoverflow.com/questions/5095968/does-float-have-a-negative-zero-0f

1. 1

While I don’t think they are common I can totally imagine lookup tables using floats. Simply not implementing them would make floats the odd one out of primitive types supported as keys.

What’s also worth considering is that Go frequently uses the map type was a base for encoding data to serialization. So when you skip that you would have to implement encoding yourself.

So I agree this is not the most common use case but there are implications also cause in Go it’s a built-in type.

2. 1

Yeah, scientific computing seems like a place where floats as keys would be pretty common.

Also for JavaScript, isn’t it because JS doesn’t really have “integers”? IIRC it’s all IEEE floating-point for numbers in JS, with no separate integral type.

1. 1

Yeah, scientific computing seems like a place where floats as keys would be pretty common.

I would repeat my challenge above, because I don’t think this operation is useful: show me some good code that uses it :) I haven’t seen it.

1. 1

I’m not going to play this game, because you can easily just declare any given sample as not “good, production” code. Though you will be throwing out literally every line of JavaScript ever written, since its `Number` type is actually an IEEE double.

Regardless of whether it lives up to your standards, though, I believe there likely is a lot of explicit `float`-indexed code out there in the scientific world.

1. 0

OK, show me ANY code that uses non-integer floats as keys? What is it used for?

I believe there likely is a lot of explicit float-indexed code out there in the scientific world

I disagree, such data structures don’t have use cases and aren’t common, based on the limited code I’ve worked with

Interested in counterexamples (not to be disagreeable, mainly because I’m designing a language with floats, ints, and dicts)

1. 1

A quick search turns up plenty of people asking and sharing information about how to do this.

(snip the one SO question that wasn’t about keys)

But I’m not going to share all the SO copy/paste spam blogs that also came up in the search result).

I understand that you don’t think it’s a good idea to use floats as keys. But the evidence seems pretty solid that out in the real world are many people writing code that doesn’t conform to what you think is a good idea.

1. 2

The float there is the value of the dict entry, not the key

There’s also no code there – it’s a question about dicts and floats

1. 1

“python dict float key” turns up results for me which do clearly seem to be talking about floats as keys. As do other permutations of keywords.

Moving on now.

2. 1

JS object and array keys are all strings.

3. 2

TIL it’s actually safe to mutate a map in go while iterating over it; I’d always avoided doing that out of an assumption that it would do Bad Things.

1. 3

From the spec:

The iteration order over maps is not specified and is not guaranteed to be the same from one iteration to the next. If a map entry that has not yet been reached is removed during iteration, the corresponding iteration value will not be produced. If a map entry is created during iteration, that entry may be produced during the iteration or may be skipped. The choice may vary for each entry created and from one iteration to the next. If the map is nil, the number of iterations is 0.

1. 2

Yeah, I dug it out of the spec after I saw this to confirm; more or less what you get by just walking over the internal array in the hash table, but I know this causes problems in some other implementations.

2. 1

If I wanted to clear a map, I’d reassign it like `m := map[key]value{}` rather that delete keys one by one. But I suppose that’s different because you could have other variables referring to the original map.

1. 1

There’s a performance trade-off here in that reassigning does more heap allocation. I tend to do this too most of the time, but I could see clearing it instead as an optimization to reduce pressure on the gc.

1. 1

Yeah initially I was going to say the compiler ought to just recognize these cases and clear it, similarly to how it optimized string/bytes conversions in certain cases rather than requiring the programmer to be explicit about it. But then I realized it can’t do that because maps are reference types.

2. 1

So maps currently don’t have an equality constraint on key types, could the new generics/constraints machinery be used for that?

Putting things that can’t be compared for equality into maps seems like begging for trouble.

1. 1

Maps already do require the key to be comparable, but floats are comparable. It’s only NaN values that are not (as per the IEEE754 spec), and making floats invalid to store in a map would be throwing the baby out with the bathwater. (Not that the use cases for doing that are common, but they exist.)

1. 2

Eh, more broadly than the NaN issue, relying on equality of floats is usually a bad idea due to precision issues. Using floats as map keys seems like asking for trouble.

1. 1

“Comparable” is jargon in the Go spec, and `comparable` is a builtin generic constraint in Go. Unfortunately, the definition in the spec and the constraint don’t match. See https://github.com/golang/go/issues/56548 but the long and short is that you’re allowed to use interfaces as key types, but it doesn’t work with generics yet.