`compare` on floats is not an order, and a NaN key makes `Dict` lose entries it still lists
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 48
- Forks
- 14
- Avg merge
- 4h 14m
- Merged PRs (30d)
- 1
Description
Repository: gren-lang/core
Found against: gren 0.6.6, gren-lang/core 7.4.2, node 22
Summary
_Utils_cmp answers GT for every comparison involving NaN, in both
directions and against itself. So compare on Float is neither antisymmetric
nor consistent with ==:
nan = 0 / 0
nan == nan --> False
compare nan nan --> GT -- but == said they are not equal
compare 1.0 nan --> GT
compare nan 1.0 --> GT -- and so did the other direction
Dict and Set are ordered structures built on compare alone. An
intransitive compare does not raise there — it silently puts a key where
lookups will not find it, and takes existing keys down with it.
Reproduction
module Case exposing (results)
import Dict
nan : Float
nan =
0 / 0
d0 : Dict.Dict Float String
d0 =
Dict.empty
|> Dict.set 1.0 "one"
|> Dict.set 2.0 "two"
|> Dict.set 3.0 "three"
d1 : Dict.Dict Float String
d1 =
Dict.set nan "nan" d0
| expression | result | expected |
|---|---|---|
Dict.count d1 |
4 |
4 |
Dict.keys d1 |
1,2,3,NaN |
1,2,3,NaN |
Dict.get 1.0 d1 |
Just "one" |
Just "one" |
Dict.get 2.0 d1 |
Just "two" |
Just "two" |
Dict.get 3.0 d1 |
Nothing |
Just "three" |
Dict.get nan d1 |
Nothing |
Just "nan" |
3.0 was inserted before the NaN, is still in keys, is still counted, and can
no longer be retrieved. Nothing raises and nothing warns. The dictionary
reports a size that does not match the number of keys you can reach.
Cause
core/src/Gren/Kernel/Utils.js:
function _Utils_cmp(x, y) {
if (typeof x !== "object") {
return x === y ? /*EQ*/ 0 : x < y ? /*LT*/ -1 : /*GT*/ 1;
}
...
For NaN, x === y is false and x < y is false, so the expression falls
through to GT — whichever operand the NaN is, and even when both are. The
final else is standing in for "greater than" when what actually happened is
"unordered", a case IEEE 754 has and this encoding does not.
Dict.set then walks the red-black tree comparing against the NaN node, where
every branch decision is GT regardless of the key being searched for, so a
whole subtree becomes unreachable.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reproducing the Float and Dict examples, then inspect _Utils_cmp in core/src/Gren/Kernel/Utils.js. Trace how its comparison result drives Dict tree lookups; done means NaN comparisons have consistent behavior and inserted keys remain retrievable with the expected count and keys.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100