# 浮点数

# 误差

  • 在 Python 解释器的底层,所有对象都以二进制的形式存储。但是,大部分十进制小数,都不能准确转换成二进制。
  • 例:以下算法,是将十进制浮点数,转换成二进制数
    def float_to_binary(x, precision=16):
        # 如果 x 为负数,则转换成正数,再处理
        prefix = '+'
        if x < 0:
            x = -x
            prefix = '-'
    
        # 提取 x 的整数部分
        integer_part = int(x)
        # 整数部分可以直接转换成二进制,没有误差
        binary_integer_part = bin(integer_part)
    
        # 提取 x 的小数部分
        fractional_part = x - int(x)
        # 第 n 次循环,将十进制小数乘以 2 ,此时个位数取值为 0 或 1 ,将这个值记作第 n 位二进制小数
        binary_fractional_part = ''
        while precision > 0 and fractional_part > 0:
            fractional_part *= 2
            bit = int(fractional_part)
            binary_fractional_part += str(bit)
            fractional_part -= bit    # 去掉个位数,进入下一次循环
            precision -= 1
    
        return prefix + binary_integer_part + '.' + binary_fractional_part
    
    • 转换的示例:
      >>> float_to_binary(0.1)  # 十进制的 0.1 ,转换成二进制之后,是一个无限循环小数
      '+0b0.0001100110011001'
      >>> float_to_binary(0.2)
      '+0b0.0011001100110011'
      >>> float_to_binary(0.3)
      '+0b0.0100110011001100'
      >>> float_to_binary(0.4)
      '+0b0.0110011001100110'
      >>> float_to_binary(0.5)
      '+0b0.1'
      
    • 可见,如果 x 反复乘以 2 之后,依然存在小数部分,则说明 x 不能准确转换成二进制。
  • 因此,即使浮点数的小数部分不超过 16 位,存储为 float 类型的对象时,也会存在很小的误差。例如:
    >>> 0.1 + 0.2
    0.30000000000000004
    >>> 0.1 * 3
    0.30000000000000004
    
  • 因此,如果想对浮点数进行精确的数学运算,可以将它乘以 10^n ,转换为 int 类型的对象。等到显示时,才转换成 float 类型。
    >>> ratio = 10
    >>> a = 0.1
    >>> a = int(a*ratio)
    >>> a
    1
    >>> print(a/ratio)
    0.1
    
    或者使用 decimal 等模块进行精确运算。

# import decimal

:Python 的标准库,用于存储高精度的浮点数。

# 用法

  • 使用 decimal.Decimal 对象可以存储高精度的浮点数。

    >>> from decimal import Decimal
    >>> Decimal(-10)
    Decimal('-10')
    >>> Decimal('3.14')       # 输入可以是 int 或 str 类型的数值
    Decimal('3.14')
    >>> Decimal(3.14)         # 将 float 对象直接转换成 Decimal 会有误差,这里应该改为 Decimal(str(3.14))
    Decimal('3.140000000000000124344978758017532527446746826171875')
    >>> str(Decimal('3.14'))  # Decimal 对象可以直接转换成字符串
    '3.14'
    
  • Decimal、int 对象之间可以直接进行运算,结果会返回一个 Decimal 对象。

    >>> Decimal('0.1') + Decimal('0.2')
    Decimal('0.3')
    >>> Decimal('0.1') + 0.2        # Decimal、float 对象之间不能进行运算
    TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'float'
    >>> Decimal('0.1') + 2
    Decimal('2.1')
    >>> 0 / Decimal('0.1')          # 被除数为 0 时,结果为 0
    Decimal('0E+1')
    >>> Decimal('0.1') / 0          # 除数为 0 时,抛出异常
    decimal.DivisionByZero: [<class 'decimal.DivisionByZero'>]
    >>> Decimal('3.14') < 3.14
    True
    
  • Decimal 对象的有效位数默认为 28 。

    >>> from decimal import Decimal, getcontext
    >>> getcontext().prec
    28
    >>> getcontext().prec = 4       # 设置有效位数
    >>> Decimal('0.1') / 3
    Decimal('0.03333')
    >>> Decimal('0.1234567') / 1    # 超出有效位数时,会自动四舍五入
    Decimal('0.1235')
    

# import fractions

:Python 的标准库,用于以分数的形式进行数学运算。

  • 官方文档 (opens new window)
  • 优点:
    • float 在存储时存在微小误差,而分数没有误差。
    • 一个很长的浮点数,对应的分数可能很短,方便供人查看。

# 用法

  • 例:创建 Fraction 对象

    >>> from fractions import Fraction
    >>> a = Fraction(1, 3)  # 输入分子、分母,创建一个 Fraction 对象
    >>> a
    Fraction(1, 3)
    >>> a.numerator         # 查看分子
    1
    >>> a.denominator       # 查看分母
    3
    >>> a.numerator = 2     # Fraction 对象是只读的,不允许修改
    AttributeError: can't set attribute
    >>> str(a)              # 转换成 str 类型
    '1/3'
    >>> float(a)            # 转换成 float 类型
    0.3333333333333333
    >>> int(a)              # 转换成 int 类型,这不会四舍五入,而是只保留 float 的整数部分
    0
    
    • 分子、分母必须都是 int 类型,否则抛出异常:
      >>> Fraction(1, 1.5)
      TypeError: both arguments should be Rational instances
      
    • 创建一个 Fraction 对象时,会自动进行约分,将分子、分母同时除以公约数。
      >>> Fraction(2, 2)
      Fraction(1, 1)
      >>> Fraction(4, 2)
      Fraction(2, 1)
      >>> Fraction(1, -2)     # 如果分母带有负号,则自动改为分子带负号
      Fraction(-1, 2)
      
    • 也可以通过其它方式,创建 Fraction 对象:
      >>> Fraction(0.3333)    # 输入一个 float 类型的数字
      Fraction(6004199023210345, 18014398509481984)
      >>> Fraction('0.3333')  # 输入一个 str 类型的浮点数
      Fraction(3333, 10000)
      >>> Fraction('1/3')     # 输入一个 str 类型的分数
      Fraction(1, 3)
      
  • 调用 Fraction.limit_denominator() 方法,可以限制分母的最大值:

    >>> Fraction(0.3333)
    Fraction(6004199023210345, 18014398509481984)
    >>> _.limit_denominator(100)  # 这会返回一个新的 Fraction 对象,取值接近 0.3333 ,但是分母不超过 100
    Fraction(1, 3)
    >>> _.limit_denominator(3)
    Fraction(0, 1)
    >>> _.limit_denominator(2)    # 如果分母的取值太小,则取值可能远离 0.3333
    Fraction(1, 2)
    >>> _.limit_denominator(1)    # 这里取值变成 0/1=0
    Fraction(0, 1)
    >>> _.limit_denominator(0)
    ValueError: max_denominator should be at least 1
    
  • 两个 Fraction 对象之间,可以进行算术运算:

    >>> Fraction(1, 2) + Fraction(1, 3)
    Fraction(5, 6)
    >>> Fraction(1, 2) * Fraction(1, 3)
    Fraction(1, 6)