澄清Python中的小数类型

发布于 2021-01-29 18:13:09

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

我知道DecimalPython中的类型,但从未尝试使用它。它指出“十进制数可以精确表示”,我认为这意味着可以实现任何实数的聪明实现。我的第一次尝试是:

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

所以, 我的问题是: 有没有办法让精度无限的小数类型?如果不是,比较2个十进制数字的更优雅的方法是什么(例如,如果增量小于精度,则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

这是最好的方法吗?还有其他选择吗?

关注者
0
被浏览
49
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

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

    分数模块与您描述的有理数问题域配合良好:

    >>> 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                   # :-(
    

    最好用Decimal完成:

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

    或在sympy中使用Rational:

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

    您可以和sympy
    live
    一起



知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看