Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a concise, general method of property testing against nan values in F#?

I am doing some property testing in F# using FsCheck. I therefore wish to guarantee that certain conditions always hold, regardless of input arguments.

Consider I define a trivial identity function for float values.

let floatId (x : float) = x

I then define a test of this function which I know should always hold:

let ``floatId returns input float`` x = floatId x = x

This is a trivial test, I am just checking that calling my float identity function returns the same as the input float.

I then plug this function into FsCheck:

Check.Quick ``floatId returns input float``

Unfortunately, this property test fails!

Falsifiable, after 21 tests (0 shrinks) (StdGen (1872424299,296201373)):
Original: 
nan

Of course, looking back, it was pretty obvious this was going to happen, we know that nan <> nan.

Due to structural comparison in F#, this can plague (slightly) more complex test cases involving collections too.

If I design a similar function for float lists:

let listFloatId (lst : float list) = lst

let ``listFloatId returns input float list`` lst = listFloatId lst = lst
Falsifiable, after 6 tests (3 shrinks) (StdGen (1874889363,296201373)):
Original:
[nan; 2.0; 2.25; 4.940656458e-324]
Shrunk:
[nan]

Same problem again!


Obviously I can engineer around this problem by creating my own equality testing functions, that's fine for float values but it becomes more complex to extend to collections like list since I have to start using List.forall2 with my custom equality function and generally specialising my code to each individual collection type.

Is there a general way of solving this problem in F#?

like image 911
TheInnerLight Avatar asked Sep 06 '16 11:09

TheInnerLight


1 Answers

You can solve this problem using the LanguagePrimitives.GenericEqualityER function. This checks equality using Equivalance Relation semantics. This function actually sites the specific example of comparing [nan] lists.

Test cases can be defined like this:

let ``ER : floatId returns input float`` x = LanguagePrimitives.GenericEqualityER (floatId x)  x

let ``ER : listFloatId returns input float list`` lst = LanguagePrimitives.GenericEqualityER (listFloatId lst)  lst

This time:

Ok, passed 100 tests.
Ok, passed 100 tests.

(I am asking, and answering, this question because the above property was raised in the FSharp Software Foundation Slack channel and I thought it would be useful to have this solution on record. I can find almost no mention of this function online beyond the documentation on the LanguagePrimitives module).

like image 136
TheInnerLight Avatar answered Sep 26 '22 15:09

TheInnerLight