Clarification on the Decimal type in Python

后端 未结 4 1671
挽巷
挽巷 2020-12-05 02:01

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

4条回答
  •  一整个雨季
    2020-12-05 02:49

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

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

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

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

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

    Consider the pure floating point representation of the irrational value of √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.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:

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

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

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

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

提交回复
热议问题