I'm brand new to erlang (and programming in general). So I'm trying to get Erlang to output the calculation of Pi accurately and precisely 5 decimal places. I've spent about 5 straight days (probably about 35 hours) of trying different things and researching and I just can't figure it out. Here's my thought process and the work I've done so far:
Note: I thought to start off with something a little smaller (accurate to 2 decimals) and figured once I can get it to 2, I can adjust to get to 5 decimals.
So using the pi approximation by summation of 4* ( 1 - 1/3 + 1/5 - 1/7 ...) you can accurately get a value of pi with the more terms you use. What I am trying to do is to have it stop the recursion at precisely the moment it correctly gets to the desired decimal. Now I've been able to work out through trial and error how many terms to get to 1, 2, 3, 4, 5, 6 decimals accurately, but I'm trying to get a general way of doing it because we're assuming you don't know the value of pi to begin with.
So what I'm trying to do is have erlang come up with 2 summations...a summation of an N amount of terms and a summation on N+1 amount of terms, and once the summation of the N amount matches the summation of the next amount (N+1) of terms, it should stop and output that matching value.
The way I thought about it, since I couldn't truncate to a certain decimal, I can truncate to an integer...so multiple the summation by 10^P (where P is the amount of decimal places, so in my example below P is input 2), and then truncate to check to see when the two summations match, and when they do match, give that ouput and I can just have it divide by the 10^P to get the decimals.
-module (difpi).
-export [(sumpi1/2)].
-export [(sumpi2/2)].
sumpi1(628,_) -> 0;
sumpi1(N,P) -> trunc((((math:pow(-1,N))*(4*(math:pow(10,P))/(2*N+1)) + (sumpi1(N+1,P))))).
sumpi2(628+1,_) -> 0;
sumpi2(K,P) -> trunc((((math:pow(-1,K))*(4*(math:pow(10,P))/(2*K+1)) + (sumpi2(K+1,P))))).
I was able to figure out it took 628 terms to get to 314, and the 628th term was also 314, but how do I get it to do it without "knowing" how many terms to go?
So I'm trying to have it say... sumPi1 after 1 term, and sumPi2 after 1st 2 terms...do they match (NO), so now sumPi1 first 2 terms, and sumPi2 first 3 terms...do they match? (NO)... but I want that process to continue until they do match, then I want the output to say ok they match, and they match at 314.
I built in the 2 arguments as you can see so that I can run it and input the number of decimal places it can go to. So I could say, calculate pi to 1 decimal, 2 decimal, 3...etc. (even though I need it to only go to 5, just thought it would be cool to do).
I hope my question makes sense. Please help if you can, my brain is about to explode!
Here's what the output looks like to show that the truncated 628th and the 629th terms match (and obviously just divide by 10^2 to get 3.14 to display it to 2 decimals).
ကErlang/OTP 19 [erts-8.2] [64-bit] [smp:4:4] [async-threads:10]
Eshell V8.2 (abort with ^G)
1> cd("c:/erlang").
c:/erlang
ok
2> c(difpi).
{ok,difpi}
3> difpi:sumpi1(0,2).
314
4> difpi:sumpi2(0,2).
314
5>
Congratulations for taking the quest of learning Erlang. It's really a fascinating programming language, running on top of a fascinating virtual machine.
It's perfectly possible to solve this problem with a little help of pattern matching. You have to adopt the strategy of keeping the last result of your calculation around and comparing it with the current step before iterating further.
This kind of series converges very slowly, so it's very important to keep special attention to make use of tail call optimization, which means the recursive call to the function has to be the last and only statement of the function itself.
My proposal of implementation is the following. You can prove it works by this simple unit test:
%%% file: diff_pi_tests.erl
-module(diff_pi_tests).
-include_lib("eunit/include/eunit.hrl").
-import(diff_pi, [sum_pi/1]).
sum_pi_test_() ->
[
?_assertEqual(3.0, sum_pi(0)),
?_assertEqual(3.1, sum_pi(1)),
?_assertEqual(3.14, sum_pi(2)),
?_assertEqual(3.141, sum_pi(3)),
?_assertEqual(3.1415, sum_pi(4)),
?_assertEqual(3.14159, sum_pi(5))
].
The implementation:
%%% file: diff_pi.erl
-module(diff_pi).
-export([sum_pi/1]).
-spec sum_pi(integer()) -> float().
sum_pi(Precision) ->
sum_pi(4, -4, 3, Precision).
-spec sum_pi(number(), number(), number(), integer()) -> float().
sum_pi(LastResult, Numerator, Denominator, Precision) ->
NextResult = LastResult + Numerator/Denominator,
% Uncomment the following line to see the comparison of each step
% io:format("~p ~p~n", [LastResult, NextResult]),
case compare(LastResult, NextResult, Precision) of
true ->
Magnitude = math:pow(10, Precision),
trunc(NextResult*Magnitude)/Magnitude;
false ->
sum_pi(NextResult, -1*Numerator, Denominator+2, Precision)
end.
-spec compare(number(), number(), integer()) -> boolean().
compare(X, Y, Precision) ->
RoundX = trunc(X*math:pow(10, Precision)),
RoundY = trunc(Y*math:pow(10, Precision)),
RoundX =:= RoundY.
Compile and run the tests:
$ erlc *.erl && erl -noshell -s eunit test diff_pi -s init stop
All 6 tests passed.