Alternative to Decimal type
Ethan Furman
ethan at stoneleaf.us
Tue Jun 10 10:06:11 EDT 2008
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
More information about the Python-list
mailing list