Package: simplefractions
Thesimplefractionspackage finds the simplest fraction that converts
to a given float, or more generally, the simplest fraction that lies within
a given interval.Definition.Given fractionsx = a/bandy = c/d(written in lowest
terms with positive denominators), say thatxissimplerthanyifabs(a) <= abs(c),b <= d, and at least one of these two inequalities
is strict.For example, 22/7 is simpler than 23/8, but neither of 3/8 and 4/7 is
simpler than the other.Then it's a theorem that given any subintervalIof the real line that
contains at least one fraction, that interval contains a unique simplest
fraction. That is, there's a fractiona/binIsuch thata/bis
simpler (in the above sense) than all other fractions inI. As a
consequence, for any given finite Python floatx, there's a unique
simplest fraction that rounds to that float.Thesimplefractionspackage provides two functions:simplest_from_floatreturns, for a given floatx, the unique simplest
fraction with the property thatfloat(simplest_from_float(x)) == x.simplest_in_intervalreturns the unique simplest fraction in a given
(open or closed, bounded or unbounded) nonempty interval.Example usageStart by importing the functions from the module:>>>fromsimplefractionsimport*Thesimplest_from_floatfunction takes a single finite floatxand
produces aFractionobject that recovers that float:>>>simplest_from_float(0.25)Fraction(1,4)>>>simplest_from_float(0.33)Fraction(33,100)No matter whatxis given, the invariantfloat(simplest_from_float(x)) == xwill always be true.>>>x=0.7429667872099244>>>simplest_from_float(x)Fraction(88650459,119319545)>>>float(simplest_from_float(x))0.7429667872099244>>>float(simplest_from_float(x))==xTrueIf the floatxwas constructed by dividing two small integers, then
more often than not,simplest_from_floatwill recover those integers:>>>x=231/199>>>x1.1608040201005025>>>simplest_from_float(x)Fraction(231,199)More precisely, ifxwas constructed by dividing two relatively prime
integers smaller than or equal to67114657in absolute value,simplest_from_floatwill recover those integers.>>>simplest_from_float(64841043/66055498)Fraction(64841043,66055498)But67114657is the best we can do here:>>>simplest_from_float(67114658/67114657)Fraction(67114657,67114656)In larger cases,simplest_from_floatmight discover a simpler fraction
that gives the same float:>>>x=818421477165/1580973145504>>>simplest_from_float(x)Fraction(5171,9989)>>>818421477165/1580973145504==5171/9989TrueNote thatsimplest_from_floatdoes not magically fix floating-point
inaccuracies. For example:>>>x=1.1+2.2>>>simplest_from_float(x)Fraction(675539944105597,204709073971393)You might have expectedFraction(33, 10)here, but when converted to float,
that gives a value very close to, but not exactly equal to,x. In contrast,
the return value ofsimplest_from_float(x)will always produce exactlyxwhen converted tofloat.To fix this, you might want to ask for the simplest float that lies within
some small error bound ofx- for example, within 5 ulps (units in the
last place) in either direction.simplest_from_floatcan't do that, butsimplest_in_intervalcan! For example>>>frommathimportulp>>>x=1.1+2.2>>>simplest_in_interval(x-5*ulp(x),x+5*ulp(x))Fraction(33,10)Alternatively, you might ask for the simplest fraction approximatingxwith a relative error of at most 0.000001:>>>relerr=1e-6>>>simplest_in_interval(x-relerr*x,x+relerr*x)Fraction(33,10)Here are some more examples ofsimplest_in_intervalat work. The inputs
tosimplest_in_intervalcan be floats, integers, orFractionobjects.>>>simplest_in_interval(3.14,3.15)Fraction(22,7)By default,simplest_in_intervalassumes that you're specifying an
open interval:>>>simplest_in_interval(3,4)Fraction(7,2)Keyword argumentsinclude_leftandinclude_rightallow you to specify
that one or both endpoints should be included in the interval:>>>simplest_in_interval(3,4,include_left=True,include_right=True)Fraction(3,1)The left and right endpoints of the interval are also both optional, alowing
a semi-infinite or infinite interval to be specified:>>>simplest_in_interval(right=4)# simplest in (-inf, 4)Fraction(0,1)>>>simplest_in_interval(left=4,include_left=True)# simplest in [4, inf)Fraction(4,1)>>>simplest_in_interval()# simplest in (-inf, inf)Fraction(0,1)