ANN: Decimal.py 0.0.0

Aahz Maruch aahz at panix.com
Fri May 18 12:20:22 EDT 2001


I've just finished stage one, addition and subtraction.  Stage two is
conversion routines, multiplication, and unit tests.   Stage three is
division.  Right now, I'm just throwing this out in case anyone really
wants decimal arithmetic badly enough to try testing this; I don't
expect serious testing until I finish stage two.

I'm not looking for any design critiques right now, just bug reports.

Decimal.py:

'''
This is a first stab at a decimal arithmetic module based on the decimal
arithmetic ANSI standard X3.274-1996.  It should work with Python 1.5.2
and later.

Most of the information used to create this module was helpfully provided 
by Mike Cowlishaw's work at
http://www2.hursley.ibm.com/decimal/
The rest comes from Tim Peters' FixedPoint.py, particularly the string
conversion routine.

In the current form of Decimal(), precision is finite but unbounded, with
the exception of division.  You must use the round() method to perform
any trimming.

Currently, the only way to create a Decimal instance is with a tuple
containing the necessary values.  Element zero is the sign, element one
is a tuple containing the digits of the integer portion, and element
three is the exponent (an int).  For example:

1 => (0, (1,), 0)
-.0123 => (1, (1,2,3), -5)
'''

DEFAULT_PRECISION = 9

class Decimal:
    def __init__(self, value):
        # tuple conversion
        if isinstance(value, type((None,)) ):
            self._sign = value[0]
            self._int = value[1]
            self._exp = value[2]
            return
        if isinstance(value, _WorkRep):
            if value.sign == 1:
                self._sign = 0
            else:
                self._sign = 1
            self._int = tuple(value.int)
            self._exp = value.exp
            return
        raise TypeError("Can't convert " + `value`)

    def __repr__(self):
        return "(" + `self._sign` + ", " + `self._int` + ", " + `self._exp` + ")"

    def __str__(self):
        import string
        if self._sign:
            tmp = ['-']
        else:
            tmp = []
        for digit in self._int:
            tmp.append(str(digit))
        if self._exp:
            tmp.append('e')
            tmp.append(str(self._exp))
        return string.join(tmp, '')

    def __neg__(self):
        if self._sign:
            return Decimal( (0, self._int, self._exp) )
        else:
            return Decimal( (1, self._int, self._exp) )

    def __abs__(self):
        if self._sign:
            return -self
        else:
            return self

    def __add__(self, other):
        if self._int == (0,):
            return other
        if other._int == (0,):
            return self
        op1 = _WorkRep(self)
        op2 = _WorkRep(other)
        op1, op2 = _normalize(op1, op2)
        result = _WorkRep()
        if op1.sign != op2.sign:
            diff = cmp(abs(op1), abs(op2))
            if diff == 0:
                return Decimal( (0,(0,),0) )
            if diff < 0:
                op1, op2 = op2, op1
            if op1.sign == -1:
                result.sign = -1
                op1.sign, op2.sign = op2.sign, op1.sign
            else:
                result.sign = 1
        elif op1.sign == -1:
            result.sign = -1
            op1.sign, op2.sign = (1, 1)
        else:
            result.sign = 1
        carry, loan = (0, 0)
        op1.int.reverse()
        op2.int.reverse()
        for i in range(len(op1.int)):
            tmp = op1.int[i] + (op2.sign * op2.int[i]) + carry - loan
            carry, loan = (0, 0)
            if tmp > 9:
                carry = 1
                tmp = tmp - 10
            if tmp < 0:
                loan = 1
                tmp = tmp + 10
            result.int.append(tmp)
        if carry:
            result.int.append(1)
        if loan:
            raise "What are we doing here?"
        while result.int[-1] == 0:
            result.int.pop()
        result.int.reverse()
        result.exp = op1.exp
        return Decimal(result)

    def __sub__(self, other):
        return self.__add__(-other)

    def round(self, prec=None):
        raise


class _WorkRep:
    def __init__(self, value=None):
        if value is None:
            self.sign = None
            self.int = []
            self.exp = None
        if isinstance(value, Decimal):
            if value._sign:
                self.sign = -1
            else:
                self.sign = 1
            self.int = list(value._int)
            self.exp = value._exp
        if isinstance(value, type((None,)) ):
            self.sign = value[0]
            self.int = value[1]
            self.exp = value[2]

    def __repr__(self):
        return "(" + `self.sign` + ", " + `self.int` + ", " + `self.exp` + ")"

    __str__ = __repr__

    def __neg__(self):
        if self.sign:
            return _WorkRep( (0, self.int, self.exp) )
        else:
            return _WorkRep( (1, self.int, self.exp) )

    def __abs__(self):
        if self.sign:
            return -self
        else:
            return self

    def __cmp__(self, other):
        if self.exp != other.exp:
            raise ValueError("Operands not normalized: " + `self` + ", " + `other`)
        if self.sign != other.sign:
            if self.sign:
                return -1
            else:
                return 1
        if self.sign:
            direction = -1
        else:
            direction = 1
        int1 = self.int
        int2 = other.int
        if len(int1) > len(int2):
            return direction * 1
        if len(int1) < len(int2):
            return direction * -1
        for i in range(len(int1)):
            if int1[i] > int2[i]:
                return direction * 1
            if int1[i] < int2[i]:
                return direction * -1
        return 0



def _normalize(op1, op2):
    numdigits = op1.exp - op2.exp
    if numdigits < 0:
        numdigits = -numdigits
        tmp = op2
    else:
        tmp = op1
    tmp.int.extend([0] * numdigits)
    tmp.exp = tmp.exp - numdigits
    numdigits = len(op1.int) - len(op2.int)
    if numdigits < 0:
        numdigits = -numdigits
        tmp = op1
    else:
        tmp = op2
    tmp.int[0:0] = [0] * numdigits
    return op1, op2



More information about the Python-list mailing list