Подтвердить что ты не робот

Разъяснение по типу Decimal в Python

Все знают, или, по крайней мере, каждый программист должен знать, что использование типа float может привести к ошибкам точности. Однако в некоторых случаях точное решение было бы замечательным, и бывают случаи, когда сравнивать с использованием значения epsilon недостаточно. Во всяком случае, это не совсем так.

Я знал о типе Decimal в Python, но никогда не пытался его использовать. В нем указано, что "Десятичные числа могут быть представлены точно" , и я подумал, что это означает умную реализацию, которая позволяет представлять любое действительное число. Моя первая попытка:

>>> from decimal import Decimal
>>> d = Decimal(1) / Decimal(3)
>>> d3 = d * Decimal(3)
>>> d3 < Decimal(1)
True

Довольно разочарованный, я вернулся к документации и продолжал читать:

Контекст арифметики - это спецификация среды, определяющая точность [...]

Хорошо, так что на самом деле есть точность. И классические проблемы могут быть воспроизведены:

>>> dd = d * 10**20
>>> dd
Decimal('33333333333333333333.33333333')
>>> for i in range(10000):
...    dd += 1 / Decimal(10**10)
>>> dd
Decimal('33333333333333333333.33333333')

Итак, мой вопрос: есть способ иметь десятичный тип с бесконечной точностью? Если нет, то какой более элегантный способ сравнения двух десятичных чисел (например, d3 < 1 должен возвращать False, если дельта меньше точности).

В настоящее время, когда я занимаюсь только делениями и умножениями, я использую тип Fraction:

>>> from fractions import Fraction
>>> f = Fraction(1) / Fraction(3)
>>> f
Fraction(1, 3)
>>> f * 3 < 1
False
>>> f * 3 == 1
True

Это лучший подход? Какие могут быть другие варианты?

4b9b3361

Ответ 1

Десятичный класс лучше всего подходит для сложения финансового типа, умножения вычитания, проблем типа разделения:

>>> (1.1+2.2-3.3)*10000000000000000000
4440.892098500626                            # relevant for government invoices...
>>> import decimal
>>> D=decimal.Decimal
>>> (D('1.1')+D('2.2')-D('3.3'))*10000000000000000000
Decimal('0.0')

Модуль фракций хорошо работает с описываемой областью проблем рационального числа:

>>> from fractions import Fraction
>>> f = Fraction(1) / Fraction(3)
>>> f
Fraction(1, 3)
>>> f * 3 < 1
False
>>> f * 3 == 1
True

Для чистой многоточности с плавающей точкой для научной работы рассмотрите mpmath.

Если ваша проблема может быть помещена в символическую область, рассмотрите sympy. Вот как вы справитесь с проблемой 1/3:

>>> sympy.sympify('1/3')*3
1
>>> (sympy.sympify('1/3')*3) == 1
True

Sympy использует mpmath для произвольной точности с плавающей запятой, включает в себя способность обрабатывать рациональные числа и иррациональные числа символически.

Рассмотрим чистое плавающее представление иррационального значения √2:

>>> math.sqrt(2)
1.4142135623730951
>>> math.sqrt(2)*math.sqrt(2)
2.0000000000000004
>>> math.sqrt(2)*math.sqrt(2)==2
False

Сравните с sympy:

>>> sympy.sqrt(2)
sqrt(2)                              # treated symbolically
>>> sympy.sqrt(2)*sympy.sqrt(2)==2
True

Вы также можете уменьшить значения:

>>> import sympy
>>> sympy.sqrt(8)
2*sqrt(2)                            # √8 == √(4 x 2) == 2*√2...

Тем не менее, вы можете видеть проблемы с Sympy похожими на прямые с плавающей запятой, если не осторожны:

>>> 1.1+2.2-3.3
4.440892098500626e-16
>>> sympy.sympify('1.1+2.2-3.3')
4.44089209850063e-16                   # :-(

Это лучше сделать с десятичной:

>>> D('1.1')+D('2.2')-D('3.3')
Decimal('0.0')

Или используя фракции или Sympy и сохраняя значения, такие как 1.1 как отношения:

>>> sympy.sympify('11/10+22/10-33/10')==0
True
>>> Fraction('1.1')+Fraction('2.2')-Fraction('3.3')==0
True

Или используйте Rational в sympy:

>>> frac=sympy.Rational
>>> frac('1.1')+frac('2.2')-frac('3.3')==0
True
>>> frac('1/3')*3
1

Вы можете играть с sympy live.

Ответ 2

Итак, мой вопрос: есть ли способ иметь десятичный тип с бесконечной точностью?

Нет, поскольку для хранения иррационального числа потребуется бесконечная память.

Где Decimal полезно использовать такие вещи, как денежные суммы, где значения должны быть точными, а точность известна априори.

Из вопроса, не совсем ясно, что Decimal более подходит для вашего случая использования, чем float.

Ответ 3

существует ли способ иметь десятичный тип с бесконечной точностью?

Нет; для любого непустого интервала на реальной строке вы не можете представлять все числа в наборе с бесконечной точностью, используя конечное число бит. Вот почему Fraction полезен, поскольку он хранит числитель и знаменатель как целые числа, которые могут быть представлены точно:

>>> Fraction("1.25")
Fraction(5, 4)

Ответ 4

Если вы новичок в Decimal, это сообщение актуально: Доступна личная точность с плавающей запятой Python?

Существенная идея ответов и комментариев заключается в том, что для сложных вычислительных задач, где необходима точность, вы должны использовать модуль mpmath https://code.google.com/p/mpmath/. Важное замечание состоит в том, что

Проблема с использованием десятичных чисел заключается в том, что вы не можете много сделать на математических функциях на десятичных объектах