Подкласс десятичной дроби в Python

Я хочу использовать класс Decimal в своей программе Python для финансовых расчетов. Десятичные числа не работают с числами с плавающей запятой - сначала требуется явное преобразование в строки. Поэтому я решил создать подкласс Decimal, чтобы иметь возможность работать с числами с плавающей запятой без явных преобразований.

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

Итак, теперь вместо записи D(5000000)*D(2.2) я должен написать D(5000000)*2.2 без исключения исключений.

У меня есть несколько вопросов:

  1. Не вызовет ли мое решение неприятностей?

  2. Повторная реализация __mul__ не работает в случае D(5000000)*D(2.2), потому что другой аргумент имеет тип class '__main__.m_Decimal', но вы можете увидеть в десятичном модуле это:

decimal.py, строка 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

Десятичный модуль ожидает, что аргумент будет Decimal или int. Это означает, что я должен сначала преобразовать свой объект m_Decimal в строку, а затем в Decimal. Но это много отходов - m_Decimal является потомком Decimal - как я могу использовать это, чтобы сделать класс быстрее (Decimal уже очень медленный).

  1. Когда появится cDecimal, будет ли работать этот подкласс?

person warvariuc    schedule 11.01.2010    source источник
comment
Имейте в виду, что умножение десятичного числа на число с плавающей запятой приведет к отображению десятичного числа с пониженной точностью, поэтому эта операция не реализована в первую очередь.   -  person Ignacio Vazquez-Abrams    schedule 11.01.2010
comment
Не уверен, зачем тебе конвертировать? isinstance правильно учитывает подклассы... isinstance(d, m_Decimal) подразумевает isinstance(d, Decimal).   -  person Dirk    schedule 11.01.2010
comment
Ваша операция обязательно вызовет проблемы с назначением десятичных знаков. Не используйте их, если они вам не нужны или если вы не понимаете их назначение; Я рекомендую вам проверить стандарт IEEE 754.   -  person Alan Franzoni    schedule 12.01.2010


Ответы (3)


В настоящее время он не будет делать то, что вы хотите. Вы не можете ни на что умножить свой m_decimal: он всегда будет возвращать None из-за отсутствующего оператора return:

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

Даже с добавленным возвратом вы все равно не можете выполнить D(500000)*2.2, так как число с плавающей запятой все еще необходимо преобразовать в десятичное, прежде чем Decimal.mul его примет. Кроме того, repr здесь неуместен:

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

Я бы сделал это, создав метод класса fromfloat.

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

Затем переопределите метод mul, чтобы проверить тип other, и запустите для него m_Decimal.fromfloat(), если это число с плавающей запятой:

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)

Тогда он будет работать именно так, как вы ожидаете. Лично я не стал бы переопределять новый метод, так как мне кажется, что лучше использовать метод fromfloat(). Но это только мое мнение.

Как сказал Дирк, вам не нужно беспокоиться о преобразовании, поскольку isinstance работает с подклассами. Единственная проблема, которая у вас может возникнуть, заключается в том, что Decimal*m_Decimal вернет Decimal, а не ваш подкласс:

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

Есть два способа исправить это. Во-первых, нужно добавить явное преобразование в mul magicmethod m_Decimal:

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

Другой способ, который я, вероятно, не рекомендовал бы, - это "Monkeypatch" десятичный модуль:

decimal._Decimal = decimal.Decimal
decimal.Decimal = m_Decimal
person Community    schedule 11.01.2010

На мой взгляд, вы вообще не должны использовать поплавки. Поплавки — неподходящий инструмент для финансового приложения. Везде, где вы используете числа с плавающей запятой, вы должны иметь возможность использовать str или Decimal, чтобы гарантировать, что вы не теряете точность.

Например.
Пользовательский ввод, ввод файла — очевидно, используйте str и конвертируйте в Decimal для любых математических вычислений
База данных — используйте десятичный тип, если он поддерживается, в противном случае используйте строки и конвертируйте в Decimal в своем приложении.

Если вы настаиваете на использовании чисел с плавающей запятой, помните, что python float эквивалентен double на многих других платформах, поэтому, если вы храните их, например, в базе данных, убедитесь, что поле базы данных имеет тип double

person John La Rooy    schedule 11.01.2010
comment
› Везде, где вы используете числа с плавающей запятой, вы должны иметь возможность использовать str или Decimal, чтобы гарантировать, что вы не теряете точность. Проблема в следующем: я пишу платформу, которая будет поддерживать внешние модули, написанные другими программистами. Я использую PyQt и динамически генерируемые формы. Я разработал свой собственный виджет для редактирования десятичных знаков, и тут возникает вопрос о том, какое значение он сохраняет. Я хочу использовать десятичные числа, но для пользовательских (программистских) модулей не интуитивно писать каждый раз: myvar = decimalEditWidget.value + decimal.Decimal('2.2'). Поэтому я хотел сделать это преобразование прозрачным - person warvariuc; 12.01.2010

Используйте cdecimal или decimal.py из 2.7 или 3.2. Все они имеют метод класса 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)
person anon    schedule 14.01.2010