[Edu-sig] <FUN>more math with Python</FUN>

Kirby Urner urnerk@qwest.net
Mon, 01 Oct 2001 01:25:46 -0700


>In fact, it's such a good approximation that
>
>     drs(n) == round(n!/e)
>
>where round(x) rounds x to the nearest integer, for all n >= 1.


A very excellent point.


>If the students are advanced enough, it's an interesting exercise
>to find the smallest n for which the limitations of floating-point
>arithmetic cause round(n!/e) to return a different result than
>your exact computation using rationals.

Not following this.

   >>> def altdrs(n):
            return round(fact(n)/e)

returns the same answer as drs(n) right down to n=1.

A related question (that I understand) would be how far
do we need to push the series in brackets to get 1/e
within the limitations of floating point arithmetic:

   >>> from math import e
   >>> def e_neg1():
           f = 0
           sign = 1
           t = 0
           while float(f)<>(1/e):
             f += sign * F(1, fact(t))
              sign = -sign
              t += 1
           return (t-1,f)

   >>> e_neg1()
   (18, (138547156531409/376610217984000))

I get 18 terms, with the accompanying fraction.

    >>> 138547156531409/376610217984000  # (import future division)
    0.36787944117144233
    >>> 1/e
    0.36787944117144233

This is related to the thread wherein we get phi to the limits
of floating point, using the continued fraction:

phi= 1 +     1
         --------------
           1 +    1
               --------
                1 +  1
                    ----
                     1 + ...

It's related because evaluating a continued fraction involves
recursive use of the Fraction object F:

   >>> def evalcf(qs):
         """
         Evaluate continued fraction with partial quotients qs
         """
         if len(qs)==1:
             return F(qs[0],1)
         return F(qs[0],1) + F(1,evalcf(qs[1:]))

   >>> evalcf([1,1,1,1,1])
   (8/5)
   >>> evalcf([1,1,1,1,1,1,1,1,1])
   (55/34)
   >>> float(evalcf([1,1,1,1,1,1,1,1,1]))
   1.6176470588235294

   >>> from math import sqrt
   >>> phi = (1 + sqrt(5))/2
   >>> def getphi():
           qs = []
           v = 0
           while float(v) <> phi:
              qs.append(1)
              v = evalcf(qs)
          return (len(qs),v)

   >>> getphi()
   (40, (165580141/102334155))

>And later you flipped in a different way, by testing the
>parity of t.  A common idiom in numeric programs is to
>multiply by 1 or -1 instead, like so:

Slaps forehead.  Yes, of course!  This is what you see most
often.

>     f = 0
>     sign = 1
>     for t in range(n+1):
>         f += sign * F(1, fact(t))
>         sign = -sign
>     return fact(n) * f
>
>As a *programming* issue, it's thus interesting to consider whether
>F.__mul__ should or shouldn't try to recognize the special cases of
>multiplication by 1 and -1.  There's isn't a "one size fits all"
>answer, so it's not for students with low tolerance for uncertainty
><wink>.

Not following again.  Like, of course it should, right?

   >>> f = F(1,2)
   >>> f
   (1/2)
   >>> f*1
   (1/2)
   >>> f*-1
   (-1/2)

What other behavior would I want?

Kirby