澄清Python中的Decimal类型

时间:2021-10-30 17:36:28

Everybody know, or at least, every programmers should know, that using the float type could lead to precision errors. However, in some cases, an exact solution would be great and there are cases where comparing using an epsilon value is not enough. Anyway, that's not really the point.

每个人都知道,或者至少每个程序员都应该知道,使用float类型可能会导致精度错误。但是,在某些情况下,确切的解决方案会很好,并且有时使用epsilon值进行比较是不够的。无论如何,这不是重点。

I knew about the Decimal type in Python but never tried to use it. It states that "Decimal numbers can be represented exactly" and I thought that it meant a clever implementation that allows to represent any real number. My first try was:

我知道Python中的Decimal类型但从未尝试过使用它。它声明“十进制数字可以精确表示”,我认为它意味着一个聪明的实现,允许表示任何实数。我的第一次尝试是:

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

Quite disappointed, I went back to the documentation and kept reading:

非常失望,我回到文档并继续阅读:

The context for arithmetic is an environment specifying precision [...]

算术的上下文是指定精度的环境[...]

Ok, so there is actually a precision. And the classic issues can be reproduced:

好的,所以实际上有一个精度。经典问题可以复制:

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

So, my question is: is there a way to have a Decimal type with an infinite precision? If not, what's the more elegant way of comparing 2 decimal numbers (e.g. d3 < 1 should return False if the delta is less than the precision).

所以,我的问题是:有没有办法让Decimal类型具有无限精度?如果不是,那么比较2个十进制数的更优雅的方法是什么(例如,如果delta小于精度,则d3 <1应该返回False)。

Currently, when I only do divisions and multiplications, I use the Fraction type:

目前,当我只进行分割和乘法时,我使用分数类型:

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

Is it the best approach? What could be the other options?

这是最好的方法吗?其他选择可能是什么?

4 个解决方案

#1


34  

The Decimal class is best for financial type addition, subtraction multiplication, division type problems:

Decimal类最适用于财务类型加法,减法乘法,除法类型问题:

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

The Fraction module works well with the rational number problem domain you describe:

Fraction模块适用于您描述的有理数问题域:

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

For pure multi precision floating point for scientific work, consider mpmath.

对于科学工作的纯多精度浮点,请考虑mpmath。

If your problem can be held to the symbolic realm, consider sympy. Here is how you would handle the 1/3 issue:

如果您的问题可以在符号领域中进行,请考虑同情。以下是处理1/3问题的方法:

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

Sympy uses mpmath for arbitrary precision floating point, includes the ability to handle rational numbers and irrational numbers symbolically.

Sympy使用mpmath作为任意精度浮点,包括象征性地处理有理数和无理数的能力。

Consider the pure floating point representation of the irrational value of √2:

考虑√2的无理值的纯浮点表示:

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

Compare to sympy:

与sympy相比:

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

You can also reduce values:

您还可以减少值:

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

However, you can see issues with Sympy similar to straight floating point if not careful:

但是,如果不小心,您可以看到Sympy的问题类似于直接浮点:

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

This is better done with Decimal:

使用Decimal可以做得更好:

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

Or using Fractions or Sympy and keeping values such as 1.1 as ratios:

或者使用Fractions或Sympy并保持1.1等值作为比率:

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

Or use Rational in sympy:

或者在同情中使用Rational:

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

You can play with sympy live.

你可以和他人一起玩。

#2


4  

So, my question is: is there a way to have a Decimal type with an infinite precision?

所以,我的问题是:有没有办法让Decimal类型具有无限精度?

No, since storing an irrational number would require infinite memory.

不,因为存储无理数将需要无限的内存。

Where Decimal is useful is representing things like monetary amounts, where the values need to be exact and the precision is known a priori.

Decimal有用的地方就是货币金额,其中值必须精确且精确度是先验已知的。

From the question, it is not entirely clear that Decimal is more appropriate for your use case than float.

从这个问题来看,并不完全清楚Decimal比float更适合你的用例。

#3


2  

is there a way to have a Decimal type with an infinite precision?

有没有办法让Decimal类型具有无限精度?

No; for any non-empty interval on the real line, you cannot represent all the numbers in the set with infinite precision using a finite number of bits. This is why Fraction is useful, as it stores the numerator and denominator as integers, which can be represented precisely:

