// montecarlopi.go // description: Calculating pi by the Monte Carlo method // details: // implementations of Monte Carlo Algorithm for the calculating of Pi - [Monte Carlo method](https://en.wikipedia.org/wiki/Monte_Carlo_method) // time complexity: O(n) // space complexity: O(1) // author(s): [red_byte](https://github.com/i-redbyte), [Paul Leydier] (https://github.com/paul-leydier) // see montecarlopi_test.go package pi import ( "fmt" // Used for error formatting "math/rand" // Used for random number generation in Monte Carlo method "runtime" // Used to get information on available CPUs "time" // Used for seeding the random number generation ) func MonteCarloPi(randomPoints int) float64 { rnd := rand.New(rand.NewSource(time.Now().UnixNano())) inside := 0 for i := 0; i < randomPoints; i++ { x := rnd.Float64() y := rnd.Float64() if x*x+y*y <= 1 { inside += 1 } } pi := float64(inside) / float64(randomPoints) * 4 return pi } // MonteCarloPiConcurrent approximates the value of pi using the Monte Carlo method. // Unlike the MonteCarloPi function (first version), this implementation uses // goroutines and channels to parallelize the computation. // More details on the Monte Carlo method available at https://en.wikipedia.org/wiki/Monte_Carlo_method. // More details on goroutines parallelization available at https://go.dev/doc/effective_go#parallel. func MonteCarloPiConcurrent(n int) (float64, error) { numCPU := runtime.GOMAXPROCS(0) c := make(chan int, numCPU) pointsToDraw, err := splitInt(n, numCPU) // split the task in sub-tasks of approximately equal sizes if err != nil { return 0, err } // launch numCPU parallel tasks for _, p := range pointsToDraw { go drawPoints(p, c) } // collect the tasks results inside := 0 for i := 0; i < numCPU; i++ { inside += <-c } return float64(inside) / float64(n) * 4, nil } // drawPoints draws n random two-dimensional points in the interval [0, 1), [0, 1) and sends through c // the number of points which where within the circle of center 0 and radius 1 (unit circle) func drawPoints(n int, c chan<- int) { rnd := rand.New(rand.NewSource(time.Now().UnixNano())) inside := 0 for i := 0; i < n; i++ { x, y := rnd.Float64(), rnd.Float64() if x*x+y*y <= 1 { inside++ } } c <- inside } // splitInt takes an integer x and splits it within an integer slice of length n in the most uniform // way possible. // For example, splitInt(10, 3) will return []int{4, 3, 3}, nil func splitInt(x int, n int) ([]int, error) { if x < n { return nil, fmt.Errorf("x must be < n - given values are x=%d, n=%d", x, n) } split := make([]int, n) if x%n == 0 { for i := 0; i < n; i++ { split[i] = x / n } } else { limit := x % n for i := 0; i < limit; i++ { split[i] = x/n + 1 } for i := limit; i < n; i++ { split[i] = x / n } } return split, nil }