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)
<summary>Divides one number by another, returns a tuple with the result and the remainder.</summary>
<category index="22">Numerics</category>
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)
'a
type 'T list = List<'T>
val loop: acc: '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)
<summary>The smallest possible value.</summary>
<category index="22">Numerics</category>
val xs: 'a list (requires member MinValue and member MaxValue and comparison)
val maxValue<'Num (requires member MaxValue)> : 'Num (requires member MaxValue)
<summary>The largest possible value.</summary>
<category index="22">Numerics</category>
val minInt: int
val minUInt: uint32
namespace FSharpPlus.Math
module Generic from FSharpPlus.Math
<summary> Generic numbers, functions and operators. By opening this module some common operators become restricted, like (+) to 'T-&gt;'T-&gt;'T </summary>
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

--------------------
type uint32<'Measure> = uint<'Measure>
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
'T
Multiple items
union case Vector2d.Vector2d: 'T * 'T -> Vector2d<'T>

--------------------
type Vector2d<'T> = | Vector2d of 'T * 'T 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 (-)) 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>
val a: 't (requires member ``+``)
't
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
<summary> Additional operations on Map&lt;'Key, 'Value&gt; </summary>

--------------------
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 Change: key: 'Key * f: ('Value option -> 'Value option) -> Map<'Key,'Value> ...

--------------------
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)
<summary>Converts from BigInteger to the inferred destination type.</summary>
<category index="22">Numerics</category>
val x1: Vector2d<int>
val result: x: 'T -> 'Functor<'T> (requires member Return)
<summary> Lifts a value into a Functor. Same as return in Computation Expressions. </summary>
<category index="2">Applicative</category>
val x1': Vector2d<int>
val x2: Vector2d<int>
val x2': Vector2d<int>
module Applicative from FSharpPlus.Math
<summary>Math Operators ready to use over Applicative Functors.</summary>
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
[<Struct>] type Ratio = new: numerator: bigint * denominator: bigint -> Ratio val Numerator: bigint val Denominator: bigint override ToString: unit -> string static member ( * ) : a: Ratio * b: Ratio -> Ratio static member (+) : a: Ratio * b: Ratio -> Ratio static member (-) : a: Ratio * b: Ratio -> Ratio static member (/) : a: Ratio * b: Ratio -> Ratio static member Abs: r: Ratio -> Ratio static member FromBigInt: x: bigint -> Ratio ...

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

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

--------------------
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)
<summary> Gets the absolute value of the given number. <para /> Rule: signum x * abs x = x </summary>
<category index="22">Numerics</category>
<param name="value">The input value.</param>
<returns>The absolute value of the input.</returns>
val signum: value: 'Num -> 'Num (requires member Signum)
<summary>Sign of the given number <para /> Rule: signum x * abs x = x </summary>
<category index="22">Numerics</category>
<param name="value">The input value.</param>
<returns>-1, 0, or 1 depending on the sign of the input.</returns>
val some3_2: Ratio option
val trySqrt: x: 'a -> 'a option (requires member TrySqrt)
<summary>Square root of a number of any type. Returns None if there is no square root.</summary>
<category index="22">Numerics</category>
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)
<summary>Square root of a number of any type. Throws an exception if there is no square root.</summary>
<category index="22">Numerics</category>
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
[<Struct>] type Complex = new: real: float * imaginary: float -> unit member Equals: value: Complex -> bool + 1 overload member GetHashCode: unit -> int member ToString: unit -> string + 3 overloads member TryFormat: destination: Span<char> * charsWritten: byref<int> * format: ReadOnlySpan<char> * provider: IFormatProvider -> bool static member ( * ) : left: float * right: Complex -> Complex + 2 overloads static member (+) : left: float * right: Complex -> Complex + 2 overloads static member (-) : left: float * right: Complex -> Complex + 2 overloads static member (/) : left: float * right: Complex -> Complex + 2 overloads static member (<>) : left: Complex * right: Complex -> bool ...
<summary>Represents a complex number.</summary>

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