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