没有;对于实线上的任何非空间隔,您不能使用有限数量的位来表示具有无限精度的集合中的所有数字。这就是Fraction有用的原因,因为它将分子和分母存储为整数,可以精确表示:

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

#4


1  

If you are new to Decimal, this post is relevant: Python floating point arbitrary precision available?

如果您是Decimal的新手,这篇文章是相关的:Python浮点任意精度可用吗?

The essential idea from the answers and comments is that for computationally tough problems where precision is needed, you should use the mpmath module https://code.google.com/p/mpmath/. An important observation is that,

答案和评论的基本思想是,对于需要精确度的计算难度较大的问题,您应该使用mpmath模块https://code.google.com/p/mpmath/。一个重要的观察是,

The problem with using Decimal numbers is that you can't do much in the way of math functions on Decimal objects

使用十进制数字的问题在于你在十进制对象上的数学函数方面做不了多少

#1


34  

The Decimal class is best for financial type addition, subtraction multiplication, division type problems:

Decimal类最适用于财务类型加法,减法乘法,除法类型问题:

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

The Fraction module works well with the rational number problem domain you describe:

Fraction模块适用于您描述的有理数问题域:

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

For pure multi precision floating point for scientific work, consider mpmath.

对于科学工作的纯多精度浮点,请考虑mpmath。

If your problem can be held to the symbolic realm, consider sympy. Here is how you would handle the 1/3 issue:

如果您的问题可以在符号领域中进行,请考虑同情。以下是处理1/3问题的方法:

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

Sympy uses mpmath for arbitrary precision floating point, includes the ability to handle rational numbers and irrational numbers symbolically.

Sympy使用mpmath作为任意精度浮点,包括象征性地处理有理数和无理数的能力。

Consider the pure floating point representation of the irrational value of √2:

考虑√2的无理值的纯浮点表示:

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

Compare to sympy:

与sympy相比:

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

You can also reduce values:

您还可以减少值:

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

However, you can see issues with Sympy similar to straight floating point if not careful:

但是,如果不小心,您可以看到Sympy的问题类似于直接浮点:

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

This is better done with Decimal:

使用Decimal可以做得更好:

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

Or using Fractions or Sympy and keeping values such as 1.1 as ratios:

或者使用Fractions或Sympy并保持1.1等值作为比率:

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

Or use Rational in sympy:

或者在同情中使用Rational:

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

You can play with sympy live.

你可以和他人一起玩。

#2


4  

So, my question is: is there a way to have a Decimal type with an infinite precision?

所以,我的问题是:有没有办法让Decimal类型具有无限精度?

No, since storing an irrational number would require infinite memory.

不,因为存储无理数将需要无限的内存。

Where Decimal is useful is representing things like monetary amounts, where the values need to be exact and the precision is known a priori.

Decimal有用的地方就是货币金额,其中值必须精确且精确度是先验已知的。

From the question, it is not entirely clear that Decimal is more appropriate for your use case than float.

从这个问题来看,并不完全清楚Decimal比float更适合你的用例。

#3


2  

is there a way to have a Decimal type with an infinite precision?

有没有办法让Decimal类型具有无限精度?

No; for any non-empty interval on the real line, you cannot represent all the numbers in the set with infinite precision using a finite number of bits. This is why Fraction is useful, as it stores the numerator and denominator as integers, which can be represented precisely:

没有;对于实线上的任何非空间隔,您不能使用有限数量的位来表示具有无限精度的集合中的所有数字。这就是Fraction有用的原因,因为它将分子和分母存储为整数,可以精确表示:

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

#4


1  

If you are new to Decimal, this post is relevant: Python floating point arbitrary precision available?

如果您是Decimal的新手,这篇文章是相关的:Python浮点任意精度可用吗?

The essential idea from the answers and comments is that for computationally tough problems where precision is needed, you should use the mpmath module https://code.google.com/p/mpmath/. An important observation is that,

答案和评论的基本思想是,对于需要精确度的计算难度较大的问题,您应该使用mpmath模块https://code.google.com/p/mpmath/。一个重要的观察是,

The problem with using Decimal numbers is that you can't do much in the way of math functions on Decimal objects

使用十进制数字的问题在于你在十进制对象上的数学函数方面做不了多少