123.3 + 0.1 is 123.3999999999 ?

Steven Taschuk staschuk at telusplanet.net
Tue Jun 3 13:49:27 EDT 2003


Quoth A. Lloyd Flanagan:
  [...]
> def get_one():
>     yield '0'
>     yield '.'
>     while 1:
>         yield '9'
> 
> Exercise for the reader:  write a program to prove that get_one()
> equals one.

Here's a hack for this purpose:

    import copy

    def generatorstate(genit):
        # broken, really; assumes no use of globals, for example
        frame = genit.gi_frame
        return copy.deepcopy((frame.f_locals, frame.f_lasti))

    def period(genit):
        items = []
        states = [generatorstate(genit)]
        while True:
            items.append(genit.next())
            states.append(generatorstate(genit))
            if states[-1] in states[:-1]:
                break
        i = states.index(states[-1])
        return items[:i], items[i:]

    def rational(genit):
        integerpart = int(''.join(list(iter(genit.next, '.'))))
        preamble, repeated = period(genit)
        preamble = ''.join(preamble)
        repeated = ''.join(repeated)
        repeated = Rational(int(repeated), 10**len(repeated)-1) \
                   / 10**len(preamble)
        preamble = Rational(int(preamble), 10**len(preamble))
        return integerpart + preamble + repeated

Then, with a suitable Rational class (see below), we have

    >>> rational(get_one())
    Rational(1, 1)

as desired.

A simple implementation of a Rational class:

    def gcd(a, b):
        while b:
            a, b = b, a % b
        return abs(a)

    class Rational(object):
        def __init__(self, numerator, denominator=1):
            if denominator == 0:
                raise ZeroDivisionError('%r/%r' % (numerator, denominator))
            if denominator < 0:
                numerator = -numerator
                denominator = -denominator
            g = gcd(numerator, denominator)
            self.numerator = numerator//g
            self.denominator = denominator//g

        def __str__(self):
            return '%s/%s' % (self.numerator, self.denominator)

        def __repr__(self):
            return 'Rational(%r, %r)' % (self.numerator, self.denominator)

        def __add__(self, other):
            if isinstance(other, int) or isinstance(other, long):
                return self + Rational(other)
            elif isinstance(other, Rational):
                return Rational(self.numerator*other.denominator
                                    + other.numerator*self.denominator,
                                self.denominator*other.denominator)
            else:
                return NotImplemented
        __radd__ = __add__

        def __mul__(self, other):
            if isinstance(other, int) or isinstance(other, long):
                return self * Rational(other)
            elif isinstance(other, Rational):
                return Rational(self.numerator * other.numerator,
                                self.denominator * other.denominator)
            else:
                return NotImplemented
        __rmul__ = __mul__

        def invert(self):
            return Rational(self.denominator, self.numerator)

        def __truediv__(self, other):
            if isinstance(other, int) or isinstance(other, long):
                return self / Rational(other)
            elif isinstance(other, Rational):
                return self * other.invert()
            else:
                return NotImplemented
        __div__ = __truediv__

        def __rtruediv__(self, other):
            if isinstance(other, int) or isinstance(other, long):
                return Rational(other) / self
            else:
                return NotImplemented
        __rdiv__ = __rtruediv__

-- 
Steven Taschuk                staschuk at telusplanet.net
"I tried to be pleasant and accommodating, but my head
 began to hurt from his banality."   -- _Seven_ (1996)





More information about the Python-list mailing list