Skip to content

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

Open
@exercitusvir

Description

@exercitusvir

(Reposted from github.com/fsharp/fsharp)

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:

    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<GenericUnion<UnionWithFloat>>
    let comparableLeftNonGenericUnion = leftNonGenericUnion :> System.IComparable<NonGenericUnion>

    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:

    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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status

    In Progress

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions