Skip to content

Zenhackers

  • Home
September 1, 2012September 17, 2012 Rouan van DalenSoftware Development

Almost PI

pi

While doing some light reading on the “interweb” I came across the following interesting interview question:

Given that Pi can be estimated using the function
4 * (1 – 1/3 + 1/5 – 1/7 + …) with more terms giving greater accuracy, write a function that calculates Pi to an accuracy of 5 decimal places.

What I think is great about this interview question is that is not about maths, but about how to translate the given mathematical formula into code.

I came up with 2 Haskell solutions for the problem, the first a very quick and naive solution, and the second solution is a bit clearer and performs better.

The first solution is as follows:

pi = 4.0 * (r 1.0 3 50000000 0)
 
r startVal denominator numOfPlacesMax currentNumOfPlaces =
   if currentNumOfPlaces > numOfPlacesMax
      then startVal
      else startVal - (1 / denominator) + (r (1 / (denominator + 2)) (denominator + 4) numOfPlacesMax (currentNumOfPlaces + 1))

pi = 4.0 * (r 1.0 3 50000000 0) r startVal denominator numOfPlacesMax currentNumOfPlaces = if currentNumOfPlaces > numOfPlacesMax then startVal else startVal - (1 / denominator) + (r (1 / (denominator + 2)) (denominator + 4) numOfPlacesMax (currentNumOfPlaces + 1))

After trying to make the code clearer, I refactored it into the following solution:

pi = 4.0 * (r 1 False 10000000 0 0)
 
r !denominator doSubtract numOfPlacesMax !currentNumOfPlaces !acc
   | currentNumOfPlaces > numOfPlacesMax = acc
   | otherwise =  if doSubtract
                     then r nextDenominator (not doSubtract) numOfPlacesMax nextNumOfPlaces (acc - (1 / denominator))
                     else r nextDenominator (not doSubtract) numOfPlacesMax nextNumOfPlaces (acc + (1 / denominator))
   where
      nextDenominator = denominator + 2
      nextNumOfPlaces = currentNumOfPlaces + 1

pi = 4.0 * (r 1 False 10000000 0 0) r !denominator doSubtract numOfPlacesMax !currentNumOfPlaces !acc | currentNumOfPlaces > numOfPlacesMax = acc | otherwise = if doSubtract then r nextDenominator (not doSubtract) numOfPlacesMax nextNumOfPlaces (acc - (1 / denominator)) else r nextDenominator (not doSubtract) numOfPlacesMax nextNumOfPlaces (acc + (1 / denominator)) where nextDenominator = denominator + 2 nextNumOfPlaces = currentNumOfPlaces + 1

The first solution was consuming about 2GB of memory, then exiting with an “out of memory” exception. This is because of laziness and not using tail recursion. In the second version of the solution, I changed the code to be properly tail recursive, but that change alone is not enough. Because of laziness, I had to make some of the parameters of the r function strict by using BangPatterns. Now the second solution runs in constant space.

Tagged Haskell

Related Posts

Shifting gears // The need for speed!

June 26, 2023June 26, 2023Software Development

Keeping things simple – a challenge all teams face

June 12, 2023June 12, 2023Software Development
Copyright © 2022 Hait. All rights reserved.
↑