Alternative to Decimal type

Frank Millman frank at chagford.com
Mon Jun 9 10:11:16 CEST 2008


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.

There is one inconvenience with this approach. For example, if I have
a product quantity with a scale of 4, and a price with a scale of 2,
and I want to multiply them to get a value with a scale of 2, I have
to remember to scale the result down by 4. This is a minor chore, and
errors are quickly picked up by testing, but it does make the code a
bit messy, so it would be nice to find a solution.

I am now doing some refactoring, and decided to take a look at the
Decimal type. My initial impressions are that it is quite awkward to
use, that I do not need its advanced features, and that it does not
help solve the one problem I have mentioned above.

I therefore spent a bit of time experimenting with a Number type that
suits my particular requirements. I have come up with something that
seems to work, which I show below.

I have two questions.

1. Are there any obvious problems in what I have done?

2. Am I reinventing the wheel unnecessarily? i.e. can I do the
equivalent quite easily using the Decimal type?

--------------------
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
        self.value = long(round(value * self.factor))
        self.scale = scale

    def __add__(self,other):
        if isinstance(other,Number):
            other = other.value / other.factor
        return Number((self.value/self.factor)+other,self.scale)

    def __sub__(self,other):
        if isinstance(other,Number):
            other = other.value / other.factor
        return Number((self.value/self.factor)-other,self.scale)

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

    def __truediv__(self,other):
        if isinstance(other,Number):
            other = other.value / other.factor
        return Number((self.value/self.factor)/other,self.scale)

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

    def __rsub__(self,other):
        return Number(other-(self.value/self.factor),self.scale)

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

    def __rtruediv__(self,other):
        return Number(other/(self.value/self.factor),self.scale)

    def __cmp__(self,other):
        if isinstance(other,Number):
            other = other.value / other.factor
        this = self.value / self.factor
        if this < other:
            return -1
        elif this > other:
            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:])
--------------------

Example usage -

>>>qty = Number(12.5,4)
>>>price = Number(123.45,2)

>>>print price * qty
1543.13  [scale is taken from left-hand operand]

>>>print qty * price
1543.1250  [scale is taken from left-hand operand]

>>>print Number(qty * price,2)
1543.13  [scale is taken from Number instance]
--------------------

At this stage I have not built in any rounding options, but this can
be done later if I find that I need it.

Any comments will be welcome.

Thanks

Frank Millman



More information about the Python-list mailing list