Mel wrote:
Frank Millman wrote:

Hi all

I have a standard requirement for a 'decimal' type, to instantiate and
manipulate numeric data that is stored in a database. I came up with a
solution long before the introduction of the Decimal type, which has
been working well for me. I know the 'scale' (number of decimal
places) of the number in advance. When I read the number in from the
database I scale it up to an integer. When I write it back I scale it
down again. All arithmetic is done using integers, so I do not lose
accuracy.
[snip]
--------------------
from __future__ import division

class Number(object):
    def __init__(self,value,scale):
        self.factor = 10.0**scale
        if isinstance(value,Number):
            value = value.value / value.factor

I think this could lead to trouble.  One complaint against binary floating
point is that it messes up low-order decimal digits, and this ensures that
all calculations are effectively done in binary floating point.  Better, I
think would be

        if isinstance (value, Number):
            self.value = value.value
            self.scale = scale + value.scale

and be done with it.  Of course, this means self.scale no longer gives the
preferred number of fractional digits.  My bias: I did a DecimalFloat class
way back when, when Decimal was being discussed, and separated the exponent
for calculations from the rounding precision for display.

What about a little rewrite so the current implementation is like the original, and all calculations are done as integers? Or is this just getting closer and closer to what Decimal does?

[only lightly tested]

from __future__ import division

class Number(object):
    def __init__(self, value, scale):
        if isinstance(value, Number):
            delta = value.scale - scale
            if delta > 0:
                self.value = value.value // 10**delta
            elif delta < 0:
                self.value = value.value * 10**abs(delta)
            else:
                self.value = value.value
        else:
            if not scale:
                scale += 1
            self.scale = scale
            self.factor = 10**self.scale
            self.value = long(round(value * self.factor))

    def __add__(self, other):
        answer = Number(other, self.scale)
        answer.value += self.value
        return answer

    def __sub__(self, rhs):
        answer = Number(rhs, self.scale)
        answer.value = self.value - answer.value
        return answer

    def __mul__(self, other):
        answer = Number(other, self.scale)
        answer.value *= self.value
        answer.value //= answer.factor
        return answer

    def __truediv__(self, rhs):
        answer = Number(rhs, self.scale)
        quotient = 0
        divisor = answer.value
        dividend = self.value
        for i in range(self.scale+1):
            quotient = (quotient * 10) + (dividend // divisor)
            dividend = (dividend % divisor) * 10
        answer.value = quotient
        return answer

    def __radd__(self, lhs):
        return self.__add__(lhs)

    def __rsub__(self, lhs):
        answer = Number(lhs, self.scale)
        answer.value = answer.value - self.value
        return answer

    def __rmul__(self, lhs):
        return self.__mul__(lhs)

    def __rtruediv__(self, lhs):
        answer = Number(lhs, self.scale)
        quotient = 0
        divisor = self.value
        dividend = answer.value
        for i in range(self.scale+1):
            quotient = (quotient * 10) + (dividend // divisor)
            dividend = (dividend % divisor) * 10
        answer.value = quotient
        return answer

    def __cmp__(self, rhs):
        other = Number(rhs, self.scale)
        if self.value < other.value:
            return -1
        elif self.value > other.value:
            return 1
        else:
            return 0

    def __str__(self):
        s = str(self.value)
        if s[0] == '-':
            minus = '-'
            s = s[1:].zfill(self.scale+1)
        else:
            minus = ''
            s = s.zfill(self.scale+1)
        return '%s%s.%s' % (minus, s[:-self.scale], s[-self.scale:])

--
Ethan
--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to