Разъяснение по типу 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
Это лучший подход? Какие могут быть другие варианты?
Ответы
Ответ 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/. Важное замечание состоит в том, что
Проблема с использованием десятичных чисел заключается в том, что вы не можете много сделать на математических функциях на десятичных объектах