> * Follow the decimal module's lead in assiduously avoiding
> float->rational conversions. Something like Rat.from_float(1.1) is
> likely to produce unexpected results (like locking in an inexact input
> and having an unexpectedly large denominator).
I agree that it's not usually what people ought to use, and I don't
think it's an essential part of the API. It might be a useful starting
point for the approximation methods though. .trim() and .approximate()
in http://svn.python.org/view/sandbox/trunk/rational/Rational.py?rev=40988&view=auto
and Haskell's approxRational
(http://www.haskell.org/ghc/docs/latest/html/libraries/base/src/Data-Ratio.html)
start from rationals instead of floats. On the other hand, it might
make sense to provide explicit methods to approximate from floats
instead of asking people to execute the two-phase process. I'm happy
to give it a different name or drop it entirely.
> * Likewise, follow decimal's lead in avoiding all automatic coercisions
> from floats: Rational(3,10) + 0.3 for example. The two don't mix.
I'm reluctant to remove the fallback to float, unless the consensus is
that it's always a bad idea to support mixed-mode arithmetic (which is
possible: I was surprised by the loss of precision of "10**23/1" while
writing this). Part of the purpose of this class is to demonstrate how
to implement cross-type operations. Note that it is an automatic
coercion _to_ floats, so it doesn't look like you've gotten magic
extra precision.
> * Consider following decimal's lead on having a context that can limit
> the maximum size of a denominator. There are various strategies for
> handling a limit overflow including raising an exception or finding the
> nearest rational upto the max denominator (there are some excellent
> though complex published algorithms for this) or rounding the nearest
> fixed-base (very easy). I'll dig out my HP calculator manuals at some
> point -- they had worked-out a number of challenges with fractional
> arithmetic (including their own version of an Inexact tag).
Good idea, but I'd like to punt that to a later revision if you don't
mind. If we do punt, that'll force the default context to be "infinite
precision" but won't prevent us from adding explicit contexts. Do you
see any problems with that?
> * Consider adding Decimal.from_rational and Rational.from_decimal. I
> believe these are both easy and can be done losslessly.
Decimal.from_rational(Rat(1, 3)) wouldn't be lossless, but
Rational.from_decimal is easier than from_float. Then
Decimal.from_rational() could rely on just numbers.Rational, so it
would be independent of this module. Is that a method you'd want on
Decimal anyway? The question becomes whether we want the rational to
import decimal to implement the typecheck, or just assume that
.as_tuple() does the right thing. These are just as optional as
.from_float() though, so we can also leave them for future
consideration.
> * Automatic coercions to/from Decimal need to respect the active decimal
> context. For example the result of Rational(3,11) +
> Decimal('3.1415926') is context dependent and may not be commutative.
Since I don't have any tests for that, I don't know whether it works.
I suspect it currently returns a float! :) What do you want it to do?
Unfortunately, giving it any special behavior reduces the value of the
class as an example of falling back to floats, but that shouldn't
necessarily stop us from making it do the right thing.
> * When in doubt, keep the API minimal so we don't lock-in design mistakes.
Absolutely!
> * Test the API by taking a few numerical algorithms and seeing how well
> they work with rational inputs (for starters, try
> http://docs.python.org/lib/decimal-recipes.html ).
Good idea. I'll add some of those to the test suite.
> * If you do put in a method that accepts floats, make sure that it can
> accept arguments to control the rational approximation. Ideally, you
> would get something something like this Rational.approximate(math.pi, 6)
> --> 355/113 that could produce the smallest rationalal approximation to
> a given level of accuracy.
Right. My initial plan was to use
Rational.from_float(math.pi).simplest_fraction_within(Rational(1,
10**6)) but I'm not set on that, and, because there are several
choices for the approximation method, I'm skeptical whether it should
go into the initial revision at all. |