FSharpPlus


Numeric functions

This library comes with some additional numeric functions and constants.

These functions work over many numeric types

let qr0  = divRem 7  3  //val qr0 : int * int = (2, 1)
let qr1  = divRem 7I 3I //val qr1 : System.Numerics.BigInteger * System.Numerics.BigInteger = (2, 1)
let qr2  = divRem 7. 3. //val qr2 : float * float = (2.333333333, 0.0) -> using default method.

Numeric constants

Apart from typical math constants, bounded types comes with minValue and maxValue constants.

Here's an example how can this be used to implement an efficient findMin function

let inline findMin (lst: 'a list) =
    let rec loop acc = function
        | [] -> acc
        | x::_ when x = minValue -> x
        | x::xs -> loop (if x < acc then x else acc) xs
    loop maxValue lst
    
let minInt  = findMin [1;0;12;2]
let minUInt = findMin [1u;0u;12u;2u]  // loops only twice

Generic operations over numeric types

Writing code that is generic over different numeric types can be really tedious in F#.

Using this library it becomes an easy task, but it's important to understand the numeric abstractions and its limitations.

In order to have a reasonable type inference over generic types we need strict operations.

For example the F# definition of (+) can take 2 different types, this makes possible to interact with some .NET types that have defined the (+) operator in a very arbitrary way.

For instance you can add a float to a DateTime with the (+) operator, and that float will be interpreted as seconds.

By opening the FSharpPlus.Math.Generic namespace this will no longer be possible, because that's the tradeoff in order to get decent type inference.

Generic number literals

Numbers with a G suffix are generics.

open FSharpPlus.Math.Generic

let res5Int  : int    = 5G
let res5UInt : uint32 = 5G

Often you need to define generic constants when defining generic functions. Since there is no way to define generic decimal literals in F# at the moment of writing this, we can use divisions:

let inline areaOfCircle radio =
    let pi = 
        314159265358979323846264338G 
                    / 
        100000000000000000000000000G
    pi * radio * radio

let area1 = areaOfCircle 5.
let area2 = areaOfCircle 5.0f
let area3 = areaOfCircle 5.0M

Defining custom types, support generic operations

type Vector2d<'T> = Vector2d of 'T * 'T  with
    static member inline (+) (Vector2d(a:'t, b:'t), Vector2d(c:'t, d:'t)) = Vector2d (((a + c):'t), ((b + d):'t))
    static member inline (-) (Vector2d(a:'t, b:'t), Vector2d(c:'t, d:'t)) = Vector2d (((a - c):'t), ((b - d):'t))
    static member inline (*) (Vector2d(a:'t, b:'t), Vector2d(c:'t, d:'t)) = Vector2d (((a * c):'t), ((b * d):'t))
    static member        Return x                               = Vector2d (x, x)
    static member        Map(Vector2d(x, y), f)                 = Vector2d (f x, f y)
    static member inline FromBigInt x = let y = fromBigInt x in Vector2d (y, y)

Note we don't define overloads for adding a vector to a number

Why? Apart from being tedious they will break math operators strictness

so we will have problems type inferencing generic functions.

OK, but then how to add (subtract, multiply) to a number?

Option 1, explicitely 'lift' the number.

Requires Return and ( + , - , * )

let x1  = Vector2d (32,5) + result 7
let x1' = result 7 + Vector2d (32,5)

Option 2, use Generic Numbers

Requires FromBigInt and (+,-,*,/)

open FSharpPlus.Math.Generic
let x2  = Vector2d (32,5) + 7G
let x2' = 7G + Vector2d (32,5)

Option 3, use Applicative Math Operators Requires only Map

open FSharpPlus.Math.Applicative
let x3 = Vector2d (32,5) .+ 7
let x3' = 7 +. Vector2d (32,5)

Integrate with 3rd party libraries

We may use types defined in other libraries, let's suppose we have this type Ratio defined somewhere.

type Ratio =
    struct
        val Numerator   : bigint
        val Denominator : bigint
        new (numerator: bigint, denominator: bigint) = {Numerator = numerator; Denominator = denominator}
    end
    override this.ToString() = this.Numerator.ToString() + " % " + this.Denominator.ToString()

let ratio (a:bigint) (b:bigint) :Ratio =
    if b = 0I then failwith "Ratio.%: zero denominator"
    let a, b = if b < 0I then (-a, -b) else (a, b)
    let gcd = gcd a b
    Ratio (a / gcd, b / gcd)

let Ratio (x,y) = x </ratio/> y

type Ratio with
    static member inline (/) (a:Ratio, b:Ratio) = (a.Numerator * b.Denominator) </ratio/> (a.Denominator * b.Numerator)                                              
    static member inline (+) (a:Ratio, b:Ratio) = (a.Numerator * b.Denominator + b.Numerator * a.Denominator) </ratio/> (a.Denominator * b.Denominator)
    static member inline (-) (a:Ratio, b:Ratio) = (a.Numerator * b.Denominator - b.Numerator * a.Denominator) </ratio/> (a.Denominator * b.Denominator)
    static member inline (*) (a:Ratio, b:Ratio) = (a.Numerator * b.Numerator) </ratio/> (a.Denominator * b.Denominator)

    static member inline Abs        (r:Ratio) = (abs    r.Numerator) </ratio/> r.Denominator
    static member inline Signum     (r:Ratio) = (signum r.Numerator) </ratio/> 1I
    static member inline FromBigInt (x:bigint) = fromBigInt x </ratio/> 1I
    static member inline (~-)       (r:Ratio) = -(r.Numerator) </ratio/> r.Denominator

Since most Rational implementations have Numerator and Denominator defined we can just use our generic functions on it:

let some3_2 = trySqrt (Ratio(9I, 4I))

Example: creating a polymorphic quadratic function

The quadratic function has different results depending on which domain it operates.

For example for real numbers it can have 0 or 2 solutions (arguably also 1 that is a double solution).

But for complex numbers it always has 2 solutions.

open FSharpPlus.Math.Generic

let inline quadratic a b c =
    let root1 = ( -b + sqrt (  b * b - 4G * a * c) )  / (2G * a)
    let root2 = ( -b - sqrt (  b * b - 4G * a * c) )  / (2G * a)
    (root1,root2)


let noRes  = quadratic 2.0  3G 9G
// val noRes : float * float = (nan, nan)

let res30_15  = quadratic 2.0  -3G -9G
// val res30_15 : float * float = (3.0, -1.5)

let res30_15f = quadratic 2.0f -3G -9G
// val res30_15f : float32 * float32 = (3.0f, -1.5f)

let resCmplx:System.Numerics.Complex * _ = quadratic 2G -3G 9G
// val resCmplx : System.Numerics.Complex * System.Numerics.Complex = ((0.75, -1.98431348329844), (0.75, 1.98431348329844))

let res30_15r:Ratio * _ = quadratic 2G -3G -9G
// val res30_15r : Ratio * Ratio = (3 % 1, -3 % 2)
namespace FSharpPlus
val qr0 : int * int
val divRem : dividend:'Num -> divisor:'Num -> 'Num * 'Num (requires member DivRem)
val qr1 : System.Numerics.BigInteger * System.Numerics.BigInteger
val qr2 : float * float
val findMin : lst:'a list -> 'a (requires member MinValue and member MaxValue and comparison)
val lst : 'a list (requires member MinValue and member MaxValue and comparison)
type 'T list = List<'T>
val loop : ('a -> 'a list -> 'a) (requires member MinValue and member MaxValue and comparison)
val acc : 'a (requires member MinValue and member MaxValue and comparison)
val x : 'a (requires member MinValue and member MaxValue and comparison)
val minValue<'Num (requires member MinValue)> : 'Num (requires member MinValue)
val xs : 'a list (requires member MinValue and member MaxValue and comparison)
val maxValue<'Num (requires member MaxValue)> : 'Num (requires member MaxValue)
val minInt : int
val minUInt : uint32
namespace FSharpPlus.Math
module Generic

from FSharpPlus.Math
val res5Int : int
Multiple items
val int : value:'T -> int (requires member op_Explicit)

--------------------
type int = int32

--------------------
type int<'Measure> = int
val res5UInt : uint32
Multiple items
val uint32 : value:'T -> uint32 (requires member op_Explicit)

--------------------
type uint32 = System.UInt32
val areaOfCircle : radio:'a -> 'a (requires member ( * ) and member ( / ) and member FromBigInt)
val radio : 'a (requires member ( * ) and member ( / ) and member FromBigInt)
val pi : 'a (requires member ( * ) and member ( / ) and member FromBigInt)
val area1 : float
val area2 : float32
val area3 : decimal
Multiple items
union case Vector2d.Vector2d: 'T * 'T -> Vector2d<'T>

--------------------
type Vector2d<'T> =
  | Vector2d of 'T * 'T
    static member FromBigInt : x:bigint -> Vector2d<'a> (requires member FromBigInt)
    static member Map : Vector2d<'a> * f:('a -> 'b) -> Vector2d<'b>
    static member Return : x:'a -> Vector2d<'a>
    static member ( + ) : Vector2d<'t> * Vector2d<'t> -> Vector2d<'t> (requires member ( + ))
    static member ( * ) : Vector2d<'t> * Vector2d<'t> -> Vector2d<'t> (requires member ( * ))
    static member ( - ) : Vector2d<'t> * Vector2d<'t> -> Vector2d<'t> (requires member ( - ))
val a : 't (requires member ( + ))
val b : 't (requires member ( + ))
val c : 't (requires member ( + ))
val d : 't (requires member ( + ))
val a : 't (requires member ( - ))
val b : 't (requires member ( - ))
val c : 't (requires member ( - ))
val d : 't (requires member ( - ))
val a : 't (requires member ( * ))
val b : 't (requires member ( * ))
val c : 't (requires member ( * ))
val d : 't (requires member ( * ))
val x : 'a
Multiple items
module Map

from FSharpPlus

--------------------
module Map

from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> =
  interface IReadOnlyDictionary<'Key,'Value>
  interface IReadOnlyCollection<KeyValuePair<'Key,'Value>>
  interface IEnumerable
  interface IComparable
  interface IEnumerable<KeyValuePair<'Key,'Value>>
  interface ICollection<KeyValuePair<'Key,'Value>>
  interface IDictionary<'Key,'Value>
  new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
  member Add : key:'Key * value:'Value -> Map<'Key,'Value>
  member ContainsKey : key:'Key -> bool
  ...

--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
val y : 'a
val f : ('a -> 'b)
val x : bigint
val y : 'a (requires member FromBigInt)
val fromBigInt : x:bigint -> 'Num (requires member FromBigInt)
val x1 : Vector2d<int>
val result : x:'T -> 'Functor<'T> (requires member Return)
val x1' : Vector2d<int>
val x2 : Vector2d<int>
val x2' : Vector2d<int>
module Applicative

from FSharpPlus.Math
val x3 : Vector2d<int>
val x3' : Vector2d<int>
Ratio.Numerator: bigint
type bigint = System.Numerics.BigInteger
Ratio.Denominator: bigint
val numerator : bigint
val denominator : bigint
val this : inref<Ratio>
val ratio : a:bigint -> b:bigint -> Ratio
val a : bigint
val b : bigint
Multiple items
type Ratio =
  struct
    new : numerator:bigint * denominator:bigint -> Ratio
    val Numerator: bigint
    val Denominator: bigint
    override ToString : unit -> string
    static member Abs : r:Ratio -> Ratio
    static member FromBigInt : x:bigint -> Ratio
    static member Signum : r:Ratio -> Ratio
    static member ( + ) : a:Ratio * b:Ratio -> Ratio
    static member ( / ) : a:Ratio * b:Ratio -> Ratio
    static member ( * ) : a:Ratio * b:Ratio -> Ratio
    ...
  end

--------------------
Ratio ()
new : numerator:bigint * denominator:bigint -> Ratio
val failwith : message:string -> 'T
val gcd : bigint
Multiple items
val Ratio : x:bigint * y:bigint -> Ratio

--------------------
type Ratio =
  struct
    new : numerator:bigint * denominator:bigint -> Ratio
    val Numerator: bigint
    val Denominator: bigint
    override ToString : unit -> string
    static member Abs : r:Ratio -> Ratio
    static member FromBigInt : x:bigint -> Ratio
    static member Signum : r:Ratio -> Ratio
    static member ( + ) : a:Ratio * b:Ratio -> Ratio
    static member ( / ) : a:Ratio * b:Ratio -> Ratio
    static member ( * ) : a:Ratio * b:Ratio -> Ratio
    ...
  end

--------------------
Ratio ()
new : numerator:bigint * denominator:bigint -> Ratio
val y : bigint
val a : Ratio
val b : Ratio
val r : Ratio
val abs : value:'Num -> 'Num (requires member Abs)
val signum : value:'Num -> 'Num (requires member Signum)
val some3_2 : Ratio option
val trySqrt : x:'a -> 'a option (requires member TrySqrt)
val quadratic : a:'a -> b:'a -> c:'a -> 'a * 'a (requires member FromInt32 and member ( * ) and member Sqrt and member ( ~- ) and member ( / ) and member ( + ) and member ( - ))
val a : 'a (requires member FromInt32 and member ( * ) and member Sqrt and member ( ~- ) and member ( / ) and member ( + ) and member ( - ))
val b : 'a (requires member FromInt32 and member ( * ) and member Sqrt and member ( ~- ) and member ( / ) and member ( + ) and member ( - ))
val c : 'a (requires member FromInt32 and member ( * ) and member Sqrt and member ( ~- ) and member ( / ) and member ( + ) and member ( - ))
val root1 : 'a (requires member FromInt32 and member ( * ) and member Sqrt and member ( ~- ) and member ( / ) and member ( + ) and member ( - ))
val sqrt : x:'b -> 'b (requires member Sqrt)
val root2 : 'a (requires member FromInt32 and member ( * ) and member Sqrt and member ( ~- ) and member ( / ) and member ( + ) and member ( - ))
val noRes : float * float
val res30_15 : float * float
val res30_15f : float32 * float32
val resCmplx : System.Numerics.Complex * System.Numerics.Complex
namespace System
namespace System.Numerics
Multiple items
type Complex =
  struct
    new : real:float * imaginary:float -> Complex
    member Equals : obj:obj -> bool + 1 overload
    member GetHashCode : unit -> int
    member Imaginary : float
    member Magnitude : float
    member Phase : float
    member Real : float
    member ToString : unit -> string + 3 overloads
    static val Zero : Complex
    static val One : Complex
    ...
  end

--------------------
System.Numerics.Complex ()
System.Numerics.Complex(real: float, imaginary: float) : System.Numerics.Complex
val res30_15r : Ratio * Ratio