BigNumbers in F# QuickStart Sample

Illustrates the basic use of the arbitrary precision classes: BigInteger, BigRational, BigFloat in F#.

View this sample in: C# Visual Basic IronPython

// Illustrates the use of the arbitrary precision number
// classes in Numerics.NET.

#light

open System

// The arbitrary precision types reside in the Numerics.NET
// namespace.
open Numerics.NET

// The license is verified at runtime. We're using a 30 day trial key here.
// For more information, see:
//     https://numerics.net/trial-key
let licensed = Numerics.NET.License.Verify("64542-18980-57619-62268")

// In this QuickStart sample, we'll use 5 different methods to compute 
// the value of pi, the ratio of the circumference to the diameter of
// a circle.

// The number of decimal digits.
let digits = 10000
// The equivalent number of binary digits, to account for round-off error:
let binaryDigits = (int)(8.0 + (float digits) * Math.Log(10.0, 2.0))

// First, create an AccuracyGoal for the number of digits we want.
// We'll add 5 extra digits to account for round-off error.
let goal = AccuracyGoal.Absolute(float (digits + 5))
printfn "Calculating %d digits of pi:" digits

// Create a stopwatch so we can time the results.
let sw = System.Diagnostics.Stopwatch()

//
// Method 1: Arctan formula
// 
// pi/4 = 88L(172) + 51L(239) + 32L(682) + 44L(5357) + 68L(12943)
// Where L(p) = Arctan(1/p)
// We will use big integer arithmetic for this.
// See the helper function Arctan later in this file.
printfn "Arctan formula using integer arithmetic:"

let IntegerArctanPi binaryDigits =
    let coefficients = [| 88; 51; 32; 44; 68 |]
    let arguments = [| 172; 239; 682; 5357; 12943 |]

    // Helper function to compute Arctan(1/p)
    let Arctan p binaryDigits =
        let rec ArctanRec result (power : BigInteger) k subtract =
            if (power.IsZero) then
                result
            else
                // Expressions involving big integers look exactly like any other arithmetic expression:
                // The kth term is (-1)^k 1/(2k+1) 1/p^2k.
                // So the power is 1/p^2 times the previous power.
                let newPower = power / (p * p)
                let term = newPower / BigInteger(2 * k + 1)
                // And we alternately add and subtract
                let newResult = if subtract then result - term else result + term
                ArctanRec newResult newPower (k+1) (not subtract)
        // We scale the result by a factor of 2^binaryDigits.
        // The first term is 1/p.
        let power = BigInteger(2) ** binaryDigits / p
        let result = ArctanRec power power 1 true
        // Scale the result.
        BigFloat.ScaleByPowerOfTwo(new BigFloat(result), -binaryDigits)

    let mutable pi = BigFloat.Zero
    for k in 0..4 do
        pi <- pi + coefficients.[k] * (Arctan arguments.[k] binaryDigits)
        printfn "Step %d: (%.3f seconds)" (k + 1) sw.Elapsed.TotalSeconds
    // The ScaleByPowerOfTwo is the fastest way to multiply
    // or divide by a power of two:
    BigFloat.ScaleByPowerOfTwo(pi, 2)

sw.Start()
IntegerArctanPi binaryDigits |> ignore
sw.Stop()

printfn "Total time: %.3f seconds." sw.Elapsed.TotalSeconds
printfn ""

//
// Method 2: Rational approximation
// 
// pi/2 = 1 + 1/3 + (1*2)/(3*5) + (1*2*3)/(3*5*7) + ...
//      = 1 + 1/3 * (1 + 2/5 * (1 + 3/7 * (1 + ...)))
// We gain 1 bit per iteration, so we know where to start.
printfn "Rational approximation using rational arithmetic."
let RationalPi binaryDigits =
    let rec RationalPiRec n an =
        match n with
        | 0 -> an
        | _ -> RationalPiRec (n-1) (new BigRational(n, 2 * n + 1) * an + 1)
    let an = RationalPiRec binaryDigits BigRational.Zero
    new BigFloat(2 * an, goal, RoundingMode.TowardsNearest)
    
printfn "This is very inefficient, so we only do up to 10,000 digits."
let rationalBinaryDigits = (int)(8.0 + 10000.0 * Math.Log(10.0, 2.0))
sw.Restart()
RationalPi rationalBinaryDigits |> ignore
sw.Stop()
printfn "Total time: %.3f seconds." sw.Elapsed.TotalSeconds
printfn ""

//
// Method 3: Arithmetic-Geometric mean
//
// By Salamin & Brent, based on discoveries by C.F.Gauss.
// See http://www.cs.miami.edu/~burt/manuscripts/gaussagm/agmagain-hyperref.pdf
printfn "Arithmetic-Geometric Mean:"
let ArithmeticGeometricMeanPi goal =
    let rec AGMPiRec k x1 x2 (S:BigFloat) =
        let aMean = BigFloat.ScaleByPowerOfTwo(x1 + x2, -1)
        let gMean = BigFloat.Sqrt(x1 * x2)
        let c = (aMean + gMean) * (aMean - gMean)
        // GetDecimalDigits returns the approximate number of digits in a number.
        // A negative return value means the number is less than 1.
        let correctionDigits = -c.GetDecimalDigits()
        printfn "Iteration %d: %.1f digits (%.3f seconds)" k correctionDigits sw.Elapsed.TotalSeconds
        if (correctionDigits >= float digits) then
            x1 * x1 / (1 - S)
        else
            AGMPiRec (k+1) aMean gMean (S + BigFloat.ScaleByPowerOfTwo(c, k))
    let x1 = BigFloat.Sqrt(BigFloat(2), goal, RoundingMode.TowardsNearest)
    let x2 = BigFloat.One
    let S = BigFloat(2)
    AGMPiRec 0 x1 x2 S

sw.Restart()
ArithmeticGeometricMeanPi goal |> ignore
sw.Stop()
printfn "Total time: %.3f seconds." sw.Elapsed.TotalSeconds
printfn ""

//
// Method 4: Borweins' quartic formula
//
// This algorithm quadruples the number of correct digits
// in each iteration.
// See http://en.wikipedia.org/wiki/Borwein's_algorithm
printfn "Quartic formula:"
let QuarticPi goal =
    let rec QuarticPiRec k a (y4 : BigFloat) =
        let qrt = BigFloat.Root(1 - y4, 4)
        let y = (1 - qrt) / (1 + qrt)
        let y2 = y * y
        let y3 = y * y2
        let y4 = y2 * y2
        let (daRaw : BigFloat) = (a * (BigFloat.ScaleByPowerOfTwo(y + y3, 2) + (6 * y2 + y4)) 
            - BigFloat.ScaleByPowerOfTwo(y + y2 + y3, 2 * k + 1))
        let da = daRaw.RestrictPrecision(goal, RoundingMode.TowardsNearest)
        let aNew = (a + da)
        let correctionDigits = -da.GetDecimalDigits()
        printfn "Iteration %d: %.1f digits (%.3f seconds)" k correctionDigits sw.Elapsed.TotalSeconds
        if (4.0 * correctionDigits < float digits) then
            QuarticPiRec (k+1) aNew y4
        else
            aNew
    let sqrt2 = BigFloat.Sqrt(BigFloat(2), goal, RoundingMode.TowardsNearest)
    let y = sqrt2 - BigFloat.One
    let a = BigFloat(6, goal) - BigFloat.ScaleByPowerOfTwo(sqrt2, 2)
    let invPi = QuarticPiRec 1 a (y ** 4)
    BigFloat.Inverse(invPi)
sw.Restart()
QuarticPi goal |> ignore
sw.Stop()
printfn "Total time: %.3f seconds." sw.Elapsed.TotalSeconds
printfn ""

// 
// Method 5: The built-in method
//
// The method used to compute pi internally is an order of magnitude
// faster than any of the above.
printfn "Built-in function:"
sw.Reset()
sw.Start()
let piBuiltIn = BigFloat.GetPi(goal)
sw.Stop()
printfn "Total time: %.3f seconds." sw.Elapsed.TotalSeconds
// The highest precision value of pi is cached, so
// getting pi to any precision up to that is super fast.
printfn "Built-in function (cached):"
sw.Reset()
sw.Start()
let piBuiltInCached = BigFloat.GetPi(goal)
sw.Stop()
printfn "Total time: %.3f seconds." sw.Elapsed.TotalSeconds

printf "Press Enter key to exit..."
Console.ReadLine() |> ignore