views:

330

answers:

3

I want to use Decimal class in my Python program for doing financial calculations. Decimals to not work with floats - they need explicit conversion to strings first. So i decided to subclass Decimal to be able to work with floats without explicit conversions.

m_Decimal.py:

# -*- coding: utf-8 -*-
import decimal

Decimal = decimal.Decimal

def floatCheck ( obj ) : # usually Decimal does not work with floats
    return repr ( obj ) if isinstance ( obj, float ) else obj # this automatically converts floats to Decimal

class m_Decimal ( Decimal ) :
    __integral = Decimal ( 1 )

    def __new__ ( cls, value = 0 ) :
        return Decimal.__new__ ( cls, floatCheck ( value ) )

    def __str__ ( self ) :
        return str ( self.quantize ( self.__integral ) if self == self.to_integral () else self.normalize () ) # http://docs.python.org/library/decimal.html#decimal-faq

    def __mul__ ( self, other ) :
        print (type(other))
        Decimal.__mul__ ( self,  other )

D = m_Decimal

print ( D(5000000)*D(2.2))

So now instead of writing D(5000000)*D(2.2) i should be able to write D(5000000)*2.2 without rasing exceptions.

I have several questions:

  1. Will my decision cause me any troubles?

  2. Reimplementing __mul__ doesn't work in case of D(5000000)*D(2.2), because the other argument is of type class '__main__.m_Decimal', but you can see in decimal module this:

decimal.py, line 5292:

def _convert_other(other, raiseit=False):
    """Convert other to Decimal.

    Verifies that it's ok to use in an implicit construction.
    """
    if isinstance(other, Decimal):
        return other
    if isinstance(other, (int, long)):
        return Decimal(other)
    if raiseit:
        raise TypeError("Unable to convert %s to Decimal" % other)
    return NotImplemented

The decimal module expects argument being Decimal or int. This means i should convert my m_Decimal object to string first, then to Decimal. But this is lot of waste - m_Decimal is descendant of Decimal - how can i use this to make the class faster (Decimal is already very slow).

  1. When cDecimal will appear, will this subclassing work?
A: 

Currently, it won't do what you want at all. You can't multiply your m_decimal by anything: it will always return None, due to a missing return statement:

    def __mul__ ( self, other ) :
        print (type(other))
        return Decimal.__mul__ ( self,  other )

Even with the return added in, you still can't do D(500000)*2.2, as the float still needs converting to a Decimal, before Decimal.mul will accept it. Also, repr is not appropriate here:

>>> repr(2.1)
'2.1000000000000001'
>>> str(2.1)
'2.1'

The way I would do it, is to make a classmethod, fromfloat

    @classmethod
    def fromfloat(cls, f):
        return cls(str(f))

Then override the mul method to check for the type of other, and run m_Decimal.fromfloat() on it if it is a float:

class m_Decimal(Decimal):
    @classmethod
    def fromfloat(cls, f):
        return cls(str(f))

    def __mul__(self, other):
        if isinstance(other, float):
            other = m_Decimal.fromfloat(other)
        return Decimal.__mul__(self,other)

It will then work exactly as you expect. I personally wouldn't override the new method, as it seems cleaner to me to use the fromfloat() method. But that's just my opinion.

Like Dirk said, you don't need to worry about conversion, as isinstance works with subclasses. The only problem you might have is that Decimal*m_Decimal will return a Decimal, rather than your subclass:

>>> Decimal(2) * m_Decimal(2) * 2.2

Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    Decimal(2) * m_Decimal(2) * 2.2
TypeError: unsupported operand type(s) for *: 'Decimal' and 'float'

There are two ways to fix this. First is to add an explicit conversion to the m_Decimal's mul magicmethod:

    def __mul__(self, other):
        if isinstance(other, float):
            other = m_Decimal.fromfloat(other)
        return m_Decimal(Decimal.__mul__(self,other))

The other way, which I probably wouldn't recommend, is to "Monkeypatch" the decimal module:

decimal._Decimal = decimal.Decimal
decimal.Decimal = m_Decimal
RoadieRich
Thanks, i will try that
blindvic
A: 

In my opinion you shouldn't use floats at all. Floats are not the right tool for a financial application. Anywhere you are using floats you should be able to use str or Decimal to ensure you are not losing precision.

For example.
User input, File input - obviously use str and convert to Decimal to do any math
Database - use decimal type if it is supported, otherwise use strings and convert to Decimal in your app.

If you insist on using floats, remember that python float is equivalent to double on many other platforms, so if you are storing them in a database for instance, make sure the database field is of type double

gnibbler
>Anywhere you are using floats you should be able to use str or Decimal to ensure you are not losing precision.The problem is this:i am writing a platform which will support external modules written by other programmers. I use PyQt and dynamically generated forms. I developed my own widget for editing decimals, and here appears the question about the value it keeps. I want to use decimals, but for user (programmer) modules it's not intuitive to write every time: myvar = decimalEditWidget.value + decimal.Decimal ('2.2')So i wanted to make this conversion transparent
blindvic
A: 

Use cdecimal or decimal.py from 2.7 or 3.2. All of those have the from_float class method:


class MyDecimal(Decimal):
    def convert(self, v):
        if isinstance(v, float):
            return Decimal.from_float(v)
        else:
            return Decimal(v)
    def __mul__(self, other):
        other = self.convert(other)
        return self.multiply(other)
anon