Curious? Factor to the rescue!
Monday, July 12, 2010
A lot of dynamic language users have strong interest in math (particularly those who use functional programming languages). Some months back I came across a fun blog post about using Clojure to approximate Euler’s Number, according to this observation:
“Here is an example of e turning up unexpectedly. Select a random number between 0 and 1. Now select another and add it to the first. Keep doing this, piling on random numbers. How many random numbers, on average, do you need to make the total greater than 1? Answer: 2.71828…”
I wanted to contrast his solution with one using Factor. We’ll use these vocabularies:
USING: kernel math random sequences ;
We can use the
random
vocabulary to create a random float between 0
and 1
, counting how
many numbers we need to add before the value is greater than 1
:
: numbers-added ( -- n )
0 0 [ dup 1 < ] [
[ 1 + ] dip 0.0 1.0 uniform-random-float +
] while drop ;
Taking the average of a sequence of numbers is pretty easy:
: average ( seq -- n )
[ sum ] [ length ] bi / ;
Similar to the original blog post, we’ll want to estimate the value of
e
by running numbers-added
a certain number of times and then taking
the average of the resulting sequence:
: approximate-e ( n -- approx )
[ numbers-added ] replicate average ;
Running this from one thousand to one million times gives us an
increasingly accurate approximation of e
:
IN: scratchpad { 1000 10000 100000 1000000 }
[ approximate-e >float ] map .
{ 2.694 2.7186 2.72149 2.716958 }
IN: scratchpad USING: math.constants ; e .
2.718281828459045
This could be improved by virtualizing the sequences and then performing
an incremental average as we generate each of n
approximations. That
would likely result in both memory and speed improvements (although I
haven’t tried it yet).
The code for this is on my GitHub.