dotnet / dotnet/fsharp

Incorrect result of Equals involving NaN in structural type nested in generic structural type

Open
#556 6 comments 0 reactions 0 assignees View on GitHub
Area-Library Feature Improvement
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.