F
Frank Millman
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 -
1543.13 [scale is taken from left-hand operand]
1543.1250 [scale is taken from left-hand operand]
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
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 -
1543.13 [scale is taken from left-hand operand]
1543.1250 [scale is taken from left-hand operand]
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