Search code examples
swift4normal-distribution

How can I return a float or double from normal distribution in Swift 4?


I am trying to return a float or a double from a normal distribution with mean = 0 and standard deviation = 4 in Swift4. The closest I can come to getting what I need is by using GameplayKit -> GKGaussianDistribution as implemented in the code below:

func generateForecast() {
    let gauss = GKGaussianDistribution(randomSource: self.source, mean: 0.0, deviation: 4.0)
    self.epsilon = gauss.nextInt()
}

My problem is when I call

gauss.nextInt()

I obviously get an integer. And when I try

gauss.nextUniform()

I get a number between -1 and 1.

Is there a fairly simple way to return a float or double from a normal distribution in Swift4 instead of an Int or a float between -1 and 1?

import AppKit
import PlaygroundSupport
import GameplayKit

let nibFile = NSNib.Name(rawValue:"MyView")
var topLevelObjects : NSArray?

Bundle.main.loadNibNamed(nibFile, owner:nil, topLevelObjects: &topLevelObjects)
let views = (topLevelObjects as! Array<Any>).filter { $0 is NSView }

// Present the view in Playground
PlaygroundPage.current.liveView = views[0] as! NSView

let s = 0.001
var auto_corr: [Int] = []

class Market {
    var numAgents: Int
    var traders: [Agent] = []
    var price: Double
    var epsilon: Int
    var priceHist: [Double] = []
    var returnHist: [Double] = []
    var returnRealHist: [Double] = []
    var logReturn: Double = 0
    var realReturn: Double = 0
    let source = GKRandomSource()

    init(numAgents: Int, price: Double, epsilon: Int) {
        self.numAgents = numAgents
        self.price = price
        self.epsilon = epsilon
        for _ in 1...numAgents {
            self.traders.append(Agent(phi: 1, theta: 1))
        }
    }

    func generateForecast() {
        let gauss = GKGaussianDistribution(randomSource: self.source, mean: 0.0, deviation: 4.0)
        self.epsilon = gauss.nextInt()
    }

}

Solution

  • The documentation for GKGaussianDistribution does not mention that it overrides nextUniform() from the base class so don't assume it will return normally distributed values for you:

    You can roll your own Gaussian Distribution using the Box-Muller Transformation:

    class MyGaussianDistribution {
        private let randomSource: GKRandomSource
        let mean: Float
        let deviation: Float
    
        init(randomSource: GKRandomSource, mean: Float, deviation: Float) {
            precondition(deviation >= 0)
            self.randomSource = randomSource
            self.mean = mean
            self.deviation = deviation
        }
    
        func nextFloat() -> Float {
            guard deviation > 0 else { return mean }
    
            let x1 = randomSource.nextUniform() // a random number between 0 and 1
            let x2 = randomSource.nextUniform() // a random number between 0 and 1
            let z1 = sqrt(-2 * log(x1)) * cos(2 * Float.pi * x2) // z1 is normally distributed
    
            // Convert z1 from the Standard Normal Distribution to our Normal Distribution
            return z1 * deviation + mean 
        }
    }
    

    I intentionally did not subclass it from GKRandomDistribution since there are other methods I need to override but are not relevant to this question.