The F# Power Pack includes an arbitrarily large Rational type called BigNum, which in turn uses the arbitrarily large BigInt type for the numerator and denominator. The BigInteger type has recently moved into .Net 4.0 under the System.Numerics namespace, It would be nice if BigNum could join it there.

What if you want a fixed size Rational implementation that can be used from any .Net language? The following is a sample F# implementation using Int64 for the numerator and denominator values:

open System
type Rational64 (numerator:Int64,denominator:Int64) =
do if denominator = 0L && numerator <> 0L then
raise (new DivideByZeroException())
let rec gcd x y =
match y with
| 0L -> x
| _ -> gcd y (x % y)
let norm =
let u = int64 (sign denominator) * numerator
let v = abs denominator
let d = gcd (abs u) v
if denominator <> 0L then u / d, v / d
else numerator, denominator
let numerator, denominator = norm
static let zero = Rational64(0L,1L)
static let Op f (a:Rational64,b:Rational64) =
let x,y = a.Numerator, a.Denominator
let u,v = b.Numerator, b.Denominator
f x y u v
static let Add = Op (fun x y u v -> new Rational64(x * v + u * y, y * v))
static let Sub = Op (fun x y u v -> new Rational64(x * v - u * y, y * v))
static let Mul = Op (fun x y u v -> new Rational64(x * u, y * v))
static let Div = Op (fun x y u v -> new Rational64(x * v, y * u))
static let Eq = Op (fun x y u v -> x * v = u * y)
static let Lt = Op (fun x y u v -> x * v < u * y)
static let Le = Op (fun x y u v -> x * v <= u * y)
static let Gt = Op (fun x y u v -> x * v > u * y)
static let Ge = Op (fun x y u v -> x * v >= u * y)
static let Compare (a:Rational64,b:Rational64) =
if Lt (a,b) then -1
else if Gt (a,b) then 1
else 0
new(numerator:Int64) = Rational64(numerator,1L)
member this.Numerator = numerator
member this.Denominator = denominator
static member Zero = zero
member this.ToDecimal() =
decimal this.Numerator / decimal this.Denominator
member this.ToDouble() =
double this.Numerator / double this.Denominator
override this.ToString() =
match numerator, denominator with
| n, 1L -> n.ToString()
| n, d -> sprintf "%d/%d" n d
static member ( + ) (a,b) = Add (a,b)
static member ( - ) (a,b) = Sub (a,b)
static member ( * ) (a,b) = Mul (a,b)
static member ( / ) (a,b) = Div (a,b)
static member op_Equality (a,b) = Eq (a,b)
static member op_Inequality (a,b) = not (Eq (a,b))
static member op_LessThan (a,b) = Lt (a,b)
static member op_LessThanOrEqual (a,b) = Le (a,b)
static member op_GreaterThan (a,b) = Gt (a,b)
static member op_GreaterThanOrEqual (a,b) = Ge (a,b)
interface IComparable with
member this.CompareTo (x) = Compare (this, x :?> Rational64)
static member Equals(a,b) = Eq(a,b)
override this.Equals x = this = (x :?> Rational64)

Finally specific for F# by implementing a NumericLiteralX module it is possible to declare Rational64 literals like so (Q for Quotient):

module NumericLiteralQ =
let FromZero () = Rational64(0L)
let FromOne () = Rational64(1L)
let FromInt32 (x) = Rational64(int64 x)
let FromInt64 (x:Int64) = Rational64(x)
module Test =
let x = 42Q

Rational64.fs (4.90 kb)

Rational64Test.cs (4.74 kb)