[Edu-sig] <FUN>more math with Python</FUN>
Tim Peters
tim.one@home.com
Mon, 1 Oct 2001 19:14:05 -0400
[Tim]
>> 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.
[Kirby Urner]
> Not following this.
>
> >>> def altdrs(n):
> return round(fact(n)/e)
>
> returns the same answer as drs(n) right down to n=1.
Then 1 certainly isn't the smallest n for which equality fails <wink>.
Think about larger n, not smaller. After all, "e" there has at best 53 bits
of precision, and for large enough n "fact(n)" can't even be represented
approximately as a float (in 2.2a4 you'll get an OverflowError then; in
earlier Pythons *probably* a platform floating Infinity).
> 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:
Yes, that is related.
> ...
> while float(v) <> phi:
Note that loops like this may never terminate: there's really no a priori
guarantee that any approximation will *exactly* equal the value computed by
some other approximation (and math.sqrt() and math.e are approximations
too).
>> 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?
It's a pragmatic ("programming") issue, not a semantic issue: the purpose
of special-casing 1 and -1 within F.__mul__ would not be to deliver a
different answer, but to return the correct answer *faster*. Even just
making a copy of an unbounded rational number can take an unbounded amount
of time (depending on how long it takes to copy over all the data), so
special-casing multiplication by 1 to simply return "self" can save an
unbounded amount of runtime.
On the other hand, testing for 1 also takes time, and that time is wasted
whenver the outcome is "nope -- it's not 1". So deciding whether it's a
good idea *overall* is a balancing act.
For most programs using floats or (machine) ints, it turns out it's usually
an overall speed loss to special-case potentially special values. The
tradeoffs are much muddier for unbounded numeric types, while the existence
of idioms like "multiply by 1 or -1 on alternate iterations" make it an
important pragmatic issue ("who in their right mind would bother multiplying
by one?" turns out to be a poor argument <wink>).