2010-01-11 9 views
1

Je souhaite utiliser la classe Decimal dans mon programme Python pour effectuer des calculs financiers. Les décimales ne fonctionnent pas avec les flottants - ils nécessitent d'abord une conversion explicite en chaînes. J'ai donc décidé de sous-classer Decimal pour pouvoir travailler avec des flottants sans conversions explicites.Sous-classe Decimal en Python

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)) 

Alors maintenant, au lieu d'écrire D(5000000)*D(2.2) je devrais pouvoir écrire D(5000000)*2.2 sans lainage exceptions.

J'ai plusieurs questions:

  1. Est-ce que ma décision me causer des ennuis?

  2. Réimplémenter __mul__ ne fonctionne pas en cas de D(5000000)*D(2.2), parce que l'autre argument est de type class '__main__.m_Decimal', mais vous pouvez voir dans le module décimal ceci:

decimal.py, ligne 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 

Le module décimal s'attend à ce que l'argument soit Decimal ou int. Cela signifie que je devrais d'abord convertir mon objet m_Decimal en chaîne, puis en décimal. Mais c'est beaucoup de gaspillage - m_Decimal est descendant de Decimal - comment puis-je l'utiliser pour rendre la classe plus rapide (Decimal est déjà très lent).

  1. Lorsque cDecimal apparaîtra, ce sous-classement fonctionnera-t-il?
+3

Gardez à l'esprit que la multiplication d'un décimal par un flotteur rend une décimale réduite précision, ce qui explique pourquoi cette opération n'est pas mis en œuvre en premier lieu. –

+1

Vous ne savez pas pourquoi vous devriez convertir? 'isinstance' prend correctement en compte le sous-classement ...' isinstance (d, m_Decimal) 'implique' isinstance (d, Decimal) '. – Dirk

+1

Votre opération va sûrement causer des problèmes avec le but des décimales. Ne les utilisez pas si vous n'en avez pas besoin ou si vous ne comprenez pas leur raison d'être; Je vous recommande de vérifier la norme IEEE 754. –

Répondre

1

Actuellement, il ne fera pas ce que vous voulez du tout. Vous ne pouvez multiplier votre m_decimal par quoi que ce soit: il retournera toujours Aucun, en raison d'une déclaration de retour manquant:

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

Même avec le retour ajouté, vous ne pouvez toujours pas faire D (500000) * 2.2, comme le float a encore besoin d'être converti en décimal, avant décimal. mul l'acceptera. En outre, rééd ne convient pas ici:

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

La façon dont je le ferais, est de faire un classmethod, fromfloat

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

outrepasser ensuite la méthode Mul pour vérifier le type d'autres, et exécuter m_Decimal.fromfloat() sur elle si elle est un flotteur:

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) 

il fonctionnera alors exactement comme prévu. Personnellement, je ne remplacerais pas la nouvelle méthode, car il me semble plus simple d'utiliser la méthode fromfloat(). Mais c'est juste mon opinion.

Comme l'a dit Dirk, vous n'avez pas à vous soucier de la conversion, comme c'est le cas avec les sous-classes. Le seul problème que vous pourriez avoir est que décimal * m_Decimal renverra une décimale, plutôt que votre sous-classe:

>>> 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' 

Il y a deux façons de résoudre ce problème.Tout d'abord est d'ajouter une conversion explicite à mul magicmethod du m_Decimal:

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

L'autre façon, que je ne serais probablement pas recommander, est de « monkey-patch » le module décimal:

decimal._Decimal = decimal.Decimal 
decimal.Decimal = m_Decimal 
+0

Merci, je vais essayer cela – warvariuc

1

Dans mon opinion que vous ne devriez pas utiliser de flotteurs du tout. Les flotteurs ne sont pas le bon outil pour une application financière. Partout où vous utilisez des flottants, vous devriez pouvoir utiliser str ou Decimal pour vous assurer que vous ne perdez pas de précision.

Par exemple.
Saisie de l'utilisateur, entrée de fichier - évidemment utiliser str et convertir en décimal pour faire n'importe quel math
Base de données - utiliser le type décimal s'il est supporté, sinon utiliser les chaînes et convertir en décimal dans votre application.

Si vous insistez sur l'utilisation de flotteurs, rappelez-vous que python float est équivalent à double sur beaucoup d'autres plates-formes, donc si vous les stocker dans une base de données par exemple, assurez-vous que le champ de base de données est de type double

+0

> Partout où vous utilisez des flotteurs, vous devriez être en mesure d'utiliser str ou Decimal pour vous assurer que vous ne perdez pas de précision. Le problème est le suivant: J'écris une plateforme qui supportera les modules externes écrits par d'autres programmeurs. J'utilise PyQt et les formes générées dynamiquement. J'ai développé mon propre widget pour éditer des décimales, et voici la question sur la valeur qu'il garde. Je veux utiliser des décimales, mais pour les modules utilisateur (programmeur), il n'est pas intuitif d'écrire à chaque fois: myvar = decimalEditWidget.value + decimal.Decimal ('2.2') Donc je voulais que cette conversion soit transparente – warvariuc

0

Utilisation cdecimal ou decimal.py de 2.7 ou 3.2. Tous ceux qui ont la méthode de classe from_float:


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)