Incorrect result of Equals involving NaN in structural type nested in generic structural type
- Dominant language
- F#
- Stars
- 4.3k
- Forks
- 876
- Avg merge
- 4d 11h
- Merged PRs (30d)
- 131
Description
(Reposted from [github.com/fsharp/fsharp](https://github.com/fsharp/fsharp/issues/458#issuecomment-124862702))
I know that `nan = nan` and `nanf = nanf` both return `false`.
But `nan.Equals(nan)` and `nanf.Equals(nanf)` return `true`. These are intended results and this also applies when `nan` or `nanf` is contained in another structural type such as a union.
The .NET Guidelines also say that `Equals`, `GetHashCode` and `CompareTo` should be consistent. That is, two values that are equal should be equal as per `Equals`, should return the same hash and `CompareTo` should return `0`.
F# types adhere to this except for the following case when a `single`/`float32` or `double`/`float` that is NaN is a value of a structural type such as a union, which is nested in another structural type such as a union or record that has at least one type parameter.
Here is sample code that shows the inconsistent behavior:
``` F#
type UnionWithFloat = UnionWithFloat of float
type GenericUnion<'value> = GenericUnion of 'value
type NonGenericUnion = NonGenericUnion of UnionWithFloat
let leftGenericUnion = GenericUnion (UnionWithFloat nan)
let rightGenericUnion = GenericUnion (UnionWithFloat nan)
let leftNonGenericUnion = NonGenericUnion (UnionWithFloat nan)
let rightNonGenericUnion = NonGenericUnion (UnionWithFloat nan)
let comparableLeftGenericUnion = leftGenericUnion :> System.IComparable>
let comparableLeftNonGenericUnion = leftNonGenericUnion :> System.IComparable
let genericUnionsEqual = leftGenericUnion.Equals(rightGenericUnion) // false!
let nonGenericUnionsEqual = leftNonGenericUnion.Equals(rightNonGenericUnion) // true
let genericUnionsHashCodesEqual = leftGenericUnion.GetHashCode() = rightGenericUnion.GetHashCode() // true
let nonGenericUnionsHashCodesEqual = leftNonGenericUnion.GetHashCode() = rightNonGenericUnion.GetHashCode() //true
let genericUnionsComparisonEqual = comparableLeftGenericUnion.CompareTo(rightGenericUnion) = 0 // true
let nonGenericUnionsComparisonEqual = comparableLeftNonGenericUnion.CompareTo(rightNonGenericUnion) = 0 // true
```
The inconsistent result is the following line:
``` F#
let genericUnionsEqual = leftGenericUnion.Equals(rightGenericUnion) // false!
```
All the other ways of comparison correctly return `true`.
Note that this also applies to `single`/`float32` and records. And you only see the inconsistent results when the type is generic (`GenericUnion` here), which nests another type (`UnionWithFloat` here). I have not tested this for more types or constellations. This was tested with F# 3.1 if this makes a difference.
I would be nice if you could fix this as it had me scratching my head for some time until I could pin down when this happens. It would also be nice if you could verify that the results are consistent for all possible types in all constellations in F#, whether generic and/or nested.
Contributor guide
Assessment
This issue has not been assessed yet